最全详解二叉树之堆

文章目录

  • 最强详解二叉树之堆
  • 1.堆的概念及结构
  • 2.堆的存储方式
  • 3.堆的实现
    • 3.1向下调整法
    • 3.2向上调整法
    • 3.3堆的初始化
    • 3.4堆的扩容
    • 3.5堆的销毁
    • 3.6堆的插入
    • 3.7取堆顶的元素
    • 3.8堆的个数
    • 3.9堆的判空
    • 3.10交换函数与比较函数
    • 3.11 优化
  • 4.堆的应用
    • 4.1堆排序
  • 总结

最强详解二叉树之堆

1.堆的概念及结构

堆的概念:如果有一个关键码的集合k={k0,k1,k2,……kn-1},把所有的元素按照完全二叉树的顺序存储方式存储在一个一维数组中。如果满足:ki<=k2i+1且ki<=k2i+2; i=0,1,2……则称之为小堆;如果满足:ki>=k2i+1,ki>=k2i+2; i=0,1,2……则称之为大堆;将根节点最大的根叫做最大堆根或大根堆,将根节点最小的堆叫做最小堆或小根堆。

什么是堆?
通俗的来说就是二叉树的一种,按照特定规律摆放的一种完全二叉树。

对于任意的节点如果都比其孩子节点小是小堆;对于任意节点如果都比其孩子节点大为大堆。

最全详解二叉树之堆_第1张图片

2.堆的存储方式

堆的存储方式是属于二叉树的一种顺序结构。

普通的二叉树是不适合用数组来存储的。因为可能存在大量的空间浪费,而堆(完全二叉树)更适合使用顺序结构存储。

现实中我们通常把堆使用顺序结构的数组来存储。

需要注意的是这里堆和操作系统虚拟进程地址空间的堆是两回事, 一个是数据结构,一个是操作系统中管理内存的一块区域分段。

最全详解二叉树之堆_第2张图片

3.堆的实现

3.1向下调整法

向下调整法是先找到倒数第一个非叶子节点,按照大堆或者小堆,从下往上
依次进行向下调整,给入一个根节点,child默认为左孩子,判断左孩子存在的情况下,如果右孩子存在,先将左右孩子进行对比找出较大的那一个,再将较大的与根节点进行比较,如果较大的孩子大于根,则进行交换,然后将孩子节点当作根节点,找它的孩子的下标进行下一轮比较。直到左孩子节点小于size,退出循环,调整完毕。

最全详解二叉树之堆_第3张图片

 核心代码
void AdjustDown(Heap* hp, int parent)
{
     	//child为左孩子,因为parent可能不存在右孩子
	DataType* arr = hp->arr;
	int size = hp->size;
	int child = 2 * parent + 1;
	while (child < size)
	{
     	//右孩子存在的情况下
		//找到左右孩子中最小的孩子
		if (child + 1<size && hp->pcom(arr[child+1],arr[child ]))
		{
     
			child += 1;
		}
		//检测双亲是否满足堆的特性
		if (hp->pcom(arr[child] ,arr[parent]))
		{
     
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
     
			break;
		}
	}

}

3.2向上调整法

向上调整法:只需要将最后一个叶子节点与其根节点进行比较交换,从下往上进行调整。一般用在堆的插入。

void AdjustUp(Heap* hp)
{
     	
	int child =hp-> size - 1;
	int parent = ((child - 1) >> 1);

	while (child>=0 )
	{
     
		if (hp->pcom(hp->arr[child] , hp->arr[parent]))
		{
     
			Swap(&hp->arr[child], &hp->arr[parent]);
			child = parent;
			parent = ((child - 1)  >> 1);
		}
		else
		{
     
			return;
		}
	}

}

3.3堆的初始化

堆的初始化就是对结构体里面的成员,进行开辟空间,初始化,将数组中的元素放到创建的空间中,现在这个数组并不是堆,需要从倒数第一个非叶子节点进行调整一直调整到根节点,就可以调整成堆。这里的调整运用的是向下调整法。


//堆的初始化
void HeapInit(Heap* hp, DataType* arr, int size ,PCOM pcom)
{
     	
	int lastnotLeap;
	assert(hp);
	hp->arr = (DataType*)malloc((sizeof(DataType))*size);
	if (NULL == hp->arr)
	{
     
		assert(0);
		return;
	}
	hp->capacity = size;
	memcpy(hp->arr, arr, sizeof(DataType)*size);
	//将数组里面的元素放到hp中
	//for (int i = 0; i < size; ++i)
	//{
     
	//	hp->arr[i] = arr[i];
	//}
	hp->size=size;
	hp->pcom = pcom;
	//1.找倒数第一个非叶子节点
	lastnotLeap = ((size-1)-1) / 2;
	//2.从该节点位置开始逐个往前直到根节点,每遇到一个节点向下调整
	for (int root = lastnotLeap; root >= 0; root--)
	{
     
		AdjustDown(hp, root);
	}
}

3.4堆的扩容

堆的扩容实现了自动扩容,不用担心arr的空间不够。
判断如果有效个数size与容量capacity相等,则利用realloc创建空间大小为原来的2倍。然后将hp->capacity置为原来的2倍。

//扩容
void CheckCapacity(Heap* hp)
{
     
	assert(hp);
	if (hp->size == hp->capacity)
	{
     
		hp->arr = (DataType*)realloc(hp->arr, sizeof(DataType)*hp->size*2);
		if (NULL == hp->arr)
		{
     	
			assert(hp);
			return;

		}
		hp->capacity *= 2;

	}
}

3.5堆的销毁

  核心代码
void HeapDestroy(Heap* hp)
{
     
	assert(hp);
	if (hp->arr)
	{
     
		free(hp->arr);
		hp->arr = NULL;
		hp->capacity = 0;
		hp->size = 0;
	}

3.6堆的插入

插入 的时候,这里调用了扩容函数,会进行自动扩容,所以不用担心空间的问题;
将元素插入到数组的尾,在用向上调正,直到满足堆。

核心代码

void HeapInsert(Heap* hp, DataType data)
{
     
	CheckCapacity(hp);
	hp->arr[hp->size++] = data;
	AdjustUp(hp);

}

3.7取堆顶的元素

检测非空,然后直接返回h->arr[0];

 核心代码
DataType HeapTop(Heap* hp)
{
     
	assert(!HeapEmpty(hp));
	return hp->arr[0];
}

3.8堆的个数

检测hp的合法性,然后直接返回, hp->size。

 核心代码

int HeapSize(Heap* hp)
{
     
	assert(hp);
	return hp->size;
}

3.9堆的判空

检测hp的合法性,然后直接返回hp->size==0。

核心代码
int HeapEmpty(Heap* hp)
{
     
	assert(hp);
	return 0 == hp->size;
}

3.10交换函数与比较函数

交换函数

  核心代码
void Swap(DataType* left, DataType* right)
{
     
	int temp = *left;
		*left = *right;
	*right = temp;
	
}

比较函数:

 核心代码
int less(DataType left, DataType right)
{
     
	return left < right;
}
int Greater(DataType left, DataType right)
{
     
	return left > right;
}

3.11 优化

在进行堆实现的时候一般如果我们把代码写死的话,每次就只能写成大堆或者小堆。
在这里我们可以将有比较的写成比较函数,然后定义一个函数指针,指向比较函数,将函数指针当作参数,如果我们想要变为大堆或者小堆,只需要改变主函数里面的实参即可。

  核心代码
typedef int(*PCOM)(DataType left, DataType right);

typedef struct Heap
{
     
	DataType* arr;
	int size; 
	int capacity;
	PCOM pcom;//函数指针变量----指向所有的比较函数
}Heap;

4.堆的应用

4.1堆排序

堆排序:1.首先要进行建堆,如果排升序要建大堆,如果排降序要建小堆。
2.利用堆删除的思想进行排序----将根节点以最后一个叶子节点进行交换,然后删除最后一个节点,进行向下调整,调整好了,下标减去一 ,继续循环,直到节点被删完,退出循环,数组中输出的元素会从小到大排列好。

 核心代码
// 大堆,向下调整法
void HeapAdjust(int arr[], int size, int parent)
{
     
	int child = 2 * parent + 1;
	while (child <size)
	{
     
		if (child+1<size &&arr[child + 1]>arr[child])
			child += 1;
		if (arr[child]>arr[parent])
		{
     
			int temp = arr[child];
			arr[child] = arr[parent];
			arr[parent] = temp;
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
     
			return;
		}
	}
}

//堆的排序
void HeapSort(int arr[], int size)
{
     	
	int end = size - 1;//最后一个元素的下标
	//1建堆
	for (int root = (size - 2) / 2; root >= 0; root--)
	{
     
		HeapAdjust(arr, size, root);

	}
	//2.利用堆删除的思想来进行排序
	while (end > 0)
	{
     

		Swap(&arr[end], &arr[0]);
		HeapAdjust(arr, end, 0);
		end--;
	}
}

总结

将所学的知识进行总结,要走的路还有很长,继续加油吧!!!

你可能感兴趣的:(数据结构,堆排序,二叉树)