堆是一种基本的数据结构。在这里我用数组来形容,在一个二叉堆的数组中,每一个元素都要保证大于等于另外两个特定位置的元素。同时相应的,这些元素又要大于等于另外两个相应位置的元素,整个数据结构以此类推。如果我们将整个数据结构画成树状结构,就能够清晰地看出整个结构的样子。
开始认为堆上层元素是必须全部大于下层元素的,后来发现这是不正确的
当一颗二叉树的每个结点都大于等于它的两个子节点时,它被称为堆有序。相应地,在堆有序的二叉树中,每个结点都小于等于它的父节点,从任意结点往上,我们都能得到一列非递减的元素;从任意结点往下,我们都能得到一列非递增的元素。根节点就是堆有序的二叉树中的最大节点。所以说在一个堆有序的二叉树中,父节点的位置是K/2,它的两个子节点的位置是2k,2k+1。这样通过索引的变化,我们就可以在数组中得到不同的元素了。
下面是堆的创建插入删除代码
代码每次插入删除都会进行打印,方面新学习堆的同学理解
#include <stdio.h>
#include <stdlib.h>
typedef struct HeapStruct *MaxHeap ;
struct HeapStruct
{
int *Element ; // 存储堆元素的数组
int Size; // 堆的当前元素个数
int Capacity ; // 堆的最大容量
};
MaxHeap Create ( int MaxSize )
{
// 创建容量为MaxSize的空的最大堆
MaxHeap H = malloc ( sizeof ( struct HeapStruct ) ) ;
H -> Element = malloc ( ( MaxSize + 1 ) * sizeof ( int ) ) ;
// 下标为1的位置开始存值,下标为0的位置不存放堆元素
H -> Size = 0 ;
H -> Capacity = MaxSize ;
H -> Element [0] = 1000 ; // 定义“哨兵”为大于堆中所有可能元素的值 便于以后更快操作
return H ;
}
void Insert ( MaxHeap H,int item )
{
// 将元素item插入最大堆H,其中H -> Elements [0] 已经定义为哨兵
int i ;
if ( H->Size ==1000 )
{
printf ( "最大堆已满" );
return ;
}
i = ++H -> Size ; // i 指向插入后堆中的最后一个元素的位置
for ( ; H -> Element [ i/2 ] < item; i /= 2 ) // 与父结点做比较,i / 2 表示的就是父结点的下标
H -> Element [ i ] = H -> Element [ i/2 ]; // 向下过滤结点
H -> Element [ i ] = item ; //若for循环完成后,i更新为父节点i,然后将 item 插入
for(i = 1 ; i <= H->Size; i++)
{
printf("%d", H->Element[i]);
}
printf("\n");
}
int DeleteMax( MaxHeap H )
{
/* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
int Parent, Child;
int MaxItem, temp;
if (H->Size == 0)
{
printf("最大堆已为空");
return;
}
MaxItem = H->Element[1]; /* 取出根结点最大值 */
/* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
temp = H->Element[H->Size--];
for( Parent=1; Parent*2<=H->Size; Parent=Child )
{
Child = Parent * 2;//child指向根下层元素的左节点
if( (Child!= H->Size) &&
(H->Element[Child] < H->Element[Child+1]) )
Child++; //Child指向左右子结点的较大者
if( temp >= H->Element[Child] ) break;//如果比较大的节点元素还大,就可以将temp元素放在树根
else //移动temp元素到下一层
H->Element[Parent] = H->Element[Child];//将儿子元素移到树根,temp再和下层元素作比较
}
H->Element[Parent] = temp;
int i;
for(i = 1 ; i <= H->Size; i++)
{
printf("%d", H->Element[i]);
}
printf("\n");
return MaxItem;
}
int main()
{
int l, n, i;
while(scanf("%d", &n)!=EOF)
{
struct HeapStruct *H = Create(n);
for( i = 0 ; i< n ;i++)
{
scanf("%d",&l);
Insert(H,l);
}
printf("请输入需要删除最大值的次数:\n");
int m;
scanf("%d", &m);
while(m--)
DeleteMax(H);
}
return 0;
}
下面是部分代码的讲解
- 插入代码
void Insert ( MaxHeap H,int item )
{
// 将元素item插入最大堆H,其中H -> Elements [0] 已经定义为哨兵
int i ;
if ( H->Size ==1000 )
{
printf ( "最大堆已满" );
return ;
}
i = ++H -> Size ; // i 指向插入后堆中的最后一个元素的位置
for ( ; H -> Element [ i/2 ] < item; i /= 2 ) // 与父结点做比较,i / 2 表示的就是父结点的下标
H -> Element [ i ] = H -> Element [ i/2 ]; // 向下过滤结点
H -> Element [ i ] = item ; //若for循环完成后,i更新为父节点i,然后将 item 插入
for(i = 1 ; i <= H->Size; i++)
{
printf("%d", H->Element[i]);
}
printf("\n");
}
如图我们建立一个堆,准备向里面插入数字6
根据代码可知让i = H->size++,并且将6赋值给item进行下面的比较
第一次i = 5, 那么i/2取整得2, 即6与父节点H -> Element [ 2 ] (4)进行比较,因为6>4,所以将H -> Element [ 2 ] (4)的值赋给H -> Element [ 5 ] ,即H -> Element [ i ] = H -> Element [ i/2 ]; //i=5,步骤完成后i = i / 2,将i进行更新,完成后如下图
此时i = 2,那么i/2 = 1,且item (6) > H->Element[1] (5),故重复上述步骤,步骤完成后如下图
现在i = 1了,但是程序并没有停止,此时i / 2 = 0,这时会和谁进行 比较呢,还记得我们定义了一个哨兵吗,哨兵定义为1000,故比item大,所以程序循环到这里停止,然后H->Element[1] = item,得出最终结果