堆排序

一些概念

普通队列:先进先出,后进先出(顺序已经确定)
优先队列:出队顺序与入队顺序无关,和优先级相关(动态确定顺序)

优先队列在处理动态问题上很有优势,同时处理静态问题也是比较好的,比如在100000000个元素中选出前100名?也就是,在N个元素中选出前M个元素。不同方法的时间复杂度分别是排序: NlogN ,优先队列: NlogM

动态

有新的元素会加入数据结构,同时又有元素会离开队列,也就是说入队和出队是有限队列的两个基本特征。

出队:取出优先级最高的元素。

堆的存储

这里写图片描述

1)节点值总是不大于父亲节点的值;
2)完全二叉树。

由于堆是一颗完全二叉树,所以可以用数组来存储一颗二叉树。

这里写图片描述

ShiftUp

入队时,从末端加入元素,然后要调整元素顺序,使其任然维持最大堆的定义,关键操作是每次和父亲相比较,逐层向上升直到满足堆的定义。

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

ShiftDown

出队只能取出根节点的元素,出队后为了保证完全二叉树,可以将最后一个元素与根节点位置交换,然后对根节点元素进行ShiftDown操作。

void shiftDown(int 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;
        swap(data[k], data[j]);
        k = j;
    }
}

堆的数据结构

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; // 在此轮循环中,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;
            swap(data[k], data[j]);
            k = j;
        }
    }

public:

    MaxHeap(int capacity){
        data = new Item[capacity + 1];
        count = 0;
        this->capacity = capacity;
    }

    ~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];
    }

};

Heapify

MaxHeap(Item arr[], int n){
    data = new Item[n + 1];
    capacity = n;

    for (int i = 0; i < n; i++)
        data[i + 1] = arr[i];
    count = n;

    for (int i = count / 2; i >= 1; i--)
        shiftDown(i);
}

堆排序

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();

}


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();

}

使用Heapify构建堆,时间复杂度是 O(n) ,而使用入队操作构建一个堆时间复杂度是 O(nlgn)

堆排序优化

原地堆排序,空间复杂度是 O(1) ,值得注意的是由于是在原地,因此堆的索引是从0就开始的,也就是要重新换算下父亲及叶子节点之间的关系。

这里写图片描述

template<typename T>
void __shiftDown(T arr[], int n, int k){

    while( 2*k+1 < n ){
        int j = 2*k+1;
        if( j+1 < n && arr[j+1] > arr[j] )
            j += 1;

        if( arr[k] >= arr[j] )break;

        swap( arr[k] , arr[j] );
        k = j;
    }
}

template<typename T>
void __shiftDown2(T arr[], int n, int k){

    T e = arr[k];
    while( 2*k+1 < n ){
        int j = 2*k+1;
        if( j+1 < n && arr[j+1] > arr[j] )
            j += 1;

        if( e >= arr[j] ) break;


        arr[k] = arr[j];
        k = j;
    }

    arr[k] = e;
}

template<typename T>
void heapSort(T arr[], int n){
    // 首先进行Heapify操作
    for( int i = (n-1)/2 ; i >= 0 ; i -- )
        __shiftDown2(arr, n, i);
    // 然后每次把堆顶元素与最后一个叶子交换,之后堆堆顶元素进行shiftDown操作
    for( int i = n-1; i > 0 ; i-- ){
        swap( arr[0] , arr[i] );
        __shiftDown2(arr, i, 0);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值