《算法导论》第六章——堆排序——优先队列

  虽然写这个博客主要目的是为了给我自己做一个思路记忆录,但是如果你恰好点了进来,那么先对你说一声欢迎。我并不是什么大触,只是一个菜菜的学生,如果您发现了什么错误或者您对于某些地方有更好的意见,非常欢迎您的斧正!

  最近在学习《算法导论》,这里是我对第六章的学习心得。有什么补充的欢迎在评论流。以下是我认为的一些知识点,就是我学习到的一些东西。
第六章堆排序
6.1堆
  (二叉)堆是一个数组,它可以被看成一个近似的完全二叉树。它的高度是O(logn)

在这里插入图片描述


  二叉堆可以分为最大堆和最小堆。

6.2维护堆的性质
  这一章我觉得主要就是学习MaxHeapity(A,i)这个函数。我们先看一下这个函数的执行
过程:

在这里插入图片描述


  具体的解释我在注释里已经写清楚了。我们看图可以发现,它比较根结点与左儿子右儿子的大小,把最大的那个数的下标赋值给largest,如果largest最后等于根节点的下标,那么这一个部分就是符合最大堆性质的,如果largest最后不等于根结点的下标,那么将根结点与那个largest所在结点的数值进行交换。
  我一开始以为这个函数是比较高大上的,就是一个函数可以检索整棵树,就一直想不明白怎么写,后来发现它只是检查一个部分,真正要实现对整棵树的维护,得靠for循环遍历这棵树的每个根结点。
  代码部分我到最后一起给出。
6.3建堆
  我们用自底向上的方法利用过程MaxHeapity把一个大小为 n的数组转换为最大堆,我之前也说了,MaxHeapity只是维持一部分的性质,要想维护整棵树,就要靠for循环遍历这棵树的每个根结点。我们不难发现,数组A[n/2+1]到A[n](如果n=10,那么arr[6]到arr[10]都是叶结点)的这部分都是树的叶结点,所以我们只需要对arr[1]到arr[5]这一部分进for循环。
  过程如下:

在这里插入图片描述

6.4堆排序算法
①利用BulidMaxHeap把输入数组A[1..n]建成最大堆,其中n=A.length。
②因为数组中最大元素在根结点A[1],于是我们将A[1]与A[n]互换
③调用Maxheapity(A,1),因为此时根结点不是最大的数,我们要调用它维护堆的性质

在这里插入图片描述

在这里插入图片描述


  这个图看不看得懂其实并没有什么关系,能帮助理解,看不懂也不要太过强求,我觉得理解这个程序运行就OK了!上面①②③点写的已经比较详细了。如果还有什么不懂的,可以在评论与我交流,因为我也不一定会,哈哈哈。

6.5优先队列
  一开始学习的我并不知道“优先队列”是个什么东西,于是我查找了一下百度百科对它的定义:普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。(百度百科的链接在下面)
https://baike.baidu.com/item/%E4%BC%98%E5%85%88%E9%98%9F%E5%88%97/9354754?fr=aladdin
  我们先不讨论“队列”是什么,因为这一章在堆排序这一块里,所以我只说我对这个优先队列的理解:大家都知道排队的时候是先来先走,那么假设走的顺序变了,变成了按照身份尊卑排序,哪怕国王来的最晚,他也可以享受先走的权利,这个国王就是我们之前描述的最大堆的根结点。而平民,哪怕来的最早,只要有身份比他高的人来了,他就得排到后面去。(嗯,想了很久才举出来一个例子,想不到什么好例子,还请见谅)那么怎么区分你是国王还是平民呢,那就得看数值大小了。好了例子举完了,虽然这个时候可能还是脑袋一团浆糊,那么功利一点地说:优先队列这一章,就是你要利用你学过的堆排序,去实现以下四个函数!(感觉这才是我的画风,单刀直入,emmm不知道为什么我要举个例子)

在这里插入图片描述


(别问我为什么不是一二三四地去实现,书上就是这么写的!)

第二个函数的实现:Maximum(S)
  我们之前说过了,最大堆的最大的那个数在根结点,那么这个函数里我们只要返回A[1]就可以了,对,就是这么简单!
第三个函数的实现:ExtractMax(S)
  我们来看书本中给出的伪代码,具体解释看我的注释。

1.   if A.size < 1      //这两行判断非空
2.       error “heap underflow”
3.   max = A[1]       //把最大值(根结点)赋给max
4.   A[1]=A[A.size]       //把最后一个数放到根结点
5.   A.size = A.size - 1      //长度减一
6.   MaxHeapity (A,1)      //因为第4行破坏了最大堆,这个函数是维护堆的性质
7.   return max      //返回最大值

第四个函数的实现:IncreaseKey(S,x,k)
  这就是说,本来x是个平民或者一个小小的官,我现在要给它一个更大的官当,或许直接让他当国王,那么它就得从叶结点变成某个根结点。我们来看它的伪代码:

1.  if key < A[i]    //如果新的官比原来的官小就报错(没人愿意降级吧)
2.     error “new key is smaller than current key”
3.   A[i]=key   //升值
4.   while i>1 and A[parent(i)] < A[i]     //如果i>1并且新的值比它的父值要大,就交换
5.     exchange A[i] with A[parent(i)]
6.     i=parent(i)     //把父亲的下标赋给i

在这里插入图片描述

第一个函数的实现:Insert(S,x)
  把元素x插入到S中。书中的思路是:首先增加一个关键字为-∞(也就是很小啦)的一个叶结点,然后调用第三个函数(也就是增加结点关键字的值)来插入这个最大堆。(感觉前人真的很强!)伪代码如下:

在这里插入图片描述


  这里的话我觉得我不会动态增加什么的,所以我只能一开始就设置一个比较大的数组了。如果你有什么好的主意,请在评论区告诉我,感激不尽!

堆排序.h
#pragma once
typedef int dataType;

/*打印数组*/
void PrintTreeArray(dataType arr[], int length);

/*维护堆的性质*/
void MaxHeapity(dataType arr[], int length, int i);

/*建堆*/
void BulidMaxHeap(dataType arr[],int length);

/*堆排序算法*/
void HeapSort(dataType arr[], int length);

/*②返回S中具有最大关键字的元素*/
dataType Maximum(dataType S[]);

/*③去掉并返回S中具有最大关键字的元素*/
dataType ExtractMax(dataType S[], int* length);

/*④将元素S[i]的关键字增加到key*/
void IncreaseKey(dataType S[], int i, dataType key);

/*①插入一个key到集合S中*/
void InsertKey(dataType S[], dataType key);

/*测试函数*/
void TestStack();
堆排序.cpp
#include "堆排序.h"
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

/*打印数组*/
void PrintTreeArray(dataType arr[], int length)
{
	for (int i = 1; i < length; i++)/*我们的数组从1开始*/
		cout << arr[i] << " ";
	cout << endl; cout << endl;
}

/*维护堆的性质*/
void MaxHeapity(dataType arr[], int length, int i)
{
	int left = 2 * i;        /*左儿子的下标*/
	int right = left + 1;    /*右儿子的下标*/
	int largest;             /*记录根结点与左右儿子中值最大的下标*/
	if (left <= length && arr[left] > arr[i])/*如果左儿子大于根结点*/
		largest = left;
	else
		largest = i;
	if (right <= length && arr[right] > arr[largest])/*如果右儿子大于目前记录的最大的那个数*/
		largest = right;

	if (largest!=i)/*如果最大的数不是根结点,就交换*/
	{
		int temp = arr[largest];
		arr[largest] = arr[i];
		arr[i] = temp;
		MaxHeapity(arr, length, largest);/*对这个数再进行检查*/
	}
}

/*建堆*/
void BulidMaxHeap(dataType arr[], int length)
{
	int i;
	for (i = length / 2+1; i >= 1; i--)
	{
		MaxHeapity(arr, length, i);
	}
}

/*堆排序算法*/
void HeapSort(dataType arr[], int length)
{
	int i;
	int size = length-1;
	/*①利用BulidMaxHeap把输入数组A[1..n]建成最大堆,其中n=A.length。*/
	BulidMaxHeap(arr, length);
	for (i = length-1; i >= 2; i--)
	{
		/*②因为数组中最大元素在根结点A[1],于是我们将A[1]与A[n]互换*/
		int temp = arr[i];
		arr[i] = arr[1];
		arr[1] = temp;

		size--;
		/*③调用Maxheapity(A,1),因为此时根结点不是最大的数,我们要调用它维护堆的性质*/
		MaxHeapity(arr, size, 1);
	}
}

/*②返回S中具有最大关键字的元素*/
dataType Maximum(dataType S[])
{
	return S[1];
}

/*③去掉并返回S中具有最大关键字的元素*/
dataType ExtractMax(dataType S[], int* length)
{
	if (*length < 1)
		cout << "heap underflow" << endl;
	dataType max = S[1];
	S[1] = S[*length-1];
	*length = *length - 1;
	MaxHeapity(S, *length,1);
	return max;
}

/*④将元素S[i]的关键字增加到key*/
void IncreaseKey(dataType S[], int i, dataType key)
{
	if (key < S[i])
		cout << "new key is smaller than current key!" << endl;
	S[i] = key;/*增加关键字的值*/
	while (i > 1 && S[i / 2] < S[i])/*S[i/2]是S[i]的根结点,可以自己观察一下*/
	{
		int temp = S[i/2];
		S[i / 2] = S[i];
		S[i] = temp;
		i = i / 2;
	}
}

/*①插入一个key到集合S中*/
void InsertKey(dataType S[], dataType key,int *length)
{
	*length = *length + 1;
	S[*length - 1] = -100;/*假装我这里-100是-∞*/
	IncreaseKey(S, *length - 1, key);/*因为length为11的时候其实只有10个数*/
}

/*测试函数*/
void TestStack()
{
	/*对MaxHeapity函数(维护堆的性质)的测试*/
	cout << "对MaxHeapity函数(维护堆的性质)的测试" << endl;
	int arr[11] = { 0,16,4,10,14,7,9,3,2,8,1 };
	PrintTreeArray(arr, 11);
	MaxHeapity(arr, 11, 2);
	PrintTreeArray(arr, 11);
	cout << endl;

	/*对BulidMaxHeap函数(建堆)的测试*/
	cout << "对BulidMaxHeap函数(建堆)的测试" << endl;
	int arr2[11] = { 0,4,1,3,2,16,9,10,14,8,7 };
	PrintTreeArray(arr2, 11);
	BulidMaxHeap(arr2, 11);
	PrintTreeArray(arr2, 11);
	cout << endl;

	/*对HeapSort函数(堆排序算法)的测试*/
	cout << "对HeapSort函数(堆排序算法)的测试" << endl;
	int arr3[11]= { 0,4,1,3,2,16,9,10,14,8,7 };
	PrintTreeArray(arr3, 11);
	HeapSort(arr3, 11);
	PrintTreeArray(arr3, 11);
	cout << endl;
	/*我们建立随机函数进行一下最大堆排序测试*/
	int length;
	int k;
	srand((unsigned)time(NULL));/*建立随机种子*/
	cout << "输入你想要的数组长度:";
	cin >> length;
	length++;
	dataType* arr4 = new dataType[length];
	arr4[0] = 0;
	for (k = 1; k < length; k++)
	{
		arr4[k] = rand() % 100 + 1;
	}
	PrintTreeArray(arr4, length);
	HeapSort(arr4, length);
	PrintTreeArray(arr4, length);
	delete[] arr4;
	cout << endl;

	/*优先队列的测试*/
	cout << "对优先队列的测试" << endl;
	int queueLength;
	cout << "输入你想要的数组长度:";
	cin >> queueLength;/*你输入11的话其实我要建立一个12的数组,因为0的位置我们不放关键字*/
	queueLength++;
	dataType Sarray[50] = { 0 };
	Sarray[0] = 0;
	for (k = 1; k < queueLength; k++)
	{
		Sarray[k] = rand() % 100 + 1;
	}
	cout << "先看一下我们的队列" << endl;
	PrintTreeArray(Sarray, queueLength);
	BulidMaxHeap(Sarray, queueLength);/*我们把它变成一个最大堆*/
	cout << "看一下变成最大堆后我们的队列:" << endl;
	PrintTreeArray(Sarray, queueLength);
	cout << "测试第二个函数,返回最大值:" << Maximum(Sarray) << endl; cout << endl;
	cout << "测试第三个函数,去掉并返回S中具有最大关键字的元素" <<endl;
	cout << "此时我们去掉了它的最大值:" << ExtractMax(Sarray, &queueLength) << endl;
	cout << "去掉最大值后,我们的函数变成了:" << endl;
	PrintTreeArray(Sarray, queueLength);
	cout << "测试第四个函数,将元素S[i]的关键字增加到key" << endl;
	int key;
	int n;
	cout << "输入key的值:";
	cin >> key;
	cout << "你想增加第几个数的的关键值:";
	cin >> n;
	n--;
	IncreaseKey(Sarray, n, key);
	cout << "第四个函数运行后,我们的数组:" << endl;
	PrintTreeArray(Sarray, queueLength);
	cout << "测试第一个函数,把一个关键字key插入到S中" << endl;
	cout << "输入你想插入的值:";
	cin >> key;
	InsertKey(Sarray, key,&queueLength);
	cout << "插入key后,我们的数组:" << endl;
	PrintTreeArray(Sarray, queueLength);
	getchar();
}
主函数
#include "堆排序.h"
#include <stdio.h>

int main()
{
	TestStack();
	getchar();
	return 0;
}
运行结果
这里是堆排序:

在这里插入图片描述


这里是优先队列:
在这里插入图片描述

参考网站以及博客:
算法 —— 排序 —— 优先队列
https://blog.csdn.net/qian520ao/article/details/80531150
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值