堆和堆排序(Heap Sort)以及堆排序的优化 —— C++

时间复杂度:O(nlogn)
优先队列:出队顺序和入队顺序无关,和优先级相关
比如在医院看病,急诊病人优先,再比如在操作系统中执行任务,操作系统将动态的选择每一次优先级最高的任务进行执行
为什么选择优先队列:例如在1000000个元素中选出前一百名,在 n 个元素中选出前 m 个元素,排序算法的算法复杂度是O(n
log n)级别的,而使用优先队列,可以将其算法复杂度降低到O(n*log m)级别
优先队列的主要操作:入队,出队(取出优先级最高的元素)
优先队列在堆中的实现
在这里插入图片描述
最大堆:堆中的某个节点的值总是不大于其父节点的值;堆总是一棵完全二叉树
最小堆:堆中的某个节点的值总是不小于其父节点的值;堆总是一棵完全二叉树
(并不意味着层数越高数值越大)
最大堆原理:将一个一个元素插入堆的同时,进行 shiftUp() 函数,使堆满足最大堆性质
最大堆代码

template<typename Item>
class MaxHeap{
private:
    Item *data;//存储堆内容的数组
    int count;//元素的个数
    int capacity;//堆的容量
    
    void shiftUp(int k){
        //与父节点相比,若父节点小于其优先性,交换二者位置 ,直到满足最大堆的定义
        //k的值最小为2
        while( k > 1 && data[k/2] < data[k] ){
            swap( data[k/2], data[k] );
            k /= 2;
        }
    }
    
    void shiftDown(int k){
        //当前节点 k 有左孩子则一定有孩子  
        while( 2*k <= count ){
            int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
            //有右孩子
            if( j+1 <= count && data[j+1] > data[j] )
                j ++;
            // 使得data[j] 是 data[2*k]和data[2*k+1]中的最大值
           
            if( data[k] >= data[j] ) 
                break;
            //data[k] < data[j],交换位置
            swap( data[k] , data[j] );
            k = j;
        }
    }
    
public:
    MaxHeap(int capacity){//堆的容量由用户指定
        data = new Item[capacity+1];//数组存储堆的内容是从索引 1 开始的
        count = 0;
         this->capacity = capacity;
    }
    ~MaxHeap(){
        delete[] data;
    }
    int size(){
        return count;
    }
    bool isEmpty(){
        return count == 0;
    }
    //新添元素
    //将元素添加到数组末尾,count++ ,判断是否保持堆得性质
    //不满足,与其父节点交换位置,接着继续观察是否满足堆的性质,循环此步骤,直到满足
    void insert(Item item){
        //检测堆是否还有空间,没有空间,程序结束
        assert( count + 1 <= capacity );
        data[count+1] = item;
        count ++;
        //调整新添元素,使二叉树仍然保持堆的性质
        shiftUp(count);
    }
    //取出堆顶元素
    //将数组末尾元素交换到堆首,count--, 判断是否保持堆得性质
    //不满足,跟优先性大的子孩子交换位置,循环此步骤,直到满足
    Item extractMax(){
        assert( count > 0 );
        Item ret = data[1];

        swap( data[1] , data[count] );
        count --;
        shiftDown(1);

        return ret;
    }
};

template<typename T>
void heapSort1(T arr[], int n){
    //实例化一个最大堆
    MaxHeap<T> maxheap = MaxHeap<T>(n);
    for( int i = 0 ; i < n ; i ++ )
        maxheap.insert(arr[i]);
    //反向遍历,使之从小到大排序
    for( int i = n-1 ; i >= 0 ; i-- )
        arr[i] = maxheap.extractMax();
}

优化1:(建堆方法有差别)将数组赋值给堆,在堆中从后向前的考察每一个不是叶子节点的节点(如图示,考察顺序为 5 4 3 2 1),使以它为根所构成的子树满足最大堆的性质,即在这个位置上执行 shiftDown() 函数
实现优化原因:将 n 个元素逐个插入到一个空堆中,算法复杂度是O(n*logn)级别
heapify 的过程,算法复杂度为O(n)级别
补充:1.完全二叉树所有的叶子节点(没有孩子的节点)本身就是一个最大堆
2.对于一棵完全二叉树来说,第一棵非叶子节点的索引是这棵完全二叉树的元素个数/2得到的值
在这里插入图片描述
代码实现

#include <algorithm>
#include <cassert>
using namespace std;
template<typename Item>
class MaxHeap{
private:
    Item *data;
    int count;
    int capacity;

    void shiftUp(int k){
        while( k > 1 && data[k/2] < data[k] ){
            swap( data[k/2], data[k] );
            k /= 2;
        }
    }

    void shiftDown(int k){
        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[j+1] > data[j] ) j ++;
            if( data[k] >= data[j] ) break;
            swap( data[k] , data[j] );
            k = j;
        }
    }

public:
    MaxHeap(int capacity){
        data = new Item[capacity+1];
        count = 0;
        this->capacity = capacity;
    }
    //传入 n 个元素的数组
    MaxHeap(Item arr[], int n){
        data = new Item[n+1];
        capacity = n;
        //data[]数组从 1 开始
        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;
        //从第一个不是叶子节点的结点开始进行shiftDown()操作
        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    ~MaxHeap(){
        delete[] data;
    }

    int size(){
        return count;
    }

    bool isEmpty(){
        return count == 0;
    }

    void insert(Item item){
        assert( count + 1 <= capacity );
        data[count+1] = item;
        shiftUp(count+1);
        count ++;
    }

    Item extractMax(){
        assert( count > 0 );
        Item ret = data[1];
        swap( data[1] , data[count] );
        count --;
        shiftDown(1);
        return ret;
    }

    Item getMax(){
        assert( count > 0 );
        return data[1];
    }
};

template<typename T>
void heapSort2(T arr[], int n){
    //构造堆
    MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
    for( int i = n-1 ; i >= 0 ; i-- )
        arr[i] = maxheap.extractMax();
}

优化2:原地堆排序:一个数组,我们可以通过 heapify 过程使之变成一个最大堆,在这个最大堆中,第一个元素 v 的位置就是整个数组中最大值的位置,我们在具体排序过程中,v 显然应该排在数组末尾的位置,则交换第一个元素 v 和最后一个元素 w ,此时 w 所在的数组显然不再满足最大堆的性质,则将 w 所在数组执行 shiftDown2()函数,此时第一个元素又成为最大值,再次执行交换、shiftDown2() 过程,直到完成排序
在这里插入图片描述
注意:数组下标从零开始,顾改变堆得索引值,但节点的左右节点以及父节点仍有规律可循
在这里插入图片描述
代码

template<typename T>
//实现在arr[n]所定义的堆中 k 这个位置的元素找到它合适的位置
void shiftDown2(T arr[], int n, int k){
    T e = arr[k];
    //左孩子变成2*k+1
    while( 2*k+1 < n ){
        int j = 2*k+1;
        //拥有右孩子
        if( j+1 < n && arr[j+1] > arr[j] )
            j ++;
        //此时 j 为左右孩子中较大孩子的位置 
        if( e >= arr[j] ) break;
        
        arr[k] = arr[j];
        k = j;
    }
    arr[k] = e;
}

template<typename T>
void heapSort(T arr[], int n){
    //进行一次heapify 的过程,第一个非叶子节点下标 i = (n-1)/2
    for( int i = (n-1)/2 ; i >= 0 ; i -- )
        shiftDown2(arr, n, i); 
    //把当前最大的元素放到它合适的位置上
    //对他们第 0 位上的元素执行 shiftshiftDown2()
    //注意,每次循环数组 w 元素个数 -1,即为 i 的个数,
    for( int i = n-1; i > 0 ; i-- ){
        swap( arr[0] , arr[i] );
        shiftshiftDown2(arr, i, 0);
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值