堆排序

思路

  1. 什么是堆
    • ①一棵完全二叉树,所以能用数组表示
    • ②堆顶为最大或最小(大根堆,小根堆),除了堆顶,堆里的元素没有用
    • ③左孩子右孩子都是堆
  2. 堆排序分两步
    • ①建堆
      • 以原数组为空间建堆(代码选用着这种)
      • 新建空间建堆
      • 两种方法建立起来的堆不一样
    • ②调整堆
      • 因为一个堆有用的就是堆顶元素,当我们取出堆顶元素,需要对堆进行维护,让堆顶保持最大或最小
      • ①建堆时使用,以原数组为空间,我们要从最后一个非叶子节点开始,确定这节点为根节的子树成为堆
        • 原数组必定为满足条件①,我们要让它满足其余两个条件
        • 从最后一个非叶子节点开始的原因:类似于动态规划的思想,先完成小问题,利用小问题的解自底向上解决大问题
        • 同时我们将树调整为堆是这个小问题过程是自顶向下的,父节点在比较中下移
      • ②排序时使用,把堆顶元素和数组末尾元素交换,除去原堆顶元素进行堆的调整

代码思路

  1. 建堆,从最后一个非叶子节点开始,以这颗结点为根节点进行堆调整
  2. 堆调整,调整以root为结点的子树为堆
    • ①求出左右孩子left、right,判断root要和哪个孩子交换或者是否需要交换,取now
    • ②交换后的孩子更新为根节点root,继续①
    • ③循环直到左孩子结点超过end
  3. 堆排序
    • ①堆顶元素与末尾元素交换,对除去末尾的整棵树进行堆调整

注意

  1. 时间复杂度O(nlogn),建初堆O(n),排序O(nlogn),n个节点,logn层
  2. 不稳定排序,出现在堆调整时

代码

#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;
}

结果

由结果显示可得,堆只有堆顶元素是有用的,建堆方法不同,堆中元素顺序可能不同

posted @ 2020-10-13 17:01  肥斯大只仔  阅读(86)  评论(0编辑  收藏  举报