數據結構內排序之慘死攻略(一)

目錄

1 基本定義

2 插入排序

2.1 直接插入排序(Straight Insertion Sort)

2.1.1 栗子

2.1.2 代碼實現

2.1.3 算法分析

2.2 Shell排序

2.2.1 栗子——增量每次除以2遞減

2.2.2 代碼實現

2.2.3 算法分析

2.2.4 增量選擇

3 選擇排序

3.1 直接選擇排序

3.1.1 栗子

3.1.2 代碼實現

3.1.3 算法分析

3.2 堆排序

3.2.1 栗子

3.2.2代碼實現

3.2.3 算法分析

4 交換排序

4.1 冒泡排序

4.1.1 栗子

4.1.2 代碼實現

4.1.3 算法實現

4.2 快速排序

4.2.1 軸值的選擇(核心)

4.2.2 栗子

4.2.3 代碼實現

4.2.4 算法分析


1 基本定義

內排序:整個排序過程在內存中完成

排序的穩定性

  • 存在多個具有相同排序碼的記錄
  • 排序後這些記錄的相對次序保持不變

排序算法的時間代價:記錄的比較和移動次數

2 插入排序

《插入排序之舞》

2.1 直接插入排序(Straight Insertion Sort)

發撲克牌的時候我喜歡一邊摸牌,一邊理牌。這個過程就類似於直接插入排序。

基本操作:將一個記錄插入到已經排好序的有序表中,從而得到一個新的,記錄數+1的有序表。

2.1.1 栗子

本來有牌45

抽到了一張34,放45的前面

抽到了一張78,放45的後面

抽到了一張12,依次跟78,45,34比較,最後放在45的前面

其他同理

2.1.2 代碼實現

template<class Record>
void ImprovedInsertSort(Record Array[], int n){//Array爲待排數組,n爲數組長度
    Record TempRecord;    //臨時變量
    //將待排元素一個個取出,存到臨時變量裏
    for(int i=1; i<n; i++){
        TempRecord = Array[i];
        //記錄i往前與序列中的元素比較
        int j = i - 1;
        //將大於等於記錄i的記錄後移(j=-1時,即原序列無元素,記錄i爲第一個元素)
        while(j >= 0 && (TempTecord < Array[j])){
            Array[j+1] = Array[j];
            j = j - 1;
        }
        //此時j後面就是記錄i的位置
        Array[j+1] = TempRecord
    }
}

2.1.3 算法分析

2.2 Shell排序

《希爾排序之舞》

基於直接插入排序的兩個性質

  1. 序列本身有序的情況下,時間代價爲O(n)
  2. 對於短排序,直接插入排序比較有效

Shell排序算法思想

  1. 將序列轉化爲若干小序列,在這些小序列內進行插入排序
  2. 逐漸壯大小序列的規模,減少序列個數,使待排序序列逐漸處於更有序的狀態
  3. 最後對全體記錄進行一次直接插入排序

2.2.1 栗子——增量每次除以2遞減

選擇增量爲4的兩個元素爲一組(4 = 待排序列總數 / 2)

採用直接插入排序

選擇增量爲2的4個元素爲一組,採用直接插入排序

最後一輪,增量爲1,對全部記錄進行一次直接插入排序

2.2.2 代碼實現

template<class Record>
void ShellSort(Record Array[], int n){//Array[]爲待排序數組,n爲數組長度
    int i, delta;
    //增量delta每次除以2遞減,直到delta=1
    for(delta = n/2; delta>0; delta /=2)
        for(i = 0; i<delta; i++)
            //分別對delta個子序列進行插入排序
            //&傳Array[i]的地址,數組總長度爲n-i
            //i=0,數組長爲n,delta=n/2
            ModInsSort(&Array[i],n-i,delta);
            //如果增量序列不能保證最後一個delta間隔爲1
            //可以安排下面這個掃尾性質的插入排序
            //ModInsSort(Array, n, 1)
}

針對增量而修改的插入排序算法

template<class Record>
void ModInsSort(Record Array[], int n, int delta){
    int i, j;
    //對子序列中第i個記錄,尋找合適的插入位置
    //以下標爲delta的記錄開始處理,其前面的元素下標爲0
    for(i = delta; i < n; i += delta)
        //j以delta爲步長向前尋找逆序對進行調整
        for(j = i; j >= delta; j -= delta){
            if(Array[j] < Array[j-delta])//逆序對
                swap(Array,j,j-delta);//交換
            else break;
        }
}

2.2.3 算法分析

2.2.4 增量選擇

(1)增量不互質

選取的增量之間不互質,導致重複排序。如

45與34'第一輪在同一子序列裏

第二輪還在同一子序列裏

(2)增量互質

3 選擇排序

《選擇排序之舞》

3.1 直接選擇排序

依次選出剩下的未排序的記錄中的最小記錄。

過程類似於打擂臺。

3.1.1 栗子

第一回合

第一個站在擂臺上的是45,34去跟45打,34贏了。

78去跟34打,78輸了,目前站在擂臺的還是34。

12去跟34打,精彩,34輸了,12領先。

34'去跟12打,輸了。32去跟12打,輸了。29去跟12打,輸了。64去跟12打,還是輸。

第一回合,獲勝的是12!其他同理。

3.1.2 代碼實現

template<class Record>
void SelectSort(Record Array[], int n){
    //依次選出第i小的記錄,即剩餘記錄中最小的那個
    for(int i=0; i<n-1; i++){
        //首先假設記錄i就是最小的
        int smallest = i;
        //開始向後掃描所有剩餘記錄
        for(int j=i+1; j<n; j++)
            //如果發現更小的記錄,記錄它的位置
            if(Array[j] < Array[smallest])
                smallest = i;
        swap(Array,i,smallest);//將smallest賦值給Array[i] 
    }
}

3.1.3 算法分析

3.2 堆排序

基於最大堆來實現。而堆的本質是一棵用數組表示的完全二叉樹。

3.2.1 栗子

(1)建立最大堆

將已經存在的N個元素按最大堆的要求存放在一個一維數組中。

(2)找到最大值就將其刪除。即將堆裏的最後元素與堆頂元素交換

(3)最大值78出堆,堆的規模-1。剩餘的元素從根開始調整。

3.2.2代碼實現

template<class Record>
void sort(Record Array[], int n){
    int i;
    //建堆
    MaxHeap<Record> max_heap = MaxHeap<Record>(Array,n,n);
    //算法操作n-1次,最小元素不需要出堆
    for(i=0; i<n-1; i++)
        //依次找出剩餘記錄中的最大記錄,即堆頂
        max_heap.RemoveMax();
}

3.2.3 算法分析

此外可以改寫堆排序算法,發現逆序對直接交換。

4 交換排序

4.1 冒泡排序

不斷比較相鄰的記錄,不滿足排序要求的就交換相鄰記錄,直到所有記錄都已經排好序。

《冒泡排序之舞》

4.1.1 栗子

從最後開始。發現逆序對就交換。

發現逆序對32 29

交換位置

發現逆序對34’ 29

交換位置

......第一輪比較結束後,選出了最小元素12

直接選擇排序中沒有這麼多次的交換,站在擂臺上的數不一定是最小數。

4.1.2 代碼實現

template<class Record>
void BubbleSort(Record Array[], int n){
    bool NoSwap;            //用來標識是否發生了交換
    int i, j;
    for(i = 0; i<n-1; i++){//比較n-1輪
        NoSwap = true;      //標識初始爲true  
        for(j = n-1; j>i;j--){
            if(Array[j] < Array[j-1]){//判斷是否爲逆序對
                swap(Array,j,j-1);//交換逆序對
                NoSwap = false;        //發生了交換,標誌爲false
            }
        }
        if(NoSwap)        //這一輪的元素都是排好序的,沒發生交換,直接退出冒泡排序
            return;       
    }
}

4.1.3 算法實現

4.2 快速排序

《快速排序(Quicksort)學習筆記》

  • 選擇軸值pivot(核心)
  • 將序列劃分成兩個子序列L和R,使得L中所有記錄都小於或等於pivot,R中記錄都大於pivot
  • 對子序列L和R遞歸進行快速排序
  • 基於分治策略

4.2.1 軸值的選擇(核心)

儘可能使得L,R長度相等

  • 選擇首元素或尾元素作爲軸值(完全正序或完全逆序的輸入就會比較糟糕)
  • 隨機選擇
  • 選擇平均值(代價)

4.2.2 栗子

一次的分割過程

選擇32作爲軸值

然後把最右端的元素64填充進去,然後最右邊的位置就空出來了

接着從左邊開始看,將左邊的元素25與軸值32做比較。

25<32,不是逆序元素,i繼續往右走指向34,34>32,34填充到最右邊的位置。此時左邊空出了一個位置。

此時左邊空出了一個位置。移動右邊的j,從右邊找一個逆序元素29填入左邊的空位。

此時右邊空出了一個位置。從左邊找一個逆序元素45填入右邊的空位。

風水輪流轉,左邊有空位了。繼續移動右邊的j,從右邊找一個逆序元素12填入左邊的空位

最終

具體過程描述:

  • 選擇軸值並存儲軸值(上述例子取中間記錄作爲軸值)
  • 最後一個元素放到軸值位置
  • 初始化下標 i, j,分別指向頭尾
  • 遞增直到遇到比軸值大的元素,將此元素覆蓋到 j 的位置;j 遞減直到遇到比軸值小的元素,將此元素覆蓋到 i 的位置
  • 重複上一步直到 i==j,將軸值放到 i 的位置,完畢

4.2.3 代碼實現

template<class Record>
void QuickSort(Record Array[], int left, int right){
    //Array[]爲待排序數組,left,right分別爲數組兩端
    if(right <= left)    //如果只有0個或1個記錄,就不需要排序
        return;
    int pivot = SelectPivot(left,right);    //選擇軸值
    swap(Array,pivot,right);                //軸值放到數組末端
    pivot = Partition(Array,left,right);    //分割後軸值正確
    QuickSort(Array,left,pivot-1);          //左子序列遞歸快排
    QuickSort(Array,pivot+1,right);         //右子序列遞歸快排
}
int SelectPivot(int left,int right){
    //選擇軸值,參數left,right分別表示序列的左右端下標
    return(left+right)/2; //選中間記錄作爲軸值
}

分割函數

template<class Record>
int Partition(Record Array[],int left,int right){
    //分割函數,分割後軸值已到達正確位置
    int l = left;      //l爲左指針
    int r = right;     //r爲右指針
    Record TempRecord = Array[r];//保存軸值
    while(l != r){//l,r不斷向中間移動,直到相遇
        //l指針向右移動,直到找到一個大於軸值的記錄
        while(Array[l] <= TempRecord && r>l)
            l++;
        if(l<r)//未相遇,將逆置元素換到右邊空位
            Array[r] = Array[l];
            r--;  //r指針向左移動一步
        //r指針向左移動,直到找到一個小於軸值的記錄
        while(Array[r] >= TempRecord && r>l){
            r--;
        if(l < r){       //未相遇,將逆置元素換到左空位
            Array[l] = Array[r];
            l++;         //l指針向右移動一步
        }
    }//end while
    Array[l] = TempRecord;//將軸值回填到分界位置上
    return l;  //返回分界位置l
}

4.2.4 算法分析

你們以爲這就結束了?天真!

下面轉至《數據結構內排序之慘死攻略(二)》


學習自:

張銘《數據結構》

 

 

 

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