1. 程式人生 > >Aemonair's 世界樹.

Aemonair's 世界樹.

快速排序

簡介:

 快速排序(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. 再對左右區間重複第二步,直到各區間只有一個數。

過程演示:

首先:
       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;
}
  • 執行結果:
[email protected]:~/Desktop# cc.sh QuickSort.c 
Compiling ...
-e CC      QuickSort.c -g -lpthread
-e         Completed .
-e         Sun Jul 24 16:46:53 CST 2016
[email protected]:~/Desktop# ./QuickSort 

  0  1  2  3  4  5  6  7  8  9

總結:

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

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

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

相關推薦

Aemonair's 世界

快速排序 簡介:  快速排序(QuickSort)是對氣泡排序的一種改進的交換排序演算法,又稱 分割槽交換排序(partition-exchange sort). 在平均狀況下,快速排序排序n個專案要Ο(n log n)次比較。在最壞狀

HDU 2852 KiKi's K-Number (狀數組 && 二分)

eof while ron name += oid names 如果 一個 題意:給出對容器的總操作次數n, 接下來是這n個操作。這裏對於一個容器提供三種操作, 分別是插入、刪除和查找。輸入0 e表示插入e、輸入1 e表示刪除e,若元素不存在輸出No Elment!、輸

Bzoj3572 [Hnoi2014]世界

content color get open 生活 num 兩個 input string Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1713 Solved: 923 Description 世界樹是一棵

POJ訓練計劃2528_Mayor&#39;s posters(線段/成段更新+離散化)

rule you mon bsp 左右 for rst scribe i+1 解題報告 id=2528">地址傳送門 題意: 一些海報,覆蓋上去後還能看到幾張。 思路: 第一道離散化的題。 離散化的意思就是區間壓縮然後映射。 給你這麽幾個區間[1,30

HDU 6162 Ch’s gift (剖 + 離線線段

push panel hose either 插值 dfs dir sample start Ch’s gift Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K (Ja

bzoj 3572: [Hnoi2014]世界

png 樹結構 多少 方式 alt [0 truct int 藍色 Description 世界樹是一棵無比巨大的樹,它伸出的枝幹構成了整個世界。在這裏,生存著各種各樣的種族和生靈,他們共同信奉著絕對公正公平的女神艾莉森,在他們的信條裏,公平是使世界樹能夠生生不息、持續運

[BZOJ3572][HNOI2014]世界(虛DP)

iostream ash char ios swa break sort 之前 題目 代碼用時1:15 思想比較簡單的虛樹DP,但細節巨茍,大部分代碼都是LCA/DP/虛樹模板,真正需要自己寫的其實並不多。 寫之前要有一個清晰的思路和框架,細節要有一個比較清楚的認識,不能依

[HNOI 2014]世界

答案 bool hnoi get math std 編號 markdown 就是 Description 題庫鏈接 給出一棵 \(n\) 個節點的樹, \(q\) 次詢問,每次給出 \(k\) 個關鍵點。樹上所有的點會被最靠近的關鍵點管轄,若距離相等則選編號最小的那個。求每

ural 1707. Hypnotoad's Secret(線段)

per modify cst any div ini ref sdn sta 題目鏈接:ural 1707. Hypnotoad‘s Secret 題目大意:給

【題解】HNOI2014世界

清醒 space 小時 swa print bsp min 節點 建立   腦子不清醒的時候千萬別寫題。寫題寫不下去了千萬別死扛,重構才是你唯一的出路QAQ   昨天很想快點寫道題,思路沒有很清晰的時候就寫了,結果……今天一怒之下決定重整思路重

JZOJ5776. 【NOIP2008模擬】小x遊世界

ref alt zoj style return ++i col oid void 題目:【NOIP2008模擬】小x遊世界樹; 題目的附加題解給的很清楚,這裏只給一個代碼; 1 #include<iostream> 2 #include<cst

[JZOJ5776]【NOIP2008模擬】小x遊世界

+= 不一定 truct 一行 自己 平臺 更新 return print Description 小x得到了一個(不可靠的)小道消息,傳說中的神島阿瓦隆在格陵蘭海的某處,據說那裏埋藏著亞瑟王的寶藏,這引起了小x的好奇,但當他想前往阿瓦隆時發現那裏只

HDU 6447 YJJ’s Salesman (狀數組 + DP + 離散)

isp code XML .org ide oid data- element scanf 題意: 二維平面上N個點,從(0,0)出發到(1e9,1e9),每次只能往右,上,右上三個方向移動, 該N個點只有從它的左下方格點可達,此時可獲得收益。求該過程最大收益。 分析:我們

hdu 6444 Neko's loop 線段區間更新

its 得到 %d 長度 con oid 維護 大字段 每次 題目連接:Neko‘s loop 題意:給一個長度為n的環,下標從0~n-1,環上每個點有個值表示到這個點會得到的快樂值。,然後每次可以花費1能量往後跳k步。你可以選擇任意點開始跳,可以任意點結束,最多跳m次問

POJ--2528 Mayor's posters (線段

題目連結:POJ--2528 Mayor's posters 題目大意:給你一個無限長的板子,然後依次往上面貼n張等高的海報,問你最後能看到多少張海報。 思路分析:線段樹區間更新問題,但是要注意,給的長度的可能非常大,有1e9,不加處理直接維護一個線段樹肯定會 MLE,TLE,但是我們注意到一共最多隻

HDU 5592 ZYB's Premutation (線段求逆序對)

題意 給你一組從1到n的排列,表示的是你當前所擁有的逆序對數,現在讓你重新還原這個排列。 思路 我們知道 [1⋅⋅⋅i] [ 1 ·

D - Mayor's posters (線段 + 離散化)

滴答滴答---題目連結 The citizens of Bytetown, AB, could not stand that the candidates in the mayoral election campaign have been placing their electoral pos

poj2528 - Mayor's posters - 線段離散化(詳解)

Mayor's posters Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 79035   Accepted

HDU 1247 - Hat’s Words - [字典水題]

題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=1247 Problem DescriptionA hat’s word is a word in the dictionary that is the concatenation of exactly two o

BZOJ 世界

第一步首先建虛樹 第二步兩遍dfs,一次從葉子到根,一次從根到葉子,就可以得到虛樹中每個節點在M個詢問點中離他最近的是哪個(簡稱為控制點) 第三步考慮計算答案,對於整個樹,我們把節點化為三個種類   1.節點在虛樹的葉子節點的子樹中   2.節點在虛樹根節點之上的部分   3.節點在虛樹兩個節點之間