排序算法3=堆排序+二叉堆
一、什么是二叉堆
堆是一种存储数据的结构,形状类似于金字塔形,所以形象的称之为堆结构。
二叉堆:是指一种将二叉树和堆结合起来的数据结构,这种数据结构有两个主要的特点,其一是满足完全二叉树的性质,顺序存储,中间不存在空余位置,其二是任意一个结点的父节点大于(或小于)子结点。
其中堆的存储方式是采用数组进行存储,所以堆通常可以被看做是一个完全二叉树的数组对象。
其中根结点最大的称为最大堆,根结点最小的称为最小堆。
二、怎么用代码定义一个二叉堆
#include<iostream>
#include<string>
#include<ctime>
using namespace std;
template<typename T>
class maxHeap {
private:
int size;
T* arr;
void shiftUp(int size) {
while (size >= 2) {
if (arr[size] > arr[size / 2]) {
swap(arr[size], arr[size / 2]);
size /= 2;
}
else
break;
}
}
void shiftDown(int k) {
while (2 * k <= size) {
int j = 2 * k;;
if (j+ 1 <= size && arr[j] < arr[j+1])
j += 1;
if (arr[k] >= arr[j])
break;
swap(arr[k], arr[j]);
k = j;
}
}
public:
maxHeap(int n) {
arr = new T[n+1];
size = 0;
}
~maxHeap() {
delete[] arr;
}
int Size() {
return size;
}
bool isEmpty() {
return size == 0;
}
void insert(T k) {
arr[size + 1] = k;
size++;
shiftUp(size);
}
void print() {
for (int i = 1; i <=size; i++)
cout << arr[i]<<" ";
cout << endl;
}
T extractTop() {
T temp = arr[1];
arr[1] = arr[size];
size--;
shiftDown(1);
return temp;
}
};
int main() {
int n = 10;
maxHeap<int> maxheap = maxHeap<int>(n);
srand((unsigned int)time(NULL));
for (int i = 0; i < n; i++)
maxheap.insert(rand() % 100 + 1);
cout << "原排列:" << endl;
maxheap.print();
cout << "从大到小排序:" << endl;
for (int i = 0; i < n; i++) {
cout << maxheap.extractTop() << " ";
}
cout << endl;
system("pause");
return 0;
}
接下来,我简单讲解一下代码组成。
第一部分是堆的基本定义,用模板类的形式定义了一个最大堆,其中包括私有成员arr数组,size为成员个数,注意的是本次代码用数组存储堆时是从序号1开始存储,定义公有成员 有参构造函数,传入参数开辟数组内存空间大小同时size初始化为0,析构函数删除堆,同时还定义了两个成员函数,返回堆成员的个数和判断是否为空。然后在主函数内创建对象,传入内存空间大小。
第二部分,进行堆的插入,这里用图来形象说明一下。图中已经有了10个元素,从上到下,从左至右序号分别是1,2,3,4,5,6,7,8,9,10,假设现在要插入一个数序号为11,值为80,初始时80位于序号为5的右子树,然后开始逐级往上比,循环条件是序列号大于等于2,因为根结点的序号为1,等于2时进行的就是最后一次比较。序号11值80比序号11/2值45大,则交换这两个元素在数组中的位置,同时把值80的序号变成11/2=5,然后序号5值80比序号5/2=2值72大,则交换这两个元素在数组中的位置,同时把值80的序号变成5/2=2,然后序号2值80比序号2/2=1值93小跳出循环,循环结束,最大堆完成。
第三部分,进行堆排序,由于最大堆的特点根结点时最大的值,所以只要每次取出根结点的值,然后把剩下的堆再次变成最大堆的形式,则就可以逐次取出最大值,输出也就是从大到小的一个排序序列了。
这里以上面最后一个排好的最大堆为例,令temp=93,然后把45赋值到序列1的位置上,同时size--,进入重组最大堆的函数中,传入序号1,循环条件是该节点存在子节点即2i<=size的,然后定义小标j=2i;判断是否存在j+1,即是否存在右子树且右子树是否大于左子树,如果满足则j=j+1;然后将该小标对应的元素值与父节点进行比较,如果子结点大于父节点则交换这两个数的位置,同时i=j进行赋值进行下一层的比较,如果子节点不大于父节点则跳出循环。此时循环结束后又恢复成了最大堆的形式。


