持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
2022计算机考研408—数据结构—排序 手把手教学考研大纲范围内的排序 22考研大纲数据结构要求的是C/C++,笔者以前使用的都是Java,对于C++还很欠缺, 如有什么建议或者不足欢迎大佬评论区或者私信指出
Talk is cheap. Show me the code. 理论到处都有,代码加例题自己练习才能真的学会
堆排序
思路: 首先要强调一下,堆排序并不是要构成一个堆,它本身还是个数组,只不过是我们在排序的时候把他看成一个堆,他实际上还是一个数组 我们这里用到的堆是大根堆和小根堆,他们都是完全二叉树 完全二叉树就是他们结点位置和满二叉树的位置是一样的
红色角标为真实数组的下标
PS:图片来源
这是大根堆和小根堆
完全二叉树和满二叉树基本相同 每一层的数量都是上一层的二倍 某一层最左边结点下标*2就是最右边结点下标
完全二叉树中子结点和父结点之间还有一些显著的关系
一个数的下标为x
他的父节点下标为(x - 1)/ 2
这里的除2是计算机除2,不保存余数,整除不成为小数
他的左子结点为 2 * x + 1
他的右子结点为 2 * x + 2
如果还不明白,可以自己带个结点试一下(看堆分析容易理解) (左子结点-1)/2就是父节点 (右子结点 - 1)/2也是父节点,它除2的时候会把余数直接省去
大根堆就是 父节点 > 左子结点 并且 父节点 > 右子结点
小根堆就是 父节点 < 左子结点 并且 父节点 < 右子结点
构建大根堆只是思想构建,不是实际构建,实际还是数组
先把数组构建成大根堆,大根堆不一定是有序的,但一定是符合大根堆的规律,父结点一定比子结点大,全部构建好(构建大根堆,一定要从下向上构建,如果无序直接从上向下构建,有的会不符合大根堆定义)
默认叶子结点为排好序的堆(叶子结点没有子结点,只有一层,符合大跟堆得特征)
先添加非叶子结点,逐步向上添加
如果从上到下结点添加,叶子结点为有序堆,上到下添加会把有序堆打散,无法完成堆排序
完全二叉树的特点,n个结点,最后一个非叶子结点二叉树为(n/2)下标为(n/2-1),逐步向上添加
构建好大根堆后,转换成小根堆
转换小根堆的步骤,先把大根堆最大的(也就是堆顶)与最后面的结点换位置,这样能保证最大的在最后面
,也就是说,数组第一位与最后一位交换
交换完,小的就到了最上面,把刚交换完最大的固定不动,其他的重新构建大根堆
此时最大的在最后面,其他的仍然是大根堆
第二次的时候,把当前大根堆最大的值(当前堆顶)与倒数第二位交换(倒数第一位是刚才的堆顶,最大的,已经固定了,保持不变),最小的又到了堆顶,继续重建大根堆 如此往复,大根堆就变成了小根堆,此时的小根堆就是有序的
#include <iostream>
#include <vector>
using namespace std;
void heapSort(vector<int> &num);
void heapBuild(vector<int> &num, int fatherIndex, int len);
int main() {
int n; //n为将要输入的数组长度
cin >> n; //输入n cin方法需要上面使用std
vector<int> num; //定义vector 记得上面导入vector
int temp; //temp为输入vector时的中间变量
for (int i = 0; i < n; i++) {
cin >> temp; //输入
num.push_back(temp);
}
heapSort(num); //调用自定义的排序方法
cout << "\n\n排序后" << endl;
for (int i = 0; i < num.size(); i++) {
cout << num[i] << " "; //输出
}
return 0;
}
void heapSort(vector<int> &num) {
//构建大根堆
for (int i = num.size() / 2 - 1; i >= 0; i--) { // 倒数第二排开始, 创建大顶堆,必须从下往上比较
heapBuild(num, i, num.size()); // 否则有的不符合大顶堆定义
}
cout << "构建完大根堆后的数组\n";
for (int i = 0; i < num.size(); i++) {
cout << num[i] << " ";
}
int len = num.size();
//也算一种选择排序,因为每次把大根放到最后就少排序一个
while (len > 1) {
//每次都把大根与最后面的值交换,
int temp = num[0];
num[0] = num[len - 1];
num[len - 1] = temp;
len--; //最后一个元素也就是刚才的大根已经确定好了,可以少排序一个元素
heapBuild(num, 0, len); //除去确定好的大根,把剩下的元素重新构建
cout << "\n下标:"<< len << " 值:" << num[len] <<" 确定好位置了\n";
for (int i = 0; i < num.size(); i++) {
cout << num[i] << " ";
}
}
}
void heapBuild(vector<int> &num, int fatherIndex, int len) {
//堆(完全二叉树)左子结点和右子结点与父结点的关系
int left = fatherIndex * 2 + 1;
int right = fatherIndex * 2 + 2;
while (left < len) {
int largestIndex; //largestIndex保存的是当前子结点与父结点中最大的索引
if (num[left] < num[right] && right < len) {
largestIndex = right;
} else {
largestIndex = left;
}
if (num[largestIndex] < num[fatherIndex]) { //如果父节点大于子结点,当前符合大根堆,退出即可
largestIndex = fatherIndex;
break;
}
//不符合大根堆就交换
int temp = num[largestIndex];
num[largestIndex] = num[fatherIndex];
num[fatherIndex] = temp;
//接着去下面构建大根堆
fatherIndex = largestIndex;
left = fatherIndex * 2 + 1;
right = fatherIndex * 2 + 2;
}
}