22计算机408考研—数据结构—堆排序

191 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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;
    }
}

在这里插入图片描述