堆排序超详细讲解

堆排序

完全二叉树

​ 新节点的生成按照从左到右,从上到下的规则生成的二叉树叫做完全二叉树。如下图所示
在这里插入图片描述

​ 这样的树有很好的特征可以让我们利用,我们从上到下,从左到右依次给节点编号后,有如下规律:

  • i 号节点的左孩子 = 2 * i + 1 ,i 号节点的右孩子 = 2 * i + 2
  • i 号节点的父亲 = (i-1)/2

​ 知道了这些特点后,我们说一下堆

​ 堆又可以分为大根堆和小根堆,这里我们以大跟堆为例

在这里插入图片描述

​ 如上图所示,每一个父节点都比自己的两个孩子节点要大,这样形成的完全二叉树被称为一个大跟堆。

如何生成一个堆

​ 假设我们的所有节点上的数都是随机生成的。我们要维护它成为一个大跟堆。如下图所示。

  • 维护第一个非叶子节点,使其成为一个大跟堆,如图既交换1和4。

  • 再依次维护比第一个非叶子节点更小的节点。既依次维护9 3 2

  • 9这个节点不需要维护,所以保持原状即可。

  • 3这个节点需要将3和10进行互换。互换过后,有可能会破坏现在3这个位置上大跟堆的性质,所以需要对3这个位置再做一次维护,这个例子中3这个位置上没有子节点,所以不需要维护。

  • 接着维护2这个节点,需要做的操作是将2和10互换位置,这样保证了当前10这个位置大跟堆的性质,但是却破坏掉了当前2这个位置大根堆的性质,所以需要对当前2这个节点重新做一次维护。

  • 需要做的操作是将2和4互换位置,这样重新维护了当前4这个位置的性质,同时,也可能会破坏当前2这个位置的大根堆的性质,但庆幸的是这时没有破坏掉,所以这时就将所有的节点都维护好了,这样我们就得到了一个大根堆。

​ 代码实现的话也很容易,我们使用一个数组来当大根堆,这样就很容易的表示出每个节点的父亲节点和儿子节点。

​ 接着从第一个非叶子节点(最后一个节点的父节点)开始做维护即可。

​ 如果遇到了交换的情况,就再维护与根节点交换的节点即可。

堆排序:

​ 如果是大根堆的话,我们只需要每次将数组中第一个元素与最后一个元素做一个交换后,砍掉最后一个元素,再对整棵树的根节点做一次维护即可。每次将砍掉的最后一个元素取出,就得到了依次递减的排序,同理,如果是小根堆得到的就是依次递增的排序

C代码:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//交换arr数组中下标为i和下标为j的值
void swap(int arr[], int i, int j) {
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}
//对tree中的i号节点做heapify,tree中共有n个元素
void heapify(int tree[], int n,int i) {
	if (i > n) {
		return;
	}
	int c1 = 2 * i + 1;
	int c2 = 2 * i + 2;
	int max = i;
	if (c1<n && tree[c1]>tree[max]) {
		max = c1;
	}
	if (c2<n && tree[c2]>tree[max]) {
		max = c2;
	}
	if (i != max) {
		swap(tree, max, i);
		heapify(tree, n, max);
	}
}
int main() {
	srand((unsigned)time(NULL));
	int a = rand();
	int n = 1000;
	int tree[1000];
	for (int i = 0; i < n; i++) {
		tree[i] = rand()%1000+1;
	}
	for (int i = (n - 1) / 2; i >= 0; i--) {
		heapify(tree, n, i);
	}
	int result[1000];
	int d = n;
	for (int i = 0; i < n; i++) {
		result[i] = tree[0];
		swap(tree, 0, d-1);
		d--;
		heapify(tree, d, 0);
	}
	for (int i = 0; i < n; i++) {
		printf("%d\n", result[i]);
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值