快排与堆排

本文复习一下快速排序和堆排序 2 种排序算法(为了多快好省地刷 leetcode )。

快排

主要思想:

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

时间复杂度 (O(n log n)) ,空间复杂度取决于是否使用递归实现。

代码实现:

int partition(vector<int> &v, int p, int r)
{
    int x = v[r];
    int i = p - 1;
    for (int j = p; j < r; j++)
    {
        if (v[j] < x)
            i = i + 1, swap(v[i], v[j]);
    }
    swap(v[i + 1], v[r]);
    return i + 1;
}
void quickSort(vector<int> &v, int p, int r)
{
    if (p < r)
    {
        int q = partition(v, p, r);
        quickSort(v, p, q - 1);
        quickSort(v, q + 1, r);
    }
}

partition 函数的图解(图源为《算法导论》):

在这里的实现,我们默认区间的最右侧 v[r] = 4 为主元,将上面所示的数组分为 2 部分,左侧小于等于 4,右侧大于 4 。下面是函数中几个临时变量所表示的含义:

堆排

堆的性质:

子结点的键值总是小于(或者大于)它的父节点。

三个步骤:

  • 建立大顶堆
  • 把最大的的数字放在堆的最末尾 ,即 j = n-1, ..., 1
  • 调整堆,使其满足大顶堆的性质

代码实现:

class Heap
{
private:
    int heapSize;
    // index start at 0
    inline int getLeft(int x) { return 2 * x + 1; }
    inline int getRight(int x) { return 2 * x + 2; }
    void heapify(vector<int> &v, int idx)
    {
        int l = getLeft(idx), r = getRight(idx);
        int largest = idx;
        if (l < heapSize && v[l] > v[largest]) largest = l;
        if (r < heapSize && v[r] > v[largest]) largest = r;
        // largest 这个位置被影响,所以需要递归调整
        // Of course, 也可以通过迭代实现
        if (largest != idx)
            swap(v[idx], v[largest]), heapify(v, largest);
    }
    void buildHeap(vector<int> &v)
    {
        heapSize = v.size();
        for (int i = v.size() / 2; i >= 0; i--)
            heapify(v, i);
    }

public:
    void heapSort(vector<int> &v)
    {
        buildHeap(v);
        for (int i = v.size() - 1; i >= 1; i--)
        {
            swap(v[i], v[0]);
            heapSize--;
            heapify(v, 0);
        }
    }
};

heapify

heapify 函数图解如下图所示。需要注意的是,图中数组下标是从 1 开始的,而上面的代码实现是从 0 开始的。

时间复杂度 (O(log n)) .

buildHeap

在表示堆的数组中,范围 $lfloor n/2 floor $ 到 (n-1) 是叶子节点(下标从 0 开始),对于叶子节点,自然而然会满足堆的性质,对叶子节点调用 heapify 丝毫没有影响,因此不需要调整。这就是为什么 for 循环的范围是 size/2 -> 0

时间复杂度为 (O(n)) .

heapSort

调用 buildHeap 后的数组,是一个大顶堆,所以 v[0] 是最大的数字,我们把它交换到数组的最末尾处。然后对 [0, heapSize) 范围内的数字进行 heapify ,因为影响的只有位置 0 ,所以只需要调用一次 heapify(v, 0) 就能使得数组满足堆的性质。

时间复杂度 (O(nlog n)) .

heapSort 的图解如下:

原文地址:https://www.cnblogs.com/sinkinben/p/13854240.html