堆排序

374 阅读3分钟

堆排序

堆的定义

堆实际也是一个二叉树,也是一个完全二叉树,当不是满二叉树时,从第一个叶子结点开始将元素放在已存在的叶子结点下面。

特点:

  • 完全二叉树
  • 堆中的元素小于等于父亲节点

堆排序的方案

以最大堆为例

  • 先将数组整理成一个最大堆,依次从堆中移除最大元素,移除后放在一个数组中存储起来,每移除一个元素,则将堆重新整理成一个最大堆
  • 先将数组整理成一个最大堆,将数组中最大值的元素和数组末尾的元素交换,记录未排序的数组边界,重新将为排序的数组整理成最大堆,再交换数组中的第一个元素和最后一个元素,重复直到数组元素都排序为止。
    第一种方式相对简单一些,但是需要额外开辟空间,空间复杂度是O(n),第二种方式相对复杂一些,但是不会额外开辟空间,空间复杂度时O(1)。

示例图如下:

image.png 最大堆的实现代码如下:

#include <stdio.h>
#include <vector>
#include <iostream>

using namespace std;

class MaxHeapExe {
private:
    vector<int> data;
    
public:
    int getSize() {
        return (int)data.size();
    }
    
    bool isEmpty() {
        return data.size() == 0;
    }
    
    ///                 0
    ///         1               2
    ///     3       4
    /// 上面的图形中,1位置的孩子位置是3、4
    int parent(int k) {
        return (k-1)/2;
    }
    
    int leftChild(int k) {
        return k * 2 + 1;
    }
    
    int rightChild(int k) {
        return k * 2 + 2;
    }
    
    /// 添加元素到数组末尾,让新添加的元素进行上浮
    void add(int v) {
        data.push_back(v);
        siftUp((int)data.size()-1);
    }
    
    int findMax() {
        if (data.size() == 0) {
            throw "data is empty";
        }
        
        return data[0];
    }
    
    int extractMax() {
        int ret = findMax();
        data[0] = data[data.size()-1];
        data.pop_back();
        siftDown(0);
        return ret;
    }
    
    /// 如果元素大于父亲节点,则和父亲节点交换
    void siftUp(int k) {
        
        while (k > 0) {
            int parentIndex = parent(k);
            
            if (data[k] > data[parentIndex]) {
                swap(k, parentIndex);
                k = parentIndex;
            } else {
                break;
            }
        }
    }
    
    /// 如果元素小于孩子,那么和左右孩子中的最大孩子交换
    void siftDown(int k) {
        cout << "start siftDown \n";
        /// 如果没有左孩子了,直接结束
        while (true) {
            int j = leftChild(k);
            if (j >= data.size()) {
                break;
            }
            
            /// 判断到底是左孩子大还是右孩子大
            if (j+1<data.size() && data[j+1] > data[j]) {
                j = j + 1;
            }
            
            if (data[j] > data[k]) {
                swap(j, k);
                k = j;
            } else {
                /// 说明孩子都没有父亲节点大,直接结束。这里容易忽略!!!
                break;
            }
        }
    }
    
    void swap(int i, int j) {
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
};

原地堆排序代码如下:
将最大堆改造,可支持传入数组进行操作

#include <stdio.h>
#include <vector>

using namespace std;

class MaxHeapExe {
    
public:
    
    ///             0
    ///         1       2
    ///     3       4
    /// 上面的图形中,1位置的孩子位置是3、4
    int parent(int k) {
        return (k-1)/2;
    }
    
    int leftChild(int k) {
        return k * 2 + 1;
    }
    
    int rightChild(int k) {
        return k * 2 + 2;
    }
    
    /// 如果元素小于孩子,那么和左右孩子中的最大孩子交换
    void siftDown(vector<int>& arr, int k, int n) {
        /// 如果没有左孩子了,直接结束
        while (true) {
            int j = leftChild(k);
            if (j > n) {
                break;
            }
            
            /// 判断到底是左孩子大还是右孩子大
            if (j+1<=n && arr[j+1] > arr[j]) {
                j = j + 1;
            }
            
            if (arr[j] > arr[k]) {
                swap(arr, j, k);
                k = j;
            } else {
                /// 说明孩子都没有父亲节点大,直接结束。这里容易忽略!!!
                break;
            }
        }
    }
    
    void swap(vector<int>& arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    
};

将数组进行 heapify,然后循环将最大元素和未排序的元素交换

#include <stdio.h>
#include <vector>

#include "MaxHeapExe.hpp"

using namespace std;

class MaxHeapSort {
public:
    void sort(vector<int>& arr) {
        MaxHeapExe maxHeapExe;
        int size = (int)arr.size();
        /// 先执行 heapify,将数组整理成最大堆
        for (int i=(size-2)/2; i>=0; i--) {
            maxHeapExe.siftDown(arr, i, size-1);
        }
        
        /// 不停的将最大元素和未排序的元素交换,然后执行 siftDown 的操作
        for (int i=size-1; i>0; i--) {
            maxHeapExe.swap(arr, 0, i);
            maxHeapExe.siftDown(arr, 0, i-1); /// 注意,这里 siftDown 的边界是未排序的数组的最后一个元素的下标
        }
    }
};

测试代码如下:

vector<int> arr = {62,41,30,28,16,22,13,19,17,15,32,99,11};
MaxHeapSort maxHeapSort;
maxHeapSort.sort(arr);

输出结果如下:

image.png