堆排序
思路
- 什么是堆
- ①一棵完全二叉树,所以能用数组表示
- ②堆顶为最大或最小(大根堆,小根堆),除了堆顶,堆里的元素没有用
- ③左孩子右孩子都是堆
- 堆排序分两步
- ①建堆
- 以原数组为空间建堆(代码选用着这种)
- 新建空间建堆
- 两种方法建立起来的堆不一样
- ②调整堆
- 因为一个堆有用的就是堆顶元素,当我们取出堆顶元素,需要对堆进行维护,让堆顶保持最大或最小
- ①建堆时使用,以原数组为空间,我们要从最后一个非叶子节点开始,确定这节点为根节的子树成为堆
- 原数组必定为满足条件①,我们要让它满足其余两个条件
- 从最后一个非叶子节点开始的原因:类似于动态规划的思想,先完成小问题,利用小问题的解自底向上解决大问题
- 同时我们将树调整为堆是这个小问题过程是自顶向下的,父节点在比较中下移
- ②排序时使用,把堆顶元素和数组末尾元素交换,除去原堆顶元素进行堆的调整
- ①建堆
代码思路
- 建堆,从最后一个非叶子节点开始,以这颗结点为根节点进行堆调整
- 堆调整,调整以root为结点的子树为堆
- ①求出左右孩子left、right,判断root要和哪个孩子交换或者是否需要交换,取now
- ②交换后的孩子更新为根节点root,继续①
- ③循环直到左孩子结点超过end
- 堆排序
- ①堆顶元素与末尾元素交换,对除去末尾的整棵树进行堆调整
注意
- 时间复杂度O(nlogn),建初堆O(n),排序O(nlogn),n个节点,logn层
- 不稳定排序,出现在堆调整时
代码
#include <iostream>
#include <vector>
using namespace std;
//根据root结点,向下调整
void heapAdjust(vector<int>& v, int root, int end) {
//左孩子
int left = (root << 1) + 1;
//右孩子
int right = (root + 1) << 1;
//如果左孩子不超过end
//建堆时,end为数组长度
//堆排时,end为数组未确定元素的末尾
while (left < end) {
//now为需要交换的结点
int now = left;
//如果有右孩子,且右孩子比左孩子大(大根堆)
if (right < end && v[right] > v[left]) now = right;
//判断是否需要交换
if (v[now] < v[root]) break;
swap(v[now], v[root]);
//交换完成后,更新某个孩子为根,继续向下判断
root = now;
//重新确定左孩子右孩子
left = (root << 1) + 1;
right = (root + 1) << 1;
}
}
void createHeap(vector<int>& v) {
//从最后一个非叶子节点开始
int i;
//数组有0结点的话需要这样判断,确定最后一个非叶子结点索引
i = v.size() / 2 - 1;
//以非叶子结点为根节点,调整整棵树为堆
for (; i >= 0; i--) {
heapAdjust(v, i, v.size());
}
}
void heapSort(vector<int>& v) {
//在原数组上建堆
createHeap(v);
cout << "在原数组基础上建堆:";
for (auto a : v) cout << a << " ";
cout << endl;
//将堆顶元素放在最后,调整堆(原堆顶元素调整的位置就是其最后位置,所以调整堆时不需要考虑原堆顶元素)
int i = v.size() - 1;
while (i > 0) {
swap(v[0], v[i]);
heapAdjust(v, 0, i);
i--;
}
}
//从无到有建堆
void buildHeap(vector<int> v,vector<int>& s) {
for (int i = 0; i < v.size(); i++) {
//将新结点先放到堆尾
s.push_back(v[i]);
//父节点
int now;
//因为要不断循环,所以用index暂存i
int index=i;
//求出父节点的索引
if (index % 2) now = index / 2;
else now = index /2 - 1;
while (now >= 0) {
//构建大根堆,如果不需要交换,break
if (s[index] <= s[now]) break;
//交换
swap(s[index], s[now]);
//更新下次要交换的两个结点索引
index = now;
if (index % 2) now = index / 2;
else now = index / 2 - 1;
}
}
}
int main() {
vector<int> v = { 49,38,65,97,76,13,27,49 };
vector<int> s;
buildHeap(v, s);
cout << "从无到有建堆:";
for (auto a : s) cout << a << " ";
cout << endl;
heapSort(v);
cout << "排序后:";
for (auto a : v) cout<< a << " ";
cout << endl;
return 0;
}
结果
由结果显示可得,堆只有堆顶元素是有用的,建堆方法不同,堆中元素顺序可能不同