【算法设计与分析】堆排序

堆的概念

堆(Heap)可以定义为一颗二叉树,且这个树满足两个条件:

  • 是颗完全二叉树(essentially complete): 只有树的最后一层的最右边可以有缺位
  • 满足堆特性(heap property)—父母优势(parental dominance): 每一个节点的键都大于等于它子女的键(键指该节点在堆中的位置,根节点的位置是1)

堆中,键值从上到下排序
键值之间不存在从左到右的次序(同一节点的左右子树之间没有任何关系)
堆的根包含了堆的最大元素
堆的一个节点以及该节点的子孙也是一个堆


堆和堆的数组表示

在这里插入图片描述

坐标012345678910
10875216351

可以使用数组的方式来实现堆,方法是从上到下从左到右来记录堆中的元素。为了后面计算方便,需要将数组的第0位空出来。在这种条件下,数组中的数值就有了下面2个特性

  • 1.父母节点会在数组的前 ⌊ n / 2 ⌋ \lfloor n/2 \rfloor n/2个位置中,叶子节点则会在后面 ⌈ n / 2 ⌉ \lceil n/2\rceil n/2个位置。
    例如对于上面的堆,前 ⌊ n / 2 ⌋ \lfloor n/2 \rfloor n/2坐标5及坐标5以前的位置:10,8,7,5,2。(此时根节点10是坐标1)。
  • 2.对于坐标为i的父母节点(1≤i≤ ⌊ n / 2 ⌋ \lfloor n/2 \rfloor n/2),它的子女的坐标在2i和2i+1
    同时对于坐标为i的子女节点来说,它的父节点坐标为 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2


构建堆

构建堆指的是,给定一个乱序数组,需要将它转换成堆数组的形式(即满足上面的两个特性),有两种方法来实现堆排序。

自底向上(bottom-up heap construction)

最后的父母节点开始,到根节点为止,检查这些节点的值是否满足父母优势,如果不满足父母优势,就将该节点的值和子女节点中值更大的那个交换。迭代一次之后,如果此时数组还不是堆数组,就再迭代一次,直到最后一次迭代时不再有父母节点和子女节点交换值的过程。

以数组[2,9,7,6,5,8]为例,首先这个数组不是个堆数组。数组中有6个数,前三个是父母节点中的数。构建堆的过程为:

坐标0123456
297658

坐标为3 的节点值为7,子节点坐标为6,7。
坐标7处已经没有节点了忽略,由于7小于8,坐标3和6的值交换,变为:

坐标0123456
298657

坐标为2 的节点值为9,子节点坐标为4,5。
父节点的值大于子节点的值,不做处理、

坐标为1 的节点值为2,子节点坐标为2,3。
此时交换坐标1和坐标2处节点的值,数组变为:

坐标0123456
928657

到此,第一次迭代完成。由于这次迭代中有父节点和子节点交换数值的情况,所以不能确定数组是否已迭代完成。所以要再从坐标3开始进行一轮新的迭代
最后迭代完的堆数组为:

坐标0123456
968257

自底向上的算法可以让规模为n的数组在2n次以内完成堆的构造过程。

自顶向下堆构造(top-down heap construction)

自顶向下的堆构造方式为:每次选取一个新的节点放在数组的最末端,然后将该节点和父节点比较,如果该节点大于父节点,则进行交换,并继续比较该节点和交换后的新的父节点的值,直到该节点的值小于其父节点或者该节点成为根节点。

由于堆的高度大约为 l o g 2 n log_2n log2n,所以插入的时间效率为 O ( l o g 2 n ) O(log_2n) O(log2n)

public static int[] createMaxHeap(int[] nums) {
//		自顶向下构造堆,时间复杂度为O(logn)
		if(nums==null)	return null;
		
		int[] heap=new int[nums.length+1];
		int p=1, pCur, pParent, tmp;
		for(int n:nums) {
			heap[p] = n;
			pCur = p;
			pParent = pCur/2;
			while(pParent>=1&&heap[pParent]<heap[pCur]) {
					tmp = heap[pParent];
					heap[pParent] = heap[pCur];
					heap[pCur] = tmp;
					pCur = pParent;
					pParent /= 2;
			}
			p++;
		}
		return heap;
	}



删除堆中的节点

如果需要删除一个节点,则将该节点和堆中的最后的一个节点交换,删除交换后的最后一个节点,然后按照自底向上的方式重新构造堆。
删除的时间效率同样为 O ( l o g 2 n ) O(log_2n) O(log2n)

public static int[] deleteNode(int[] heap, int num) {
		/*
		 * 删除堆中的节点
		 * 输入:heap 堆数据,num指定删除的节点值
		 * 输出:删除该节点后堆数组,长度是heap的长度-1
		 * 如果堆中没有这个元素,返回null
		 */
		Boolean hasThisNum = false;
		for(int i=1;i<heap.length;i++)
			if(heap[i] == num) {
				heap[i]=heap[heap.length-1];
				hasThisNum = true;
				break;
			}
		if(!hasThisNum)
			return null;

		int[] newHeap = new int[heap.length-2];
		for(int i=0;i<heap.length-2;i++)
			newHeap[i] = heap[i+1];
		return createMaxHeap(newHeap);
	}



堆排序(heapsort)

堆排序的过程为:

  • 构造堆—删除根节点
  • 重复上述操作直到堆中不再有节点
    时间效率θ( n l o g n nlogn nlogn)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值