数据结构二叉树——堆

一:堆的实现

堆是一二叉树的一种(完全二叉树)。

堆数据结构的设计:采用数组来存储。

优点:堆是完全二叉树,存储高效,并且除了最后一层外,每一层都被完全填满,并且所有的节点都尽可能地向左对齐,同时可以通过 索引来理清父子关系。

cpp 复制代码
typedef int HPDataType;

typedef struct Heap
{
    HPDataType* a;
    int size;
    int capacity;
}HP;

堆的一些接口实现:

cpp 复制代码
// 初始化:
void HPInit(HP* php);

// 销毁:
void HPDestroy(HP* php);

// 判断堆是否为空
bool is_HPEmpty(HP* php);

// 往堆里面插入元素(在最后一个叶子结点插入):
void HPPush(HP* php, HPDdataType x);

// 弹出堆顶元素(取堆顶元素):
void HPPop(HP* php);

// 取堆顶的数据
HPDataType Top(HP* php);

初始化和销毁和链表的操作差不多:

插入接口算法的实现:

1:扩容

2:在堆的最后一个叶子节点后面插入数据

3:向上调整,使其仍然是一个堆(大堆或者小堆)

扩容部分算法也是和链表一样的,

向上调整算法:

核心思考逻辑:

依次用将父亲和孩子节点值比较,如果大小有区别就交换

更新孩子和父亲的坐标位置

当不满足大小关系或者调整到最上面一个位置的时候就退出循环

代码:

cpp 复制代码
// 向上调整建堆
void AdjustUp(HPDataType* a, HPDataType child)
{
	assert(a != NULL);

	HPDataType parent = (child - 1) / 2;

	while (child > 0)
	{
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

插入部分还要用到一个交换函数:

弹出堆顶元素接口实现:

1:将堆顶元素和堆的采用最后一个元素交换

2:覆盖删除掉堆顶元素

3:将堆顶元素用向下调整算法重新调整成一个堆

整体接口代:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;

typedef struct Heap
{
    HPDataType* a;
    int size;
    int capacity;
}HP;

// 初始化:
void HPInit(HP* php);

// 销毁:
void HPDestroy(HP* php);

// 判断堆是否为空
bool is_HPEmpty(HP* php);

// 往堆里面插入元素(在最后一个叶子结点插入):
void HPPush(HP* php, HPDataType x);

// 弹出堆顶元素(取堆顶元素):
void HPPop(HP* php);

// 取堆顶的数据
HPDataType Top(HP* php);

// 向上调整建堆
void AdjustUp(HPDataType* a, HPDataType child);

// 向下调整
void AdjustDown(HPDataType* a, int num, HPDataType parent);

// 打印函数
void Printf(int* a, int num);

// 堆排序
void HeapSort(HPDataType* arr, int size);
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "HP(practise).h"

// 初始化:
void HPInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->capacity = php->size = 0;
}

// 销毁:
void HPDestroy(HP* php)
{
	assert(php);

	free(php->a);

	php->a = NULL;
	php->capacity = php->size = 0;

}

// 判断堆是否为空
bool is_HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;

}

// 交换函数
void swap(HPDataType* num1, HPDataType* num2)
{
	HPDataType tmp = *num1;
	*num1 = *num2;
	*num2 = tmp;
}

// 向上调整建堆
void AdjustUp(HPDataType* a, HPDataType child)
{
	assert(a != NULL);

	HPDataType parent = (child - 1) / 2;

	while (child > 0)
	{
		if (a[parent] < a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// 往堆里面插入元素(在最后一个叶子结点插入):
void HPPush(HP* php, HPDataType x)
{
	assert(php);

	// 判断是否有足够的空间,不够的话就扩容
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));

		if (tmp == NULL)
		{
			perror("realloc fali!");
			return;
		}

		php->a = tmp;
		php->capacity = newcapacity;
	}

	php->a[php->size++] = x;

	// 向下调整建堆

	AdjustUp(php->a, php->size-1);
}

// 向下调整
void AdjustDown(HPDataType* a, int num, HPDataType parent)
{
	HPDataType child = parent * 2 + 1;

	while (child < num)
	{
		// 假设法找到比较小的一个孩子节点
		if (child + 1 < num && a[child] > a[child + 1])
		{
			child++;
		}

		if (a[parent] > a[child])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

// 取堆顶的数据
HPDataType Top(HP* php)
{
	assert(php);

	return php->a[php->size - 1];
}

// 弹出堆顶元素(取堆顶元素):
void HPPop(HP* php)
{
	assert(php);

	// 算法思想:
	// 先把堆顶的元素和最后一个元素交换,然后再覆盖删除
	// 交换好元素之后需要向下调整使其还是一个大堆或者小堆

	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}


// 打印函数
void Printf(HPDataType a[], int num)
{
	for (int i = 0; i < num; i++)
	{
		printf("%d  ", a[i]);
	}
	printf("\n");
}

// 堆排序
void HeapSort(HPDataType arr[], int size)
{

	// 先建堆
	// 再排序

	// 降序建小堆
	// 升序建大堆

	// 向上调整建堆(相当于push操作)
	//for (int i = 0; i < size; i++)
	//{
	//	AdjustUp(arr, i);
	//}

	// 向下调整建堆:从最后一个非叶子结点开始调整
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)  //  因为下标从0开始并且倒数第一个非叶子结点是最后一个下标减一除以2
	{
		AdjustDown(arr, size, i);
	}

	// 交换第一个和最后一个元素,指针依次往前挪,第一个元素向下调整使其成为相应的堆
	int end = size-1;

	while (end > 0)
	{
		swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}


}

测试函数:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "HP(practise).h"

void TestHeap01()
{
	HP hp;
	HPInit(&hp);

	HPPush(&hp, 1);
	HPPush(&hp, 2);
	// printf("%d", is_HPEmpty(&hp)); // 判断是否为空堆
	HPPush(&hp, 3);
	HPPush(&hp, 4);

	// 取堆顶元素
	printf("%d", Top(&hp));

	// Printf(hp.a, hp.size);
	// HPPop(&hp);
	// HPPop(&hp);
	// Printf(hp.a, hp.size);

	HPDestroy(&hp);

}

void TestHeapSort()
{
	// 给定一个数组,使用堆排序来将其排好

	int arr[] = { 11, 9, 5, 12, 13, 15};

	int size = sizeof(arr) / sizeof(arr[0]);

	HeapSort(arr, sizeof(arr) / sizeof(arr[0]));

	Printf(arr, size);
}

//void makeheap()
//{
//	int arr[] = { 11, 9, 5, 12, 13, 15 };
//	int size = sizeof(arr) / sizeof(arr[0]);
//
//	for (int i = (size-1-1) / 2; i >= 0; i--)
//	{
//		AdjustDown(arr, size, i);
//	}
//
//	Printf(arr, size);
//}

int main()
{

	TestHeap01();
	
	//TestHeapSort();

	//makeheap();

	return 0;
}

二:堆排序:

步骤:

1:先建一个堆

2:堆排序

升序建大堆,降序建小堆。

1:向上调整建堆

2:向下调整建堆

三:top K问题:

从一堆数据中选出前K个排名的数据

1:建一个小堆:

2:依次将所有数据和堆顶数据比较,根据所需要的排名要求再决定替换堆顶数据,然后小堆向下调整,依然是一个小堆,最后调整完成之后这个小堆里面的数据就是这K个需要选出来的数据,如果需要有序的话也可以重新进行排序

案例:从文件中读写数据,然后找出最大的K个数据。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include "HP(practise).h"


void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand((unsigned int)time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		// 使用fprintf将产生的随机数据写入打开的文件流中
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void PrintTopK(int k)
{
	CreateNDate();
	int* TopK_Heap = (int*)malloc(sizeof(int) * k);
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		ferror(fp);
		return;
	}

	// 使用fscanf将文件中的数据读出来,然后写入数组中
	for (int i = 0; i < k; i++)
	{
		fscanf(fp, "%d", &TopK_Heap[i]);
	}

	// 建堆
	for (int i = 0; i < k; i++)
	{
		(TopK_Heap, k, i);
	}

	// 从文件中取数据和堆顶AdjustDown数据比较,如果比堆顶数据大或者小就向下调整
	int x = 0;

	while(fscanf(fp, "%d", &x) > 0)
	{
		if (x > TopK_Heap[0])
		{
			TopK_Heap[0] = x;
			AdjustDown(TopK_Heap, k, 0);
		}
	}

	// 打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", TopK_Heap[i]);
	}
}

int main()
{
	int k;
	scanf("%d", &k);
	PrintTopK(k);

	return 0;
}
相关推荐
云卓SKYDROID8 分钟前
除草机器人算法以及技术详解!
算法·机器人·科普·高科技·云卓科技·算法技术
半盏茶香32 分钟前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
徐子童36 分钟前
双指针算法习题解答
算法
想要打 Acm 的小周同学呀1 小时前
LRU缓存算法
java·算法·缓存
劲夫学编程2 小时前
leetcode:杨辉三角
算法·leetcode·职场和发展
毕竟秋山澪2 小时前
孤岛的总面积(Dfs C#
算法·深度优先
浮生如梦_4 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
励志成为嵌入式工程师6 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉6 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer6 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法