堆可以说是一种数据结构,但似乎又不完全是,它本质上是一棵完全二叉树
堆需要满足以下两个·条件:
①父节点的键值总是大于或等于(小于等于)任何一个子节点的值。
②每个结点的左右子树依旧是一个二叉堆。
当父节点的键值总是大于等于子节点的是最大堆;当父节点的键值总是小于等于任何一个子结点的是最小堆。
堆的存储结构
这样的储存结构有极为重要的特点:
① 结点 i 的父节点为 ( i - 1)/2;
② 结点 i 的左右孩子分别为 2*i + 1 , 2*i+2 .
就是由于数组存储完全二叉树,其父节点和子节点之间下标有特定的运算关系,这才在逻辑上保证了数的逻辑结构。
要用堆来完成排序我们第一步就是要完成堆的建立,最后再完成排序。
第一步我们先完成堆的结构调整函数,先看一看下面这个结构
此时我们需要完成以下步骤:
① 找到 i 结点的左右子结点中,若该子节点大于父节点,则调整完成,若该子结点小于父节点,则进入步骤二;
②将父节点取出暂时保存,然后用较小子结点替换父节点,再对该子节点的子节点重复上述步骤(注意都是与一开始取出的父节点12比较大小),直至到达边界n处;
③将一开始取出的父节点放到最后被移动的子节点处。
实现也十分的简单
void MinHeapFixdown(vector<int> & t, int i,int n)
{
int j, temp;
temp = t[i];
j = 2 * i + 1;
while (j < n-1)
{
if (j + 1 < n && t[j + 1] < t[j]) //在左右孩子中找最小的
j++;
if (t[j] >= temp)
break;
t[i] = t[j]; //把较小的子结点往上移动,替换它的父结点
i = j;
j = 2 * i + 1;
}
t[i] = temp;
}
完成后就 似乎 可以得到一个最小堆,那么问题出在哪了?这个函数可以说是一个只管儿子,然后通过儿子管孙子的,它最大的问题在于无法直接管理孙子,因此仅靠他是无法将一个完全乱序的数组变成一个“吊炸天”的堆的。
那么进入第二步
从最后父节点开始,倒序生成堆。
由于我们已经写好的函数只能管到它的儿子,换句话说它要求父节点的左右子树都是堆。
因此 我们只要从最后一个父节点开始,从后向前依次调用上述调整函数。
void MakeMinHeap(vector<int> & t)
{
int n = t.size();
for (int i = (n-1)/2; i >= 0; i--)
MinHeapFixdown(t,i,n);
}
好的
我们已经得到一个堆了
得到的序列是 3 4 16 12 5,并非我们需要的有序序列,最后的处理便是得到有序序列。
具体的做法是
①交换i 和 n ,然后 n –;
②对 i n 调用结构调整函数;
③重复上述步骤,直到 n = i;
这里比较好理解的就是 根节点一定是最小的,每次都把根结点取出来放到数组后面,再对前半部分调用结构调整,最后得到的便是一个 反序 的排序。
void MinheapsortTodescendarray(vector<int> & t)
{
int n = t.size();
for (int i = n - 1; i >= 1; i--)
{
Swap(t[i], t[0]);
MinHeapFixdown(t,0,i);
}
}
这样就得到一个反序的堆排序了,为了少做一次翻转,就反着来选择最大堆最小堆就好。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void Show(int x);
void MakeMinHeap(vector<int> & t);
void MinHeapFixdown(vector<int> & t, int i, int n);
void MinheapsortTodescendarray(vector<int> & t);
void Swap(int & x, int &y);
int main()
{
vector<int> temp = { 12,15,18,3,9,21 };
for_each(temp.begin(), temp.end(), Show);
cout << endl;
MakeMinHeap(temp);
MinheapsortTodescendarray(temp);
for_each(temp.rbegin(), temp.rend(), Show);
cin.get();
return 0;
}
void MinHeapFixdown(vector<int> & t, int i,int n)
{
int j, temp;
temp = t[i];
j = 2 * i + 1;
while (j < n-1)
{
if (j + 1 < n && t[j + 1] < t[j]) //在左右孩子中找最小的
j++;
if (t[j] >= temp)
break;
t[i] = t[j]; //把较小的子结点往上移动,替换它的父结点
i = j;
j = 2 * i + 1;
}
t[i] = temp;
}
void MakeMinHeap(vector<int> & t)
{
int n = t.size();
for (int i = (n-1)/2; i >= 0; i--)
MinHeapFixdown(t,i,n);
}
void MinheapsortTodescendarray(vector<int> & t)
{
int n = t.size();
for (int i = n - 1; i >= 1; i--)
{
Swap(t[i], t[0]);
MinHeapFixdown(t,0,i);
}
}
void Swap(int & x, int & y)
{
int temp = x;
x = y;
y = temp;
}
void Show(int x)
{
cout << x << " ";
}
至此 , 冒泡排序 、选择排序 、插入排序 、希尔排序 、堆排序 、 二叉排序树 、归并排序 、快速排序 都简单的介绍了一下, 最近实习生面试中,光快速排序都被要求写了三次,这些都了然于胸还是挺重要的 O(∩_∩)O哈!