排序——堆排序

发布于:2024-05-04 ⋅ 阅读:(26) ⋅ 点赞:(0)

目录

方法

示例代码

复杂度分析


方法

  • 第一步:

我们将待排序数组形象成一个堆结构,并将其调整为最大堆

(堆结构:左孩子的下标是2i+1, 右孩子下标 2i+2)

(最大堆的特点:在这个堆结构里,任何一个父节点的值都大于其子节点的值)

  • 第二步:

将堆顶元素与待排序数组(假设待排序的数据数量为nums)最后一个元素进行交换,swap(&vec[0], &vec[nums-1]);

  • 第三步:

待排序的数据量减少一个, 即nums--; 将待排序数组重新调整成最大堆结构,重复第二步,循环n-1次,将所有数据排序完成。

初始化堆(将数组调整成最大堆)从最后一个父节点开始调整(即 i = n/2-1)

示例代码

#include<iostream>
#include<vector>
using namespace std;

// 将以 father 为根的子树调整为最大堆
void adjust(vector<int>& vec, int father, int end) {
    int child = father * 2 + 1; // 左孩子节点
    while (child <= end) {
        if (child + 1 <= end && vec[child] < vec[child + 1]) { // 如果右孩子存在且比左孩子大,则选择右孩子作为交换对象
            child = child + 1;
        }
        if (vec[father] < vec[child]) { // 如果父节点小于孩子节点,则交换父节点和孩子节点的值
            swap(vec[father], vec[child]);
        }
        else {
            break; // 如果父节点已经大于等于孩子节点,则说明已经满足最大堆性质,退出循环
        }
        father = child; // 更新父节点和孩子节点的位置
        child = father * 2 + 1;
    }
}

// 堆排序函数
void Headsort(vector<int>& vec) {
    // 建立最大堆
    for (int i = vec.size() / 2 - 1; i >= 0; i--) {
        adjust(vec, i, vec.size() - 1);
    }
    // 排序阶段
    for (int i = vec.size() - 1; i >= 1; i--) {
        swap(vec[0], vec[i]); // 将当前堆顶元素放到数组末尾
        adjust(vec, 0, i - 1); // 重新调整堆,只需要考虑前 i - 1 个元素
    }
}

int main() {
    // 测试数据
    vector<int>vec{ 5,7,3,9,4,8,2,9,1 };
    int n = vec.size();
    // 调用堆排序函数
    Headsort(vec);
    // 输出排序后的结果
    for (auto x : vec) {
        cout << x << " ";
    }

    return 0;
}

复杂度分析

平均时间复杂度:O(nlogn)

n个节点的完全二又树有 log 2(n+1)层创建最大堆过程中,两两元素相互比较共比较 n-1次,在交换过程中可能破坏某个最大堆结构,我们可以假设共调整k次可以恢复最大堆结构,故建堆过程时间复杂度为 O(n+k)=O(n)。 每个根节点最大下降(l0g2(n+1))-1层,每下降一层需比较两次,假设每个根节点都下降到最底层,则下降一个根节点的时间复杂度为 O(2((l0g2(n+1)) n-1)),共有 n-1 个根节点需要下降,则下降过程的时间复杂度为

O((n-1)2(log2(n+1)-1))=0(n*logn)。

综上所述,堆排序时间复杂度为

O(n)+O(nlogn)=0(nlogn)。

空间复杂度:0(1) 在原数组上操作,即使用了常数级空间O(1)


网站公告

今日签到

点亮在社区的每一天
去签到