树的应用——堆


引子

优先队列
“优先队列” (Priority Queue),是一种特殊的“队列”,从队中取出元素的顺序是依照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。

利用数组或链表实现优先队列,则:
数组
插入:元素总是插入尾部 — O(1)
删除:查找最大(最小)的关键字 —O(n),从数组中删去需要移动元素—O(n)
有序数组
插入:找到合适的位置—O(n)orO(log2n),移动元素并插入—O(n)
删除:删去最后一个元素—O(1)
链表
插入:元素总是插入链表的头部—O(1)
删除:查找最大(最小)的元素—O(n),删除元素—O(1)
有序链表
插入:找到合适的位置—O(n),插入元素—O(1)
删除:删去首元素—O(1)
总的来讲,上述方法插入或删除操作至少有一个达到线性时间复杂性。

有没有一种实现方式,能使优先队列的插入删除操作时间复杂度都低于O(n)呢?——为此,引入堆的概念


一、堆的定义

  • 采用完全二叉树存储的优先队列,称为堆(Heap)。
    体现出堆的的特性:
    结构性:完全二叉树;
    有序性:根结点到任一结点的关键字序列保持非递增或者非递减。

常见的堆可分为最大堆(大根堆,大顶堆),最小堆(小根堆,小顶堆)
其中最大堆的定义:一个有N>0个元素的最大堆H是一棵完全二叉树,每个结点上的元素值不小于其子结点元素的值。
最大堆和最小堆

存储方式: 由于堆是一棵完全二叉树,因此,通常不必用链式存储,常用数组进行存储实现。(数组下标为0的地方通常不用来存储堆的结点。
在这里插入图片描述

二、最大堆的操作

1.ADT

类型名称:最大堆(MaxHeap)
数据对象集:一个有N>0个元素的最大堆H是一棵完全二叉树,每个结点上的元素值不小于其子结点元素的值。
操作集:对于任意最多有MaxSize个元素的最大堆H ∈ MaxHeap,元素item ∈ ElementType,主要操作有:
MaxHeap Create( int MaxSize ):创建一个空的最大堆。
Boolean IsFull( MaxHeap H ):判断最大堆H是否已满。
Insert( MaxHeap H, ElementType item ):将元素item插入最大堆H。
Boolean IsEmpty( MaxHeap H ):判断最大堆H是否为空。
ElementType DeleteMax( MaxHeap H ):返回H中最大元素(高优先级)

2.操作集

结构定义

typedef struct HeapStruct *MaxHeap;
struct HeapStruct{
	ElementType *Elements/*存储堆元素的数组*/
	int Size;			 /*当前元素的个数*/
	int Capacity;		 /*堆的最大容量*/
	
}

创建一个空的最大堆

MaxHeap Create(int Maxsize){
	MaxHeap H = malloc(sizeof(struct HeapStruct));
	H->Elements = malloc((Maxsize+1)*sizeof(Elements));
	H->Size = 0;
	h->Capacity = MaxSize;
	H->Elements[0] = MaxData;
	/*定义“哨兵”的值大于堆中所有可能元素,以便后续操作*/
	return H;
}

把MaxData换做MinData,则同样适用于最小堆的创建

最大堆的插入

最大堆的插入,前提必须满足完全二叉树的结构要求,即插在数组的末尾。所以插入操作需要做的就是确保堆的有序性
算法描述: 要插入的元素向上渗透。即只要大于其父结点关键字,就渗透成功,与父结点交换关键字,直到渗透失败为止。

void Insert(MaxHeap H,ElementType item){
	int i;
	if(IsFull(H)){
		printf("最大堆已满");
		return ;
	}
	i = ++H->Size;/*i指向符合结构要求的位置*/
	for(;H->Elements[i/2]<item;i/=2){
	/*H->Elements[0]是哨兵元素,它不小于堆中的最大元素,控制循环结束。*/
		H->Elements[i] = H->Elements[i/2];/*向上渗透*/
	}
	h->Elements[i] = item;/*插入*/
}

T(N) = O(logN)

最大堆的删除

算法描述: 取出根结点(最大值)元素,同时将最后一个结点移动到根结点位置。用该根结点向下渗透,调整堆使其有序。

ElementTyoe DeleteMax(MaxHeap H){
/*从最大堆H中取出键值为最大的元素,并删除一个结点*/
	int Parent,Child;
	ElementType MaxItemm,temp;
	if(IsEmpty(H)){
		printf("最大堆已为空");
		return;
	}
	MaxItem = H->Elements[1];/*取出根结点最大值*/
	/*用最大堆中最后一个元素从根结点开始向下渗透*/
	temp = H->Elements[H->Size--];/*暂存并删除最后一个元素*/
	for(Parent = 1;Parent*2 <= H->Size;Parent=Child){
		Child = Parent * 2;
		if(Child!=H->Size)&&(H->Elements[Child]<H->Elements[Child+1])
			Child++;/*Child指向左右结点的较大者*/
		if(temp >= H->Elments[Child]) break;
		else H->Elements[Parent] = H->Elements[Child];/*向下渗透*/
	}
	H->Elements[Parent] = temp;
	return MaxItem;
	
	
}

T(N) = O(logN)

最大堆的建立

“建立最大堆”是指如何将已经存在的N个元素按最大堆的要求存放在一个一维数组中。
通过最大堆的插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(N logN)。
有无更快的方法?——
第一步,将N个元素按输入顺序存入二叉树中,这一步只要求满足完全二叉树的结构特性,而不管其有序性。
第二步,调整各结点元素,以满足最大堆的有序特性。
()

MaxHeap BuildMaxHeap(MaxHeap H){
	/*H->Size个元素已经存在H->Elements[]中,此函数做元素调整,以满足最大堆的有序性*/
	int Parent,Child,i;
	ElementType temp;
	for(i = H->Size/2;i>0;i--){
	/*从最后一个结点的父结点开始向下渗透*/
		temp = H->Elements[i];/*父结点值*/
		for(Parent=i;Parent*2<=H->Size;Parent=Child){
		/*向下渗透*/
			Child = Parent * 2;
        	if( (Child!= H->Size) && (H->Elements[Child] < H->Elements[Child+1]) )
        	    Child++;  /*Child指向左右子结点的较大者*/
        	if( temp >= H->Elements[Child] ) break;
   			else  /* 渗透成功*/
       	   H->Elements[Parent] = H->Elements[Child];
  		} /* 结束内部for循环对以H->Elements[i]为根的子树的调整 */
 		 H->Elements[Parent] = temp;
	}
	return H;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值