數據結構(七)排序---快速排序


一:基本思想

快速排序是冒泡排序的改進版,也是最好的一種內排序,在很多面試題中都會出現,也是作為程序員必須掌握的一種排序方法。
1.在待排序的元素任取一個元素作為基准(通常選第一個元素,但最的選擇方法是從待排序元素中隨機選取一個作為基准),稱為基准元素;

2.將待排序的元素進行分區,比基准元素大的元素放在它的右邊,比其小的放在它的左邊;

3.對左右兩個分區重復以上步驟直到所有元素都是有序的。

二:圖解實現過程

1:選取第一個點50作為樞軸位置,pivotkey是樞軸變量,設置兩個標志為low,high表示是否遍歷結束

2.比較low和high兩個數據,較低的放在前面,進行交換

3.只要low<=high,我們就要繼續循環,以樞軸為基准,現在調整low,使之++,直到low值大於樞軸值,再進行交換

4.現在樞軸到low處,我們開始調整high進行比較,直到high的值小於樞軸值,進行交換

5.繼續調整,選擇樞軸值到了high,我們需要去調整low++,比較大小

6.循環直到low>=high,循環成功

三:代碼實現

#include <stdio.h>
#include <stdlib.h>

void swap(int k[], int low, int high)
{
    int temp = k[low];
    k[low] = k[high];
    k[high] = temp;
}

//交換順序表中子表記錄,是樞軸記錄到位,並返回其所在位置
//此時在他之前(后)的記錄均不大(小)於它
int Partition(int k[], int low, int high)
{
    int pivotkey;
    pivotkey = k[low];    //用子表第一個記錄左樞軸記錄
    while (low<high)    //從表的兩端交替向中間掃描
    {
        while (low<high&&k[high] >= pivotkey)
            high--;
        swap(k, high, low);    //將比樞軸記錄小的記錄交換到低端
        while (low < high&&k[low] <= pivotkey)
            low++;
        swap(k, low, high);    //將比樞軸記錄大的記錄交換到高端
    }
    return low;    //返回樞軸所在位置
}

void Qsort(int k[], int low, int high)
{
    int pivot;
    if (low<high)
    {
        pivot = Partition(k, low, high);
        Qsort(k, low, pivot-1);    //對低子表進行遞歸排序
        Qsort(k, pivot + 1, high);    //對高子表進行遞歸排序
    }
}

int main()
{
    int i;
    int a[10] = { 5, 2, 6, 0, 3, 9, 1, 7, 4, 8 };
    Qsort(a, 0, 9);

    for (i = 0; i < 10; i++)
        printf("%d ", a[i]);

    system("pause");
    return 0;
}

四:快速排序優化

(一)優化選取樞軸

如果我們選取的樞軸值正好是處於整個序列大小的中間位置,那么可以將序列分為小數集合和大數集合。但是若是我們第一個選取的是最大值呢?我們交換后實質變化並不大
{9,1,5,8,3,7,4,6,2}

改進方法

三數取中法:取三個關鍵字先解析排序,將中間數作為樞軸,一般取左端,右端和中間三個數
int Partition(int k[], int low, int high)
{
    int pivotkey;
    int m = low + (high - low) / 2; //三數取中的判斷 if (k[low] > k[high]) swap(k, low, high); if (k[m] > k[high]) swap(k, m, high); if (k[m] < k[low]) swap(k, m, low); //此時low處是三個數中間值

    pivotkey = k[low];    //用子表第一個記錄左樞軸記錄
    while (low<high)    //從表的兩端交替向中間掃描
    {
        while (low<high&&k[high] >= pivotkey)
            high--;
        swap(k, high, low);    //將比樞軸記錄小的記錄交換到低端
        while (low < high&&k[low] <= pivotkey)
            low++;
        swap(k, low, high);    //將比樞軸記錄大的記錄交換到高端
    }
    return low;    //返回樞軸所在位置
}

(二)優化不必要的交換

我們從圖片發現,50這個關鍵字,其位置變化時1-9-3-6-5其最終目的還是在中間位置,所以我們在程序中的交換是不必要的,只需要進行覆蓋即可
int Partition(int k[], int low, int high)
{
    int pivotkey;
    int m = low + (high - low) / 2;
    //三數取中的判斷
    if (k[low] > k[high])
        swap(k, low, high);
    if (k[m] > k[high])
        swap(k, m, high);
    if (k[m] < k[low])
        swap(k, m, low);
    //此時low處是三個數中間值

    pivotkey = k[low];    //用子表第一個記錄左樞軸記錄
    while (low<high)    //從表的兩端交替向中間掃描
    {
        while (low<high&&k[high] >= pivotkey)
            high--;
        k[low] = k[high]; //直接進行覆蓋即可
        while (low < high&&k[low] <= pivotkey)
            low++;
        k[high] = k[low];
    }
    k[low] = pivotkey;  //在最后將樞軸值直接放入 return low;    //返回樞軸所在位置
}

(三)優化小數組

當數組非常小時,使用快排還不如直接插入的性能好,二這個數組大小的閾值我們一般選取7最好
#define MAX_LENGTH_INSERT_SORT 7    //數組閾值大小

void InsertSort(int k[], int n)    //對數組部分進行快排
{
    int i, j, temp;
    for (i = 1; i < n;i++)
    {
        if (k[i]<k[i-1])
        {
            temp = k[i];
            for (j = i - 1; k[j] > temp; j--)
                k[j + 1] = k[j];
            k[j + 1] = temp;
        }
    }
}

void Qsort(int k[], int low, int high)
{
    int pivot;
    if ((high-low)>MAX_LENGTH_INSERT_SORT)    //條件是包含high>low
    {
        pivot = Partition(k, low, high);
        Qsort(k, low, pivot - 1);    //對低子表進行遞歸排序
        Qsort(k, pivot + 1, high);    //對高子表進行遞歸排序
    }
    else
    {
        InsertSort(k + low, high - low + 1);    //對數組部分進行快排
    }
}

(四)優化遞歸操作(尾遞歸)

 尾遞歸

遞歸與尾遞歸總結

顧名思義,尾遞歸就是從最后開始計算, 每遞歸一次就算出相應的結果, 也就是說, 函數調用出現在調用者函數的尾部, 因為是尾部, 所以根本沒有必要去保存任何局部變量. 
直接讓被調用的函數返回時越過調用者, 返回到調用者的調用者去。
尾遞歸就是把當前的運算結果(或路徑)放在參數里傳給下層函數,深層函數所面對的不是越來越簡單的問題,而是越來越復雜的問題,因為參數里帶有前面若干步的運算路徑。
尾遞歸是極其重要的,不用尾遞歸,函數的堆棧耗用難以估量,需要保存很多中間函數的堆棧。比如f(n, sum) = f(n-1) + value(n) + sum; 會保存n個函數調用堆棧,而使用尾遞歸f(n, sum) = f(n-1, sum+value(n)); 這樣則只保留后一個函數堆棧即可,之前的可優化刪去。

普通遞歸

int FibonacciRecursive(int n)
 {
     if( n < 2)
         return n;
     return (FibonacciRecursive(n-1)+FibonacciRecursive(n-));
 }

尾遞歸

 int FibonacciTailRecursive(int n,int ret1,int ret2)
 {
    if(n==0)
       return ret1; 
     return FibonacciTailRecursive(n-1,ret2,ret1+ret2);
}
我們需要盡可能將遞歸寫成尾遞歸形式

代碼實現

void Qsort(int k[], int low, int high)
{
    int pivot;
    if ((high-low)>MAX_LENGTH_INSERT_SORT)    //條件是包含high>low
    {
        while (low<high)
        {
            pivot = Partition(k, low, high);
            Qsort(k, low, pivot - 1);    //對低子表進行遞歸排序
            low = pivot + 1;    //我們將是一個遞歸的改變的k值直接插入到下一個QSort中
        }
    }
    else
    {
        InsertSort(k + low, high - low + 1);    //對數組部分進行快排
    }
}

五:性能分析

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM