8-7选择排序-堆排序

一.回顾
8-7选择排序-堆排序_第1张图片
对此二叉树进行顺序存储(按层序遍历存),图中结点的序号表示数组中存放的位置

i为当前结点序号(从1开始,同数组下标),n表示总的元素个数,当前n=12

其中1-6是叶子结点,因此非叶子结点可表示为1~⌊n/2⌋,叶子结点可表示为⌊n/2⌋+1~n
在这里插入图片描述
对于某个结点i
①i的左孩子的序号应为2i
②右孩子应为2i+1
③父节点为⌊i/2⌋
④所在层次为⌈ l o g 2 ( i + 1 ) log_2{(i+1)} log2(i+1)⌉或⌊ l o g 2 i log_2i log2i⌋+1
⑤如果2i≤n,表示当前结点i有左孩子
⑥2i+1≤n表示有右孩子
⑦如果当前结点i>⌊n/2⌋,说明当前结点是叶子结点
⑧如果当前结点i≤⌊n/2⌋,说明当前结点是分支节点(非叶子)

二.概念引入:堆
在这里插入图片描述
解释:对于一个分支节点i,他的值≥左右孩子的值,称为大根堆。即 根≥左右

在这里插入图片描述
解释:对于一个分支节点i,他的值≤任意左右孩子的值,称为小根堆。即 跟≤左右

如图是一个大根堆的存储视角(左)和逻辑视角(右)

8-7选择排序-堆排序_第2张图片
三.大根堆的建立

从最后一个分支节点开始检查
09→78→17→53
8-7选择排序-堆排序_第3张图片
从09开始,如果其值大于左右孩子,则保存不变,继续看上一个分支节点。否则,找到左右孩子中最大的,与之交换

此处09和32交换
8-7选择排序-堆排序_第4张图片
检查78,和87换
8-7选择排序-堆排序_第5张图片
检查17,和45换
8-7选择排序-堆排序_第6张图片
检查53,和87换
8-7选择排序-堆排序_第7张图片
完成。刚刚降下来的53不满足大根堆,于是53和78换
注:在代码执行时,需要先判断(如果插入)是否满足大根堆,如果满足再插入53。如果不满足,将大的孩子拿上去,再重复对比其孩子
8-7选择排序-堆排序_第8张图片
至此完成了大根堆的建立

代码实现

void BuildMaxHeap(int a[],int len){
		for (int i = len / 2; i > 0; i--)//从最后一个非叶子结点开始构建大根堆
			HeadAdjust(a, i, len);
}

void HeadAdjust(int a[], int k, int len)//建立大根堆
{
	a[0] = a[k];//0作为哨兵,暂存根节点的值
	for (int i = 2 * k; i <= len; i=i*2)//len表示数组长度(从1起),即元素个数
	{
		if (i<len&&a[i] < a[i + 1]) 
			i++;//i指向左右孩子较大的一个,i
		if (a[0] >= a[i]) 
			break;
		else {
			a[k] = a[i];//大的孩子拿上去,当前位置可视为空缺
			k = i;//k指向当前空缺位置。能否将a[0]放入,需要再判断待插入元素和其孩子的关系,看是否满足大根堆,因此继续for循环。详见下图(图1)
		}
	}
	a[k] = a[0];//for循环结束,满足大根堆,a[0]放回来
}

8-7选择排序-堆排序_第9张图片
图 1 图1 1

四.基于大根堆从小到大的选择排序-堆排序

回顾选择排序:选择最大(小)的元素加入有序子序列

将堆顶的87与堆底元素9交换
8-7选择排序-堆排序_第10张图片
此时已将最大的元素放到了正确的位置
8-7选择排序-堆排序_第11张图片
当前在堆顶的09不满足大根堆,调用HeadAdjust(int a[], int k, int len)建立新的大根堆。注意此时的len变为了7(已经确定位置的元素87无需参与)

09按如上的方法下坠,至此完成了第一趟的处理
8-7选择排序-堆排序_第12张图片
第一趟的结果:确定了最大元素的正确位置,将剩余元素恢复成了大根堆

后续的处理同上,经过n-1趟的处理完成了排序

同样小根堆实现的是递减的排序

代码实现(大根堆的堆排序)

#include
#include
using namespace std;
void HeadAdjust(int a[], int k, int len)//建立大根堆
{
	a[0] = a[k];//0作为哨兵,暂存根节点的值
	for (int i = 2 * k; i <= len; i=i*2)
	{
		if (i<len&&a[i] < a[i + 1]) 
			i++;//i指向左右孩子较大的一个,i
		if (a[0] >= a[i]) //和左右孩子中较大的比
			break;
		else {
			a[k] = a[i];
			k = i;//继续下坠
		}
	}
	a[k] = a[0];
}
int main()
{
	int a[6] = { 0,432,21,46,4322,645 };
	int len = 5;
	for (int i = len / 2; i > 0; i--)//从最后一个非叶子结点开始构建大根堆
		HeadAdjust(a, i, len);
	for (int i = len; i > 1; i--)//a[1]放到最后
	{
		swap(a[i], a[1]);
		HeadAdjust(a, 1, i - 1);//剩余元素再构建
	}
	for (int i = 1; i <= 5; i++)
	{
		cout << a[i] << " ";
	}
}

五.效率分析

1.时间复杂度
(1)建堆过程:
每次最多对比关键字两次:左右孩子比较,根和大的那个比较
如果只有左孩子,则只需要一次关键字的对比
如果树高为h,当前结点在第i层,则一共需要下坠h-i层,对比关键字不超过2(h-i)
而h=⌊ l o g 2 n log_2n log2n⌋+1
第i层最多有 2 i − 1 2^{i-1} 2i1个结点,只有第1~h-1层才可能出现元素下坠
因此第一层有 2 1 − 1 2^{1-1} 211=1个结点,下坠共需要1×2(h-1)次关键字对比;第二层共有 2 2 − 1 2^{2-1} 221=2个结点,下坠共需要2×2(h-2)次关键字对比;第h-1层共有 2 h − 2 2^{h-2} 2h2,下坠共需要 2 h − 2 2^{h-2} 2h2×2次关键字对比,带入h,最终结果≤4n

因此建堆的过程,关键字对比次数不超过4n,建堆时间复杂度为O(n)

(2)排序过程
一共需要n-1趟,每趟都要交换两个元素,并进行堆顶元素的下坠,下坠最多h-1层,而每下坠一层最多对比关键字2次。
h=⌊ l o g 2 n log_2n log2n⌋+1,因此下坠的时间复杂度不会超过O( l o g 2 n log_2n log2n),一共n-1趟排序的时间复杂度O(n l o g 2 n log_2n log2n)

因此堆排序的时间复杂度=建堆+排序=O(n l o g 2 n log_2n log2n)

2.空间复杂度O(1)
3.稳定性

if (i<len&&a[i] < a[i + 1]) i++;//i指向左右孩子较大的一个,相等时保持左孩子不变
		if (a[0] >= a[i]) break;

2和1交换,2放到末尾,因此是不稳定的
8-7选择排序-堆排序_第13张图片
过程如下
8-7选择排序-堆排序_第14张图片
8-7选择排序-堆排序_第15张图片
结果
8-7选择排序-堆排序_第16张图片
六.总结
在这里插入图片描述

你可能感兴趣的:(数据结构,数据结构,算法,java)