堆(顺序存储)

博客介绍了堆的基本操作及最大堆的建立方法。堆的操作包括结构、创建、插入和删除,插入时需根据完全二叉树特性比较节点值,删除时用最后节点替换根节点再下调。最大堆建立可先插入元素调整,也可顺序存元素再调整,后者时间复杂度为O(n)。


 优先队列:特殊的队列,取出元素的顺序是按照元素的优先权,而不是进入的先后。
 堆是利用完全二叉树进行存储,分为最大堆和最小堆。最大堆的每一个节点都大于其子树的任何一个节点,最小堆的每一个节点都小于其子树的任何一个节点。
 堆的操作集

堆的基本操作

MaxHeap Creat(int Maxsize); 创建一个空的最大堆
Boolean IsFull(MaxHeap H); 判断堆是否满
Inseret(MaxHeap H, ElemType e); 将元素插入最大堆
Boolean IsEmpty( MaxHeap H); 判断堆是否为空
Elemtype DeleteMax(MaxHeap H); 返回H中的最大值(取出并删除)

实现方法

结构

typedef struct HeapStruct *MaxHeap;
struct HeapStruct{
	ElemType *Elements; //数组基地址
	int Size;  //当前的最后一个元素
	int Capacity; //最大值
}

创建

MaxHeap Create( int Maxsize){
	MaxHeap H=malloc((Maxsize+1)*sizeof(sturct HeapStruct));
	H.Elements=(ElemType)malloc((MaxSize+1)*sizeof(ElemType));
	H.Size=0;
	H.Size=0;
	H.Elements[0]=MaxData; 
	return H;
	}

插入

 以顺序方式存储,考虑将元素插入到数组的最后一个元素之后,此时根据完全二叉树的父节点是其节点编号除以二取整的特性,可以求得父节点,将两个节点值进行比较,若不符合最大堆的性质,则交换。

void insert(MaxHeap H, ElemType e){
	int i;
	if(IsFull(H)) return;
	i=++H->Size;
	for(;H->Elements[i]<e;i/=2){
		H->Elements[i]=H->Elements[i/2]; //将父节点下调
	}
	H->Elements[i]=e;
}

 这里我们就能发现哨兵的作用,可以防止到了数组下标为0的位置之后继续比较。

删除

 为了保证最大堆完全二叉树的特性,首先想到用最后一个节点去替换根节点。替换过后,最大堆的有序性被改变,于是将根节点下调。

ElemType Delete(MaxHeap H){
	int Parent,Child;
	ElemType MaxItem,temp;
	if(IsEmpty(H) return;
	MaxItem=H->Elements[1];
	temp=H->Elements[H->Size];
	for(Parent=1;Parents*2<=H->Size;Parent=Child){
		Child=Parent*2;
		if(Child!=H->Size&&(H->Elements[Child]<H->Elements[Child+1])) Child++;
		if(temp>=H->Elements[Child]) break;
		else{
			H->Elements[Parent]=H->Elements[Child];
		}
		H->Elements[Parent]=temp;
		return MaxItem;
	}

 分析删除函数,通过将保存的原最大堆的最后一个值与子节点的最大值相比较,如果该子节点大于temp,说明temp的位置仍然在下一层,将该子节点上调一层,temp继续向下搜索。如果该子节点小于temp,说明temp的位置在上一层,则退出循环,然后将temp的值赋给Parent所指向的位置。通过上一步,我们知道Parent的值已经赋给其父节点,因此不会被破坏。同样,因为Parent这个位置之前有值存在,所以将temp放到这里时,二叉树仍然保持完全二叉树的形状。
 有两个条件需要注意,首先第一个,循环进行的条件Parents*2<=H->Size当这个条件不符合时,说明Parent所指向的节点没有儿子,所以Parent就是插入temp的位置。但是,该条件判断时只关注了没有儿子的情况,如果Parent 只有左儿子,temp显然应该插入到Parent节点的右儿子处。因此,在没有右儿子或者右儿子大于左儿子时,都要将Child的值加一,保证插入/下一次比较的节点是右儿子或者其子树。这也就是Child!=H->Size的作用。

最大堆的建立

 显然,最大堆的建立过程可以仿照二叉搜索树,先插入一个元素,后来插入的元素顺次调整。由完全二叉树的特性,每个元素插入时调整的时间复杂度为O(log2n),共需要插入n个元素,则时间复杂度为O(nlog2n).
 而如果在数组中顺序存入n个元素,再将其调整为堆,则时间复杂度为O(n)。考虑删除算法,其本质是在两个堆中插入一个元素,将这两个堆合并成一个堆。因此,从第一个有子节点的节点开始考虑,由于它最多只有左右子节点,因此,这两个节点必然构成两个堆。将父节点按照删除算法的方式向下调整,则得到一个新的堆,新堆比原先的两个堆高度高一。接着按照数组中下标顺序向前调整,直到根节点。

    void PercDown( MaxHeap H, int p )
    { /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
        int Parent, Child;
        ElementType X;
     
        X = H->Data[p]; /* 取出根结点存放的值 */
        for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
            Child = Parent * 2;
            if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
                Child++;  /* Child指向左右子结点的较大者 */
            if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
            else  /* 下滤X */
                H->Data[Parent] = H->Data[Child];
        }
        H->Data[Parent] = X;
    }
     
    void BuildHeap( MaxHeap H )
    { /* 调整H->Data[]中的元素,使满足最大堆的有序性  */
      /* 这里假设所有H->Size个元素已经存在H->Data[]中 */
     
        int i;
     
        /* 从最后一个结点的父节点开始,到根结点1 */
        for( i = H->Size/2; i>0; i-- )
            PercDown( H, i );
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值