【程序人生】數據結構雜記(五)

說在前面

個人讀書筆記

快速排序

分治策略

與歸併排序算法一樣,快速排序(quicksort)算法也是分治策略的典型應用,但二者之間也有本質區別。

歸併排序的計算量主要消耗於有序子向量的歸併操作,而子向量的劃分卻幾乎不費時間。快速排序恰好相反,它可以在O(1)O(1)時間內,由子問題的解直接得到原問題的解;但爲了將原問題劃分爲兩個子問題,卻需要O(n)時間。

快速排序算法雖然能夠確保,劃分出來的子任務彼此獨立,並且其規模總和保持漸進不變,卻不能保證兩個子任務的規模大體相當——實際上,甚至有可能極不平衡。

因此,該算法並不能保證最壞情況下的O(nlogn)時間複雜度。

儘管如此,它仍然受到人們的青睞,並在實際應用中往往成爲首選的排序算法。究其原因在於,快速排序算法易於實現,代碼結構緊湊簡練,而且對於按通常規律隨機分佈的輸入序列,快速排序算法實際的平均運行時間較之同類算法更少。

快速排序算法

在這裏插入圖片描述

如上圖所示,考查任一向量區間S[lo,hi)S[lo, hi)。對於任何lo<=mi<hilo <= mi < hi,以元素S[mi]S[mi]爲界,都可分割出前、後兩個子向量S[lo,mi)S[lo, mi)S(mi,hi)S(mi, hi)。若S[lo,mi)S[lo, mi)中的元素均不大於S[mi]S[mi],且S(mi,hi)S(mi, hi)中的元素均不小於S[mi]S[mi],則元素S[mi]S[mi]稱作向量S的一個軸點(pivot)。

設向量SS經排序可轉化爲有序向量SS'。不難看出,軸點位置mimi必然滿足如下充要條件:

  • S[mi]=S[mi]S[mi] = S'[mi]
  • S[lo,mi)S[lo, mi)S[lo,mi)S'[lo, mi)的成員完全相同
  • S(mi,hi)S(mi, hi)S(mi,hi)S'(mi, hi)的成員完全相同

因此,不僅以軸點S[mi]S[mi]爲界,前、後子向量的排序可各自獨立地進行,而且更重要的是,一旦前、後子向量各自完成排序,即可立即(在O(1)O(1)時間內)得到整個向量的排序結果。

採用分治策略,遞歸地利用軸點的以上特性,便可完成原向量的整體排序。

快速排序算法:
在這裏插入圖片描述
可見,軸點的位置一旦確定,則只需以軸點爲界,分別遞歸地對前、後子向量實施快速排序;子向量的排序結果就地返回之後,原向量的整體排序即告完成。

算法的核心與關鍵在於:
軸點構造算法partition()應如何實現?可以達到多高的效率?

軸點構造——快速劃分算法

在這裏插入圖片描述不妨取首元素m=S[lo]m = S[lo]作爲候選,將其從向量中取出並做備份,騰出的空閒單元便於其它元素的位置調整。

然後不斷試圖移動lolohihi,使之相互靠攏。

當然,整個移動過程中,需始終保證lo(hi)lo(hi)左側(右側)的元素均不大於(不小於)mm

最後,當lolohihi彼此重合時,只需將原備份的mm回填至這一位置,則S[lo=hi]=mS[lo = hi]= m便成爲一個名副其實的軸點。

以上過程在構造出軸點的同時,也按照相對於軸點的大小關係,將原向量劃分爲左、右兩個子向量,故亦稱作快速劃分(quick partitioning)算法。

在這裏插入圖片描述
c++實現數組的快速排序
時間複雜度O(nlogn)O(nlogn)

#include <iostream>

using namespace std;


template <typename T>
int __partition(T arr[], int l, int r)
{
    // 第一個值拿出來, 假設爲軸點
    T v = arr[l];

    int j = l;
    // 從第二個開始向後遍歷
    for (int i = l + 1; i <= r; i++)
    {
        if (arr[i] < v)
        {
            swap(arr[j + 1], arr[i]);
            j++;
        }
    }

    swap(arr[l], arr[j]);

    return j;
}

template <typename T>
void __quickSort(T arr[], int l, int r)
{
    if (l>=r)
    {
        return;
    }

    int p = __partition(arr, l, r);
    __quickSort(arr, l, p-1);
    __quickSort(arr, p+1, r);
}


template <typename T>
void quickSort(T arr[], int n)
{
    __quickSort(arr, 0, n-1);
}

int main()
{
    int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
    quickSort(a, 10);
    
    for (int i = 0; i< 10; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;
    
    return 0;
}

當快速排序軸點兩側數量極度不平衡的時候,算法將退化。
如上,每次都選取第一個數值爲軸點時。這樣當數組完全有序時,算法時間複雜度退化到O(n2)O(n^2),解決方法是在進行快速劃分算法中,每次都隨機選取一個點和第一個點交換,再將這個點作爲軸點。

但是當重複的值比較多時,由於劃分的兩部分,其中一部分是小於軸點,另一部分是大於或等於軸點,劃分的兩部分仍然可能極不平衡,改進的算法如下:

#include <iostream>
#include <stdlib.h>

using namespace std;


template <typename T>
int __partition(T arr[], int l, int r)
{
    swap(arr[l], arr[rand() % (r-l+1) + l]);
    // 第一個值拿出來, 假設爲軸點
    T v = arr[l];

    int i = l + 1, j = r;
    while (true)
    {
        while (i <= r && arr[i] < v)
        {
            i++;
        }
        while (j >= l+1 && arr[j] > v)
        {
            j--;
        }
        
        if (i>j)
        {
            break;
        }
        swap(arr[i], arr[j]);
        i++;
        j--;
    }

    swap(arr[l], arr[j]);

    return j;
}

template <typename T>
void __quickSort(T arr[], int l, int r)
{
    if (l>=r)
    {
        return;
    }

    int p = __partition(arr, l, r);
    __quickSort(arr, l, p-1);
    __quickSort(arr, p+1, r);
}


template <typename T>
void quickSort(T arr[], int n)
{
    srand(time(NULL));
    __quickSort(arr, 0, n-1);
}

int main()
{
    int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
    quickSort(a, 10);
    
    for (int i = 0; i< 10; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;
    
    return 0;
}

python實現的快速排序

# -*- coding:utf-8 -*-

import numpy as np
import time


def swap(list0, index1, index2):
    b = list0[index1]
    list0[index1] = list0[index2]
    list0[index2] = b


def partition(list0, l, r):
    swap(list0, l, np.random.randint(0, len(list0)) % (r-l+1) + l)
    v = list0[l]

    p = l+1
    q = r

    while True:
        while (p <= r) and (list0[p] < v):
            p = p + 1
        while (q >= l+1) and (list0[q] > v):
            q = q - 1

        if p >= q:
            break
        else:
            swap(list0, p, q)
            p = p + 1
            q = q - 1

    swap(list0, l, q)

    return q


def quick_sort(list0, l, r):
    if l >= r:
        return

    p = partition(list0, l, r)

    quick_sort(list0, l, p-1)
    quick_sort(list0, p+1, r)


def main(list0, n):
    np.random.seed(int(str(time.time()).split(".")[1]))
    quick_sort(list0, 0, n-1)

    print(list0)


if __name__ == "__main__":
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    main(a, len(a))

三路快速排序算法

c++實現數組的三路快速排序

#include <iostream>
#include <stdlib.h>

using namespace std;


template <typename T>
void __quickSort3Ways(T arr[], int l, int r)
{
    if (l>=r)
    {
        return;
    }
    
    swap(arr[l], arr[rand() % (r-l+1) + l]);
    T v = arr[l];

    int lt = l;
    int gt = r + 1;
    int i = l + 1;
    while (i < gt)
    {
        if (arr[i] < v)
        {
            swap(arr[i], arr[lt+1]);
            i++;
            lt++;
        }
        else if (arr[i]>v)
        {
            swap(arr[i], arr[gt-1]);
            gt--;
        }
        else
        {
            i++;
        }
    }
    swap(arr[l], arr[lt]);

    __quickSort3Ways(arr, l, lt-1);
    __quickSort3Ways(arr, gt, r);
}


template <typename T>
void quickSort3Ways(T arr[], int n)
{
    srand(time(NULL));
    __quickSort3Ways(arr, 0, n-1);
}

int main()
{
    int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
    quickSort3Ways(a, 10);
    
    for (int i = 0; i< 10; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;
    
    return 0;
}

python實現的三路快速排序

# -*- coding:utf-8 -*-

import numpy as np
import time


def swap(list0, index1, index2):
    b = list0[index1]
    list0[index1] = list0[index2]
    list0[index2] = b


def quick_sort(list0, l, r):
    if l >= r:
        return

    swap(list0, l, np.random.randint(0, len(list0)) % (r - l + 1) + l)
    v = list0[l]

    p = l
    q = r + 1
    i = l + 1

    while i < q:
        if list0[i] < v:
            swap(list0, i, p+1)
            p = p + 1
            i = i + 1
        elif list0[i] > v:
            swap(list0, i, q-1)
            q = q - 1
        else:
            i = i + 1

    swap(list0, l, p)

    quick_sort(list0, l, p-1)
    quick_sort(list0, q, r)


def main(list0, n):
    np.random.seed(int(str(time.time()).split(".")[1]))
    quick_sort(list0, 0, n-1)

    print(list0)


if __name__ == "__main__":
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    main(a, len(a))

求逆序對的數量——使用歸併排序

# -*- coding:utf-8 -*-


class MergeSort:
    def __init__(self, list0, n):
        self.list0 = list0
        self.n = n

    @staticmethod
    def _merge(list0, l, mid, r):
        global count
        list1 = list0[:]

        p = l
        q = mid + 1

        for i in range(r-l+1):
            if p > mid:
                list0[i+l] = list1[q]
                q = q + 1
            elif q > r:
                list0[i+l] = list1[p]
                p = p + 1
            elif list1[p] <= list1[q]:
                list0[i+l] = list1[p]
                p = p + 1
            else:
                list0[i+l] = list1[q]
                count = count + mid + 1 - p
                q = q + 1

    def _merge_sort(self, list0, l, r):
        if l >= r:
            return

        mid = int((l+r)/2)
        self._merge_sort(list0, l, mid)
        self._merge_sort(list0, mid+1, r)
        self._merge(list0, l, mid, r)

    def merge_sort(self):
        self._merge_sort(self.list0, 0, self.n - 1)


def main():
    global count
    count = 0

    # -------------------------------------
    x = 0
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    for i in range(len(a)-1):
        for j in range(i+1, len(a)):
            if a[j] < a[i]:
                x = x + 1
    # -------------------------------------

    b = MergeSort(a, len(a))
    b.merge_sort()

    print(a)
    print(count, x)


if __name__ == "__main__":
    main()

求第n大的值——使用快速排序

# -*- coding:utf-8 -*-

import numpy as np
import time
import sys


def swap(list0, index1, index2):
    b = list0[index1]
    list0[index1] = list0[index2]
    list0[index2] = b


def partition(list0, l, r, th):
    swap(list0, l, np.random.randint(0, len(list0)) % (r-l+1) + l)
    v = list0[l]

    p = l+1
    q = r

    while True:
        while (p <= r) and (list0[p] < v):
            p = p + 1
        while (q >= l+1) and (list0[q] > v):
            q = q - 1

        if p >= q:
            break
        else:
            swap(list0, p, q)
            p = p + 1
            q = q - 1

    swap(list0, l, q)

    return q


def quick_sort(list0, l, r, th):
    if l >= r:
        return

    p = partition(list0, l, r, th)
    if p == len(list0) - th:
        print(list0[p])
        sys.exit(0)

    elif p > len(list0) - th:
        quick_sort(list0, l, p-1, th)
    else:
        quick_sort(list0, p+1, r, th)


def main(list0, n, th):
    np.random.seed(int(str(time.time()).split(".")[1]))
    quick_sort(list0, 0, n-1, th)

    # print(list0)


if __name__ == "__main__":
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    # 想找第th大的數的值, th小於等於len(a) + 1
    th = 3
    main(a, len(a), th)

結語

如果您有修改意見或問題,歡迎留言或者通過郵箱和我聯繫。
手打很辛苦,如果我的文章對您有幫助,轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章