基 础 算 法 普 及 之 堆 排 序 (中)
Posted 共振邀你来杂谈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基 础 算 法 普 及 之 堆 排 序 (中)相关的知识,希望对你有一定的参考价值。
下方二维码长按识别即可添加
以下是作者个人微信,有需要的可以添加
本次推文将介绍堆这个数据结构以及堆排序。本次讲解的是大顶堆结构,之后可能会补充索引堆以及最小索引堆等内容。
话不多说,先介绍”堆“这个数据结构。
讲解堆之前我们可以先了解一下二叉树这个数据结构。
首先介绍二叉树的结点数据结构,如下图所示
二叉树顾名思义是只有不超过两个叉的枝干的树形结构。如下图所示
上图展示的就是一个二叉树的结构,同时它还是一棵满二叉树。何为满二叉树?请看下图解释。
堆采用的就是类似二叉树的结构,但是堆一般是使用数组来实现的,一说到数组,索引便成了主要问题,如何将满二叉树转换为数组中的索引呢?这使用的便是满二叉树的特点,如果有分叉的话一定拥有左孩子。解释以及索引转换方法如下图所示,为了方便计算我们将浪费数组中的第0号空间。
本推文以介绍大顶堆为主,什么是大顶堆?大顶堆就是父节点的值大于其子节点的值,像上图的例子即为一个大顶堆。(题外语:大顶堆允许8号结点的值小于七号结点的值,而对于二叉搜索树来说这是不允许的。)
接下来将要介绍堆的两个重要方法shiftUp和shiftDown,前者用于往堆中插入一个新的结点,后者用于从堆中取出一个节点。
下面模拟一下shiftUp,即插入结点时的操作
step1:
step2:
step3:
step4:
因为51 小于 62,所以shiftUp过程结束。
构成的最大堆的二叉树形式如下图
对应的堆就按每个结点右上角的索引构成
shiftUp代码如下(C++):
template<typename Item>
//调用插入方法时用到shiftUp方法
void insert(Item item){
assert(count+1 <= capacity);
data[count+1] = item;
count++;
shiftUp(count);
}
void shiftUp(int k){
while(k>1 && data[k]>data[k/2]){
swap(data[k],data[k/2]);
k /= 2;
}
}
接下来再来介绍shiftDown方法。shiftDown方法用于从堆中取出节点操作中。
step1:
step2:
step3:
step4:
shiftDown后的堆的二叉树形结构如下图:
代码如下:(C++实现)
template<typename Item>
Item extractMax(){
assert( count > 0);
Item ret = data[1];
swap(data[1],data[count]);
count--;
shiftDown(1);
return ret;
}
void shiftDown(int k){
while(2*k<=count){
int j = 2*k;
//判断有无右孩子,再比较左右孩子大小
if(j+1<=count&&data[j+1]>data[j]){
j++;
}
if(data[k]>data[j]){
break;
}
swap(data[k],data[j]);
k = j;
}
}
有了这两个方法,我们就可以实现基础的最大排序
首先给定要排序的数组,再给定其长度,那么我们只需遍历一遍所给数组然后将其一个一个插入堆中即可,因为我们建立的是大顶堆即最大堆,假如我们想要得到一个从小到大的数组,那么我们就只需要将从堆中取出的结点从末尾放入待排序的数组,反之就从头往后放入。代码如下:
template<typename T>
void heapSort1(T arr[],int n){
MaxHeap<T> maxHeap = MaxHeap<T>(n);
for(int i=0;i<n;i++){
maxHeap.insert(arr[i]);
}
for(int i=n-1;i>=0;i--){
arr[i] = maxHeap.extractMax();
}
return;
}
之后是对heapSort1进行优化,优化的思路便是改进构建堆时不停的插入操作,解决方案为Heapify操作,如下图
step1:
step2:
step3:
Heapify结果如下图
Heapify代码如下:(C++实现)
MaxHeap(Item arr[], int n){
data = new Item[n+1];
count = n;
this->capacity = n;
for(int i=0;i<n;i++){
data[i+1] = arr[i];
}
for(int j=count/2;j>=0;j--){
shiftDown(j);
}
}
template<typename T>
void heapSort2(T arr[],int n){
MaxHeap<T> maxHeap = MaxHeap<T>(arr,n);
for(int i=n-1;i>=0;i--){
arr[i] = maxHeap.extractMax();
}
return;
}
对于以上方法我们还发现了问题,就是空间浪费问题,在每次构建堆时我们都需要创建一个与原数组一样大小的数组,这样花费了额外的空间,此时我们可以寻找一个原地排序的方法。
step:
1、对待排序的数组进行一次Heapify操作
2、每次取出堆中第一个元素与堆中最后一个元素交换位置
步骤如下图所示
step:
之后的步骤不再演示,大家应该都能明白我的意思
下一步便是将0号结点与3号结点交换位置,然后对交换位置后的0号结点进行shiftDown操作。
再下一步便是0号结点与2号结点交换位置,……
最后一步便是0号结点与1号结点交换位置,……
代码如下:(C++实现)
template<typename T>
void heapSort(T arr[],int n){
for(int i=(n-2)/2;i>=0;i--){
__shiftDown2(arr, n ,i);
}
for(int i=n-1;i>0;i--){
swap(arr[i],arr[0]);
__shiftDown2(arr,i,0);
}
return;
}
template<typename T>
void __shiftDown2(T arr[],int n,int k){
T e = arr[k];
while(2*k+1<n){
int j = 2*k+1;
if(j+1<n&&arr[j+1]>arr[j]){
j++;
}
if(arr[j]<=e){
break;
}
arr[k] = arr[j];
k = j;
}
arr[k] = e;
return;
}
以上便是今天的全部内容,大家若有疑问欢迎评论区留言或者私信我
预告:下次将会和大家分享快速排序相关内容,如果有时间一并把归并排序给大家讲解了
编者不易,欢迎读者们打赏
以上是关于基 础 算 法 普 及 之 堆 排 序 (中)的主要内容,如果未能解决你的问题,请参考以下文章