虽然写这个博客主要目的是为了给我自己做一个思路记忆录,但是如果你恰好点了进来,那么先对你说一声欢迎。我并不是什么大触,只是一个菜菜的学生,如果您发现了什么错误或者您对于某些地方有更好的意见,非常欢迎您的斧正!
最近在学习《算法导论》,这里是我对第六章的学习心得。有什么补充的欢迎在评论流。以下是我认为的一些知识点,就是我学习到的一些东西。第六章堆排序
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