【算法基础】-- 选择排序篇 - 堆排序

堆排序是一种树形选择排序方法,其优点是:在排序过程中,将 L [ 1…n ]
视为一棵完全二叉树顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。

两种堆:

  1. 大顶堆(大根堆),在大顶堆中,最大元素存放在根节点中,且对其任一非根节点,它的值小于等于其双亲结点的值,即 L [ i ] >= L [ 2i ] 且 L [ i ] >= L [ 2i+1 ]

  2. 小顶堆(小根堆),在小顶堆中,最小元素存放在根节点中,且对其任一非根节点,它的值大于等于其双亲结点的值,即 L [ i ] <= L [ 2i ] 且 L [ i ] <= L [ 2i+1 ]

初始建堆是从最后一个拥有叶子节点的分支节点开始向下调整堆的,即从 L [ n / 2 ] 开始,直到 L [ 1 ] 结束 。

堆的存储结构:因为在访问孩子节点时需要用到随机访问,故采用顺序存储结构,至于为什么不用二叉链表来存储,因为堆的空间复杂度为 O ( 1 ) ,当给出待排序表的时候,不可能以二叉链表的形式来给出待排序队列,所以即使堆可以用二叉链表来存储,但是这就相当于空间复杂度为 O ( n ) ,这个二叉链表是独立于待排序表的 。

注意不能对堆采用层次遍历来得到排序结果,这是得不到的,最终结果还是得用到交换,逐个输出堆顶元素

堆的插入和删除

  1. 堆的删除和堆的输出是相同的道理,堆的输出相当于删除堆顶元素,两者只需要将待删除元素与最后一个元素 L [ len ] 进行交换,然后对 L [ 1…len-1 ] 进行向下调整即可(所谓向下调整,是指遍历时,是从 k 节点以 k *= 2 逐层向下遍历的)
  2. 堆的插入,是将待插入元素,插入到堆的最后一个元素,即作为 L [ len+1 ] 存在,然后向上调整堆(所谓向上调整,就是指遍历时,是从 k 节点以 L [ k/2 ] 逐层向上遍历的)
    向上调整代码(大顶堆)
    #include<bits/stdc++.h>
    using namespace std;
    #define MAX_SIZE 9
    typedef int ElemType;
    void AdjustDown(ElemType A[],int k,int len){
        int i;
        A[0]=A[k];//A[0]起到暂存数据元素的作用
        for(i=2*k;i<=len;i*=2){//起到递归的作用,会将以i为根节点的子树全部都调整完
            if(i<len&&A[i]<A[i+1]) i++;
            if(A[0]>=A[i]) break;
            else {
                A[k]=A[i];
                k=i;
            }
        }
        A[k]=A[0];
    }
    
    void BuildMaxHeap(ElemType A[],int len){//构建大顶堆
        for(int i=len/2;i>0;i--){
            AdjustDown(A,i,len);
        }
    }
    
    void AdjustUp(ElemType A[],int k){//向上调整代码
        A[0]=A[k];
        int i=k/2;
        while(i>0&&A[i]<A[0]){
            A[k]=A[i];
            k=i;
            i=k/2;
        }
        A[k]=A[0];
    }
    
    int main(){
        ElemType A[]={-1,9,45,78,32,17,65,53,87};
        BuildMaxHeap(A,MAX_SIZE-2);//对1~len-1进行堆排
        for(int i=1;i<MAX_SIZE;i++){
            cout<<A[i]<<' ';
        }
        cout<<endl;
        AdjustUp(A,MAX_SIZE-1);//将len插入后进行向上调整
        for(int i=1;i<MAX_SIZE;i++){
            cout<<A[i]<<' ';
        }
        return 0;
    }
    

算法实现(大顶堆):建立大顶堆,并且向下调整堆,最后交换,实现原地排序,空间复杂度O(1),下面算法中并未涉及向上调整,即堆的插入。

/*
    堆排序:
        时间复杂度->最好情况:O(nlogn)、平均情况:O(nlogn)、最坏情况:O(nlogn)
        空间复杂度->O(1)
        是否稳定->否
*/
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 9
typedef int ElemType;
void AdjustDown(ElemType A[],int k,int len){
    int i;
    A[0]=A[k];//A[0]起到暂存数据元素的作用
    for(i=2*k;i<=len;i*=2){//起到递归的作用,会将以i为根节点的子树全部都调整完
        if(i<len&&A[i]<A[i+1]) i++;
        if(A[0]>=A[i]) break;
        else {
            A[k]=A[i];
            k=i;
        }
    }
    A[k]=A[0];
}

void BuildMaxHeap(ElemType A[],int len){//构建大顶堆
    for(int i=len/2;i>0;i--){
        AdjustDown(A,i,len);
    }
}

void HeapSort(ElemType A[],int len){
    int i;
    BuildMaxHeap(A,len);
    for(i=len;i>1;i--){
        swap(A[i],A[1]);
        AdjustDown(A,1,i-1);
    }//这就是堆排,将待排序表建堆,然后逐个输出,所谓输出也只是,交换,因为最终目的是原地排序,输出还是在原表,只不过有序
    //这时,交换,这种输出方式,结合建堆的概念,即完全二叉树编号这个概念,即可在堆排后,待排序表为有序表
}

int main(){
    ElemType A[]={-1,87,45,78,32,17,65,53,9};
    HeapSort(A,MAX_SIZE-1);
    for(int i=1;i<MAX_SIZE;i++){
        cout<<A[i]<<' ';
    }
    return 0;
}
发布了83 篇原创文章 · 获赞 15 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_42127861/article/details/103101802
今日推荐