简单探

本文选自博客,可以前往博客以获得更好的阅读体验。

堆排序

堆排序是排序算法中的一种,算法时间复杂度是 O ( n l o g ( n ) ) O(n log(n)) O(nlog(n))

视频地址

堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。
堆的例子

性质

  • 如果一个节点的位置为 k k k ,则它的父节点的位置为 k 2 \cfrac{k}{2} 2k ,而它的两个子节点的位置则分别为 2 × k 2 \times k 2×k 2 × k + 1 2 \times k + 1 2×k+1
  • 每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆
  • 每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆

堆排序

基本思想

  1. 将待排序序列构造成一个堆。
  2. 进行堆调整使其变为大根堆,整个序列的最大值就是堆顶的根节点。
  3. 将其与末尾元素进行交换,此时末尾就为最大值。
  4. 然后将剩余n-1个元素重新构造成一个堆,这样会得到n-1个元素的次小值。如此反复执行,便能得到一个有序序列了。(剪枝)

代码实现

C代码

#include <stdio.h>
#include <stdlib.h>

void swap(int a[],int i,int j){
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

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(max != i){
        swap(tree,max,i);  //将最大值移动至父节点
        heapify(tree,n,max); //递归维护下面的节点
    }
}

void build_heap(int tree[],int n){   //构造堆
    int last_node = n - 1;
    int parent = (last_node - 1) / 2; //求出最后一个父节点
    for(int i = parent;i >= 0;i--){ //向上维护节点
        heapify(tree, n , i);
    }
}

void heap_sort(int tree[],int n){
    build_heap(tree,n);f
    for(int i = n - 1;i >= 0;i--){  //每次取出根节点后,堆减少一个节点
        swap(tree,i,0);
        heapify(tree,i,0);
    }
}

int main(){
    int tree[] = {2, 5, 3, 1, 10, 4};
    int n = 6;
    heap_sort(tree,n);
    for(int i = 0;i < n;i++){
        printf("%d ",tree[i]);
    }

    return 0;
}

思路

一、heapify:堆调整

  1. 针对节点 i,将其两个子节点找出来,此三个节点构成一个最小单位的完全二叉树(越界的忽略)
  2. 找到这个最小单位的完全二叉树 的最大值,并将其交换至父节点的位置
  3. 递归调用,维护交换后 子节点与其子节点被破坏的堆关系,递归出口为叶节点

堆调整是自顶向下进行调整,无需考虑下标i小于0的情况

二、 build_heap:构造堆

  1. 用一维数组表示:堆

    a [ i ] a[ i ] a[i] 的父节点为 a [ i − 1 2 ] a[\cfrac{i-1}{2}] a[2i1],两个子节点为 a [ 2 × i + 1 ] a[2 \times i + 1] a[2×i+1] a [ 2 × i + 2 ] a[2 \times i + 2] a[2×i+2]

  2. 先找到最后一个父节点,再从最后一个父节点开始向上调用 heapify维护节点。

三、heap_sort:利用堆进行堆排序

尽管我们可以构造出大根堆,但是我们也发现,大根堆并没有排好序

大根堆是根节点的值最大,这时我们可以将根节点移走,即剪去根节点,并将剩余的再次构造一个新堆,循环往复,就进行了排序操作。

那么,拿走根节点后,根节点的数值又应该由谁取代?

结合刚刚的分析,我们知道,该节点需满足:移动到根节点对堆的破坏最小。那么,结果便很明了了,最后一个节点是最好的选择。

我们可以用最后一个节点替换根节点,此时根节点和第二层的两个节点构成一个最小单位的最小二叉树,此时就可以直接调用 heapify函数,形成一个新的堆。但是我们并没有必要使用 build_heap函数,因为此时最大值就在第二层,根节点会因为递归维护被替换到最后一层,其他节点也会因为递归维护而得到调整。

最后,经过堆排序,数值便从小到大排序,同时也构成了小根堆。

后记

C语言需要我们自行手写出代码。而实际上,许多编程语言并非需要我们手写堆排序。

在这里,我们可以引入一个名为优先队列的数据结构。

优先队列

优先队列可以完成以下操作:

  • 插入一个数值
  • 取出最小的数值(获得数值,并且删除)

经过刚刚堆排序的知识,我们很容易就会发现优先队列与堆排序的相似性。可以说,优先队列进行了大根堆的堆排序,在输出时按从大到小的顺序输出。

在C++中,STL里的priority_queue就是其中之一。

#include <queue>
#include <cstdio>
using namespace std;
priority_queue<int> p;

int main{
    //插入元素
    p.push(3);
    p.push(5);
    p.push(1);

    //不断循环直至空为止
    while(!p.empty()){
        //获取并删除最大值
        printf("%d\n",p.top());
        p.pop();
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Sky_River_Z/article/details/113552571