排序_2.快速排序

快速排序

簡介:

 快速排序(QuickSort)是對冒泡排序的一種改進的交換排序算法,又稱 分區交換排序(partition-exchange sort).
在平均狀況下,快速排序排序n個項目要Ο(n log n)次比較。在最壞狀況下則需要Ο(n2)次比較,但這種狀況並不常見。
事實上,快速排序通常明顯比其他Ο(n log n)算法更快,因爲它的內部循環(inner loop)可以在大部分的架構上很有效率地被實現出來。因此稱爲快速排序.
Quick_sort

算法描述:

快速排序使用分治法(Divide and conquer)策略來把一個序列(list)分爲兩個子序列(sub-lists)。

  • 步驟爲:

    1. 從數列中挑出一個元素,稱爲”基準”(pivot),
    2. 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區結束之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作。
    3. 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。

    遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,但是這個算法總會結束,因爲在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。

  • 化簡得:

    1. 先從數列中取出一個數作爲基準數。
    2. 分區過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。
    3. 再對左右區間重複第二步,直到各區間只有一個數。

冒泡排序視頻演示 備份鏈接1 備份鏈接2

過程演示:

首先:
       a[0]  a[1]  a[2]  a[3]  a[4]  a[5]  a[6]  a[7]  a[8]  a[9]
     3     0     1     8     7     2     5     4     9     6
- 第一次:
                                                                 // 取出tmp = a[0], 
                                                                        // i = 0, j = 9, 比較 tmp與 a[j](a[9]) , 3 <---> 6, 小, j--, 得:
     3     0     1     8     7     2     5     4     9     6 
                                                                 // i = 0, j = 8, 比較 tmp與 a[j](a[8]) , 3 <---> 9, 小, j--, 得:
     3     0     1     8     7     2     5     4     9     6 
                                                                 // i = 0, j = 7, 比較 tmp與 a[j](a[7]) , 3 <---> 4, 小, j--, 得:
     3     0     1     8     7     2     5     4     9     6 
                                                                 // i = 0, j = 6, 比較 tmp與 a[j](a[6]) , 3 <---> 5, 小, j--, 得:
     3     0     1     8     7     2     5     4     9     6 
                                                                 // i = 0, j = 5, 比較 tmp與 a[j](a[5]) , 3 <---> 2, 大, a[i] = a[j], i++, 得:
     2     0     1     8     7     2     5     4     9     6 
                                                                 // i = 1, j = 5, 比較 tmp與 a[i](a[1]) , 3 <---> 0, 大, i++, 得:
     2     0     1     8     7     2     5     4     9     6 
                                                                 // i = 2, j = 5, 比較 tmp與 a[i](a[2]) , 3 <---> 1, 大, i++, 得:
     2     0     1     8     7     2     5     4     9     6 
                                                                 // i = 3, j = 5, 比較 tmp與 a[i](a[3]) , 3 <---> 8, 小,a[j] = a[i], j--, 得:
     2     0     1     8     7     8     5     4     9     6 
                                                                 // i = 3, j = 4, 比較 tmp與 a[j](a[4]) , 3 <---> 7, 小, j--, 得:
     2     0     1     8     7     2     5     4     9     6 
                                                                 // i = 3, j = 3, a[i] = a[j] = tmp;得:
     2     0     1     3     7     2     5     4     9     6  
     
   這時,經過一次排序,我們得到兩組,一組比tmp小的區域,一組比tmp大的區域,接下來,分別在兩組區域內再進行區分,重複這樣的操作,即可獲得有序序列。
    
- 第二次:
       a[0]  a[1]  a[2]  a[3]  a[4]  a[5]  a[6]  a[7]  a[8]  a[9]
     2     0     1     [3]    7     2     5     4     9     6
       a[0]  a[1]  a[2]
     2     0     1
                                                                 // 取出tmp = a[0], 

                                                                 // i = 0, j = 2, 比較 tmp與 a[j](a[2]) , 2 <---> 1,  大, a[i] = a[j], i++, 得:

     1     0     1 
                                                                 // i = 1, j = 2, 比較 tmp與 a[i](a[1]) , 2 <---> 0,  大, i++, 得:

     1     0     1 
                                                                 // i = 2, j = 2, a[i] = a[j] = tmp;得:

        1     0     2
 
- 第三次:
                                                                                                                                //這時,左邊的仍沒有結束,再進行分區。         
        1     0    [2]
       
        1     0
                                                                 //tmp = 1
                                                                 // i = 0, j = 1, 比較 tmp與 a[j](a[1]) , 1 <---> 0,  大, a[i] = a[j], i++, 得:
        0     0
                                                                 // i = 1, j = 1, a[i] = a[j] = tmp;得:
        0     1
                                                                 //至此,左邊的順序已經排序完成
    [0     1     2     3]    7     2     5     4     9     6
                                                                 //再對右邊進行同樣的操作即可。
           ...

    我們可以看到,通過每次將序列分成將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。
再對左右區間重複,直到各區間只有一個數兩部分,即可完成排序過程.
   

程序代碼:

// 快排思路:
//
// 1,選取一個數值爲關鍵值並且記錄下來
// 2. 給定兩個下標i,j分別指向頭和尾(0, length - 1)
//
// 3.循環操作:
// 從後向前找遇到比關鍵值小的放在i下標;
// 從前向後找遇到比關鍵值大的放在j下標;
// 當i和j相等,退出
// 把關鍵值填充進去
//
// 4.對關鍵值兩邊做遞歸處理
按照此定義,我們很容易寫出快速排序的程序代碼:

//劃分算法
int Partition(int s[], int left, int right) //返回調整後基準數的位置
{//以s[left]作爲基準劃分
    int i = left;
    int j = right;
    int x = s[left];   //s[left]即s[i]就是基準
    while (i < j)    //從表(即序列s[i]~s[j])的兩端交替向中間掃描
    {
        // 從右向左掃描查找第一個小於x的數s[j]
         while(i < j && s[j] >= x)
             j--;
         if(i < j)     //當i < j時,則若s[j] < x 
         {
             s[i] = s[j]; //將s[j]填到s[i]中,將s[j]交換到表的左端
             i++;
         }
         // 從左向右掃描查找第一個大於x的數s[i]
         while(i < j && s[i] < x)
             i++;
         if(i < j)         //當i < j時,則若s[i] > x 
         {
             s[j] = s[i]; //將s[i]填到s[j]中,將s[i]交換到表的左端
             j--;
         }
     }
     //退出時,i等於j。將x填到排好序時應放的位置。
     s[i] = x;
     return i;
}
//再寫分治法的代碼:
void quick_sort1(int s[], int left, int right)
{
    int i = 0;
    if (left < right)
    {
        i = Partition(s, left, right);//先成挖坑填數法調整s[]
        quick_sort1(s, left, i - 1); // 遞歸調用
        quick_sort1(s, i + 1, right);
    }
}

算法Partition完成在給定區間s[i]~s[j]中,一趟快速排序的劃分.
1.設置兩個搜索指針i和j來指向給定區間的第一個和最後一個記錄,並將第一個作爲基準.
2.首先,從j指針開始向左搜索比基準小的記錄(即,該記錄應該位於基準的左側),找到後將其交換到i指針處(此時位於基準的左側);
3.i指針右移一個位置,由此開始自左向右搜索比基準大的記錄(即,該記錄應該位於基準的右側);
4.j指針左移一個位置,並繼續上述自右向左搜索,交換的過程.
5.如此由兩端交替向中間搜索交換,直到i,j相等,表明,i記錄的左側都比基準小,j右側都比基準值大.而i,j指向的同一個位置,就是基準值最終要放置的位置.

對其合併整理,優化代碼如下:

void QuickSort2(int *a, int left, int right)
{
    if(left >= right)
    {
        return ;
    }
    int i = left;
    int j =right;
    int key = a[left];

    while(i < j)
    {
        while(i <j && key <= a[j])
        {
            j--;
        }
        a[i] = a[j];

        while(i <j && key>= a[i])
        {
            i++;
        }
        a[j] = a[i];
    }

    a[i] = key;
    QuickSort2(a , left, i-1);
    QuickSort2(a, i+1, right);
}
int QuickSort2(int *a, int left, int right)
{
    if(left >= right)
    {
        return -1;
    }
    int i = left;
    int j =right;
    int flag = 0;
    int key = a[left];

    while(i < j)
    {
        if(flag == 0)
        {
            if( key <= a[j])
            {
                j--;
            }
            else
            {
                a[i] = a[j];
                flag = 1;
            }
        }
        if(flag == 1)
        {
            if( key>= a[i])
            {
                i++;
            }
            else
            {
                a[j] = a[i];
                flag = 0;
            }
        }
    }

    a[i] = key;
    QuickSort2(a , left, i-1);
    QuickSort2(a, i+1, right);
    return i;
}

算法分析

正規分析
平均複雜度
空間複雜度

功能檢測

  • 檢測代碼:
int main()
{
    int i =0;
    int a[] = {3,0,1,8,7,2,5,4,6,9};
    int n = sizeof(a)/sizeof(a[0]);
    quicksort1(a, 0, n-1);
//  QuickSort2(a, 0, n-1);

    printf("\n");
    for(i = 0; i < n ; ++i)
    {
        printf("%3d",a[i]);
    }
    printf("\n");
    return 0;
}
  • 運行結果:
root@aemonair:~/Desktop# cc.sh QuickSort.c 
Compiling ...
-e CC      QuickSort.c -g -lpthread
-e         Completed .
-e         Sun Jul 24 16:46:53 CST 2016
root@aemonair:~/Desktop# ./QuickSort 

  0  1  2  3  4  5  6  7  8  9

總結:

快速排序被認爲是在所有同數量級(O(n lg n))的排序算法中平均性能最好.
但是如果初始序列有序或基本有序時,快速排序將退化成冒泡排序,此時的時間複雜度上升到O(n^2).
對此我們有如下優化方案:
如以中間的數作爲基準數的,or隨機選擇基準數,and區間內數據較少時直接用另的方法排序以減小遞歸深度。

  • 三平均分區法
    關於這一改進的最簡單的描述大概是這樣的:與一般的快速排序方法不同,它並不是選擇待排數組的第一個數作爲基準,而是選用待排數組最左邊、最右邊和最中間的三個元素的中間值作爲基準。這一改進對於原來的快速排序算法來說,主要有兩點優勢:
      (1) 首先,它使得最壞情況發生的機率減小了。
      (2) 其次,未改進的快速排序算法爲了防止比較時數組越界,在最後要設置一個哨點。

  • 根據分區大小調整算法
    這一方面的改進是針對快速排序算法的弱點進行的。快速排序對於小規模的數據集性能不是很好。可能有人認爲可以忽略這個缺點不計,因爲大多數排序都只要考慮大規模的適應性就行了。但是快速排序算法使用了分治技術,最終來說大的數據集都要分爲小的數據集來進行處理。
      由此可以得到的改進就是,當數據集較小時,不必繼續遞歸調用快速排序算法,而改爲調用其他的對於小規模數據集處理能力較強的排序算法來完成.
      另一種快速排序的改進策略是在遞歸排序子分區的時候,總是選擇優先排序那個最小的分區。這個選擇能夠更加有效的利用存儲空間從而從整體上加速算法的執行。

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