1. 程式人生 > >資料結構內排序之慘死攻略(二)

資料結構內排序之慘死攻略(二)

接上回合《資料結構內排序之慘死攻略(一)》

聽聞今天還要學資料結構,心中堵著一片烏雲。

就算受低潮情緒影響也要堅持學下去啊。

目錄

5 歸併排序

5.1 栗子

5.2 程式碼實現

5.3 歸併演算法優化

5.3.1 R.Sedgewick優化

5.3.2 R.Sedgewick程式碼實現

5.4 演算法分析

6 分配排序和索引排序

6.1 桶式排序

6.1.1 栗子

6.1.2 程式碼實現

6.1.3 演算法分析

6.2 基數排序

6.2.1 栗子1

6.2.2 栗子2

6.2.3 LSD(低位優先)-基於順序儲存

6.2.4 LSD(低位優先)-基於鏈式儲存

7 總結


5 歸併排序

簡化下高考一本,二本,專科分數線的劃分方式

5.1 栗子

  • 劃分為兩個子序列
  • 分別對每個子序列歸併排序
  • 有序子序列合併

5.2 程式碼實現

template<class Record>

void MergeSort(Record Array[],Record TempArray[],int left,int right){
    //Array為待排序陣列,left,right兩端
    int middle;
    if(left < right){//序列中只有0或1個記錄,不用排序
        middle = (left + right)/2;   //平分為兩個子序列
        //對左邊一半進行遞迴
        MergeSort(Array,TempArray,left,middle);
        //對右邊一半進行遞迴
        MergeSort(Array,TempArray,middle+1,right);
        Merge(Array,TempArray,left,right,middle);//歸併
    }

}

歸併函式

//兩個有序子序列都從左向右掃描,歸併到新陣列
template<class Record>

void Merge(Record Array[],Record TempArray[],int left,int right,int middle){
    int i,j,index1,index2;
    //將陣列暫存入臨時陣列
    for(j=left;j<=right;j++)
        TempArray[j] = Array[j];
    index1 = left;        //左邊子序列的起始位置
    index2 = middle + 1;  //右邊子序列的起始位置
    i = left;             //從左開始歸併
    
    while(index1 <= middle && index2 <= right){
        //取較小者插入合併陣列中
        if(TempArray[index1] <= TempArray[index2])
            Array[i++] = TempArray[index1++];
        else
            Array[i++] = TempArray[index2++];
 
    }
    while(index1 <= middle)  //只剩左序列,可直接複製
        Array[i++] = TempArray[index1++];
    while(index2 <= right)   //與上個迴圈互斥,複製右序列
        Array[i++] = TempArray[index2++];

}

5.3 歸併演算法優化

  1. 對基本已排序的序列進行直接插入排序,小序列不遞迴
  2. R.Sedgewick優化:拆分方式不變,歸併時從兩端開始處理,向中間推進,簡化邊界判斷

5.3.1 R.Sedgewick優化

優化前

優化後

還是看不懂?沒事,接著解析。先對左邊子序列進行歸併

歸併時應該為

此時對右邊子序列32,45進行倒置

接著

對右邊子序列進行歸併

對右邊的子序列34',64進行倒置

........剩下步驟同上,最終為12   34’     64  78

繼續合併

右邊子序列進行倒置

.......最終

5.3.2 R.Sedgewick程式碼實現

template<class Record>

void ModMergeSort(Record Array[],Record TempArray[],int left,int right){
    //Array為待排序陣列,left,right指向兩端
    int middle;
    if(right-left+1 > THRESHOLD){//長序列遞迴,threshold閾值,臨界點
        middle = (left + right)/2;
        ModMergeSort(Array,TempArray,left,middle);//左
        ModMergeSort(Array,TempArray,middle+1,right);//右
        //對相鄰的有序序列進行歸併
        ModMerge(Array,TempArray,left,right,middle);//歸併 
    }
    else InsertSort(&Array[left],right-left+1);//小序列插入排序
}

優化的歸併函式

template<class Record> void ModMerge(
Record Array[],Record TempArray[],int left,int right,int middle){
    int index1,index2//兩個子序列的起始位置
    int i,j,k;
    for(i = left; i <= middle; i++)
        TempArray[i] = Array[i];  //複製左邊的子序列
    for(j = 1; j <= right-middle; j++)  //顛倒複製右序列
        TempArray[right-j+1] = Array[j+middle];
    for(index=left,index2=right,k=left; k<=right; k++)
        if(TempArray[index1] <= TempArray[index2])
            Array[k] = TempArray[index1++];
        else
            Array[k] = TempArray[index2--];
}

 

5.4 演算法分析

漫畫亂入,自娛自樂。

6 分配排序和索引排序

  • 不需要進行記錄間的兩兩比較
  • 需要事先知道記錄序列的一些具體情況,關鍵碼的分佈

6.1 桶式排序

  • 事先知道序列中的記錄都位於某個小區間段[0,m]內
  • 將具有相同值的記錄都分配到同一個桶中,再依次按照編號從桶中取出記錄,組成一個有序序列

6.1.1 栗子

6.1.2 程式碼實現

template<class Record> void BucketSort(Record Array[],int n,int max){
    Record *TempArray = new Record[n]; //臨時陣列
    int *count = new int[max]; //桶容量計數器
    int i;
    for(i=0; i<n; i++)         //把序列複製到臨時陣列
        TempArray[i] = [i];   
    for(i=0; i<max; i++)       //所有計數器初始置為0
        count[i] = 0;
    for(i=0; i<n; i++)         //統計每個取值出現的次數
        count[Array[i]]++;
    for(i=0; i<max; i++)       //統計小於等於i的元素的個數
        count[i] = count[i-1] + count[i];  //c[i]記錄i+1的起址
    for(i=n-1; i>=0; i--)       //區域性開始,保證穩定性
        Array[--count][TempArray[i]] = TempArray[i];
}

6.1.3 演算法分析

6.2 基數排序

桶式排序只適合m很小的情況。

基數排序:當m很大時,可以將一個記錄的值分為多個部分來比較。

還是直接看例子吧,概念神馬的先再見。

6.2.1 栗子1


6.2.2 栗子2


6.2.3 LSD(低位優先)-基於順序儲存

原始輸入陣列 R 的長度為 n,基數為 r,排序碼個數為 d

程式碼實現

template <class Record>

void RadixSort(Record Array[], int n, int d, int r) {
    Record *TempArray = new Record[n];
    int *count = new int[r]; 
    int i, j, k;
    int Radix = 1; // 模進位,用於取Array[j]的第i位
    for (i = 1; i <= d; i++) { 
        // 對第 i 個排序碼分配 
        for (j = 0; j < r; j++) 
            count[j] = 0;   // 初始計數器均為0 
        for (j = 0; j < n; j++) { // 統計每桶記錄數 
            k = (Array[j] / Radix) % r; // 取第i位 
            count[k]++; // 相應計數器加1 
         }
        for (j = 1; j < r; j++) // 給桶劃分下標界 
            count[j] = count[j-1] + count[j]; 
        for (j = n-1; j >= 0; j--) { // 從陣列尾部收集 
            k = (Array[j] / Radix ) % r; // 取第 i 位 
            count[k]--; // 桶剩餘量計數器減1 
            TempArray[count[k]] = Array[j]; // 入桶 
        } 
        for (j = 0; j < n; j++) // 內容複製回 
            Array 中 Array[j] = TempArray[j]; 
        Radix *= r; // 修改模Radix 
     } 
}

演算法分析

6.2.4 LSD(低位優先)-基於鏈式儲存

原始輸入陣列 R 的長度為 n,基數為 r,排序碼個數為 d

鏈式儲存避免了空間浪費情況

程式碼實現(後續補)

演算法分析

7 總結

 

4) 元素的移動次數與關鍵字的初始排列次序無關:基數排序

    元素的移動次數與關鍵字的初始排列次序有關:直接插入排序,氣泡排序,快速排序

5) 快速排序每一躺結束後都將至少一個元素放在最終位置。

 

內排序暫時告一段落了,然而仙氣已經耗竭。


學習自:

張銘《資料結構》

程傑《大話資料結構》

陳越,何欽銘《資料結構》

 

附加:排序演算法的舞蹈

氣泡排序:http://t.cn/hrf58M

希爾排序:http://t.cn/hrosvb

選擇排序:http://t.cn/hros6e

插入排序:http://t.cn/hros0W

快速排序:http://t.cn/ScTA1d

歸併排序:http://t.cn/Sc1cGZ