1. 程式人生 > 實用技巧 >第八章學習心得

第八章學習心得

排序

內部排序

插入排序

一、直接插入排序

(一)方法

1.設待排序的記錄存放在陣列r[1...n]中,r[1]是一個有序序列

2.迴圈n-1次,每次使用順序查詢法,查詢r[i](i = 2,...,n)在已排好序的序列r[1...i-1]中的插入位置,然後將r[i]插入表長為i-1的有序序列r[1...i-1],直到將r[n]插入表長為n-1的有序序列r[1...n-1],最後得到一個表長為n的有序序列

(二)程式碼

void InsertSort(SqList &L)
{
    for(i = 2;i <= L.length;++i)
        if(L.r[i].key < L.r[i-1
].key)//”<"需將r[i]插入有序子表 { L.r[0] = L.r[i]; //將插入的記錄暫存到監視哨中 L.r[0] = L.r[i-1]; //r[i-1] 後移 for(j = i - 2;L.r[0].key < L.r[0].key;--j) //從後往前尋找插入位置 L.r[j+1] = L.r[j]; //記錄逐個後移,直到找到插入位置 L.r[j+1] = L.r[0]; //將r[0]即原r[i],插入到正確位置
} }
View Code

(三)時間複雜度為O(n2)

(四)空間複雜度為O(1)

(五)演算法特點

1.穩定排序

2.演算法簡便,且容易實現

3.也適用於鏈式結構,只是在單鏈表上無需移動記錄,只需修改相應的指標

4.更適合於初始記錄基本有序(正序)的情況,當初始記錄無序,n較大時,次演算法時間複雜度較高,不宜採用

二、折半插入排序

(一)方法

1.設待排序的記錄存放在陣列r[1...n]中,r[1]是一個有序序列

2.迴圈n-1次,每次使用折半查詢法,查詢r[i](i = 2,...,n)在已排好序的序列r[1...i-1]中的插入位置,然後將r[i]插入表長為i-1的有序序列r[1...i-1],直到將r[n]插入表長為n-1的有序序列r[1...n-1],最後得到一個表長為n的有序序列

(二)程式碼

void BInsertSort(SqList &L)
{
    for(i = 2;i <= L.length;++i)
    {
        L.r[0] = L.r[i];    //將待插入的記錄暫存到監視哨中
        low = 1;high = i- 1;    //置查詢區間初值
        while(low <= high)
        {
            m = (low + high)/2;    //折半
            if(L.r[0].key < L.r[m].key)    high = m-1;    //插入點在前一子表
            else low = m + 1;    //插入點在後一子表
        }
    for(j = i-1;j >= high+1;--j)
        L.r[j+1] = L.r[j];    //記錄後移
    L.r[high+1] = L.r[0];    //將r[0]即原r[i],插入到正確位置
    }
}
View Code

(三)時間複雜度為O(n2)

(四)空間複雜度為O(1)

(五)演算法特點

1.穩定排序

2.只能用於順序結構,不能用於鏈式結構

3.適合初始記錄無序,n較大時的情況

三、希爾排序

(一)方法

1.第一趟取增量d1(d1<n)把全部記錄分成d1個組,所有間隔為d1的記錄分在同一組,在各個組中進行直接插入排序

2.第二趟取增量d2(d2<d1),重複上述的分組和排序

3.依次類推,直到所取的增量dt=1(dt<dt-1<...<d2<d1),所有記錄在同一組中進行直接插入排序為止

(二)程式碼

void ShellInsert(SqList &L,int dk)
{
    for(i = dk+1;i <= L.length;++i)
    {
        if(L.r[i].key < L.r[i-dk].key)    //需將L.r[i]插入有序增量子表
        {
            L.r[0] = L.r[i];    //暫存在L.r[0]
            for(j = i-dk;j>0&&L.r[0].key < L.r[j].key;j-=dk)
                L.r[j+dk] = L.r[j];    //記錄後移,直到找到插入位置
            L.r[j+dk] = L.r[0];    //將r[0]即原r[i],插入到正確位置
        }
}

void ShellSort(SqList &L,int dt[],int t)
{
    for(k = 0;k < t;++k)
        ShellInsert(L,dt[k]);    //一趟增量為dt[t]的希爾插入排序
}
View Code

(三)時間複雜度為n(log2n)2

(四)空間複雜度為O(1)

(五)演算法特點

1.不穩定的

2.只能用於順序結構

3.增量序列可以有各種取法,但應該使增量序列中的值沒有除1之外的公因子,並且最後一個增量值必須等於1

4.記錄總的比較次數和移動次數都比直接插入排序要少,n越大時,效果越明顯。所以適合初始記錄無序、n較大時的情況

交換排序

一、氣泡排序

(一)方法

1.設待排序的記錄存放在陣列r[1...n]中。首先將第一個記錄的關鍵字和第二個記錄的關鍵字進行比較,若為逆序(即L.r[1].key>L.r[2].key),則交換兩個記錄。然後比較第二個記錄和第三個記錄的關鍵字。以此類推,直至n-1個記錄和第n個記錄進行過比較為止。上述過程稱作第一趟起泡排序,其結果使得關鍵字最大的記錄被安置到最後一個記錄的位置上

2.然後進行第二趟起泡排序,對前n-1個記錄進行同樣操作,其結果是使關鍵字次大的記錄被安置到第n-1個記錄的位置上

3.重複上述比較和交換過程,第i趟是從L.r[1]到L.r[n-i+1]依次比較相鄰兩個記錄的關鍵字,並在“逆序”時交換相鄰記錄,其結果是這n-i+1個記錄中關鍵字最大的記錄被交換到第n-i+1的位置上。直到在某一趟排序過程中沒有進行過交換記錄的操作,說明序列已全部達到排序要求,則完成排序。

(二)程式碼

void BubbleSort(SqList &L)
{
    m = L.length - 1;
    flag = 1;    //flag用來標記某一趟排序是否發生交換
    while((m>0)&&(flag == 1))
    {    
        flag = 0;    //flag置為0,如果本趟排序沒有發生交換,則不會執行下一趟排序
        for(j = 1;j <= m;j++)
            if(L.r[j].key > L.r[j+1].key)
            {
                flag = 1;    //flag置為1,表示本趟排序發生了交換
                t = L.r[j];L.r[j] = L.r[j+1];L.r[j+1] = t;    //交換前後兩個記錄
            }
        --m;
    }
}
View Code

(三)時間複雜度為O(n2)

(四)空間複雜度為O(1)

(五)演算法特點

1.穩定排序

2.可用於鏈式儲存結構

3.移動記錄次數較多,演算法平均時間效能比直接插入排序差。當初始記錄無序,n較大時,此演算法不宜採用

二、快速排序

(一)方法

1.選擇待排序表中的第一個記錄作為樞軸,將數週記錄暫存r[0]的位置上。附設兩個指標low和high,初始時分別指向表的上街和下界(第一趟時,low = 1,high = L。length)

2.從表的最右側位置依次向左搜尋,找到第一個關鍵小於數週關鍵字pivotkey的記錄,將其移到low處。具體操作:當low<high時,若high所指向的關鍵字大於等於pivotkey,則向左移動指標high(執行操作--high);否則將high所指記錄移到low所指記錄

3.然後再從表的最左側位置,依次向右搜尋找到第一個關鍵字大於pivotkey的記錄和樞軸記錄交換。具體操作時:當low<high時,若low所指記錄的關鍵字小於等於pivotkey,則向右移動指標low(執行操作++low);否則將low所指記錄與數週記錄交換

4.重複步驟2和3,直至low和high相等為止。此時low和high的位置即為樞軸在此趟排序中的最終位置,原表被分成兩個子表

(二)程式碼

int Partition(SqList &L,int low,int high)
{
    L.r[0] = L.r[low];    //用子表的第一個記錄做樞軸記錄
    pivotkey = L.r[low].key;    //樞軸記錄關鍵字保持在pivotkey中
    while(low<high)
    {
        while(low<high && L.r[high].key >= pivotkey)    --high;
        L.r[low] = L.r[high];    //將比樞軸記錄小的記錄移到低端
        while(low<high && L.r[low].key<=pivotkey)    ++low;
        L.r[high] = L.r[low];    //將比樞軸記錄大的記錄移到高階
    }
    L.r[low] = L.r[0];    //樞軸記錄到位
    return low;    //返回樞軸位置
}

void QSort(SqList &L,int low,int high)
{
    if(low < high)
    {
        pivotloc = Partition(L,low,high);    //將L.r[low...high]一分為二,pivotloc是樞軸位置
        QSort(L,low,pivotloc-1);    //對左子表遞迴排序
        QSort(L,pivotloc+1,high);    //對右子表遞迴排序
    }
}

void QuickSort(SqList &L)
{
    QSort(L,1,L.length);
}
View Code

(三)時間複雜度為O(nlog2n)

(四)空間複雜度為O(n)

(五)演算法特點

1.不穩定排序

2.排序過程中需要定位表的上界和下界,所以適合用於順序結構,很難用於鏈式結構

3.當n較大時,在平均情況下快速排序時所有內部排序方法中速度最塊的一種,所以其適合初始記錄無序、n較大時的情況

選擇排序

一、簡單選擇排序

(一)方法

1.設待排序的記錄存放在陣列r[1...n]中。第一趟從r[1]開始,通過n-1次比較,從n個記錄中選出關鍵字最小的記錄,記為r[k],交換r[1]和r[k]

2.第二趟從r[2]開始,通過n-2次比較,從n-1個記錄中選出關鍵字最小的記錄,記為r[k],交換r[2]和r[k]

3.以此類推,第i趟從r[i]開始,通過n-i次比較,從n-i+1個記錄中選出關鍵字最小的記錄,記為r[k],交換r[i]和r[k]

4.經過n-1趟,排序完成

(二)程式碼

void SelectSort(SqList &L)
{
    for(i = 1;i < L.length;i++)
    {//在L.r[i...L.length]中選擇關鍵字最小的記錄
        k = i;
        for(j = i+1;j <= L.length;++j)
            if(L.r[j].key < L.r[k].key)    k = j;//k指向此趟排序中關鍵字最小的記錄
        if(k != i)
        {//交換r[i]和r[k]
            t = L.r[i];
            L.r[i] = L.r[k];
            L.r[k] = t;
        }
    }
}
View Code

(三)時間複雜度為O(n2)

(四)空間複雜度為O(1)

(五)演算法特點

1.穩定排序

2.可用於鏈式儲存結構

3.激動記錄次數較少,當每一記錄佔用的空間較多時,次方法比直接插入排序快

二、堆排序

(一)方法

1.按對的定義將待排序序列r[1...n]調整為大根堆(這個過程稱為建初堆),交換r[1]和r[n],則r[n]為關鍵字最大的記錄

2.將r[1...n-1]重新調整為堆,交換r[1]和r[n-1],則r[n-1]為關鍵字次大的記錄

3.迴圈n-1次,直到交換了r[1]和r[2]為止,得到了一個非遞減的有序序列r[1...n]

(二)程式碼

void HeapAdjust(SqList &L,int s,int m)
{
    rc = L.r[s];
    for(j = 2*s;j <= m;j *= 2)    //沿key較大的孩子結點向下篩選
    {
        if(j<m && L.r[j].key<L.r[j+1].key)    ++j;    //j為key較大的記錄的下標
        if(rc.key >= L.r[j].key)    break;    //rc應插入在位置s上
        L.r[s] = L.r[j];
        s = j;
    }
    L.r[s] = rc;    //插入
}

void CreatHeap(SqList &L)
{//把無序序列L.r[1...n]建成大根堆
    n = L.length;
    for(i = n/2;i > 0;--i)
        HeapAdjust(L,i,n);    //反覆呼叫HeapAdjust
}

void HeapSort(SqList &L)
{//堆順序表L進行堆排序
    CreatHeap(L);    //把無序序列L.r[1...n]建成大根堆
    for(i = L.length;i > 1;--i)
    {
        x = L.r[1];    //將堆頂記錄和當前未經排序子序列L.r[1...i]中最後一個記錄互換
        L.r[1] = L.r[i];
        L.r[i] = x;
        HeapAdjust(L,1,i-1);    //將L.r[1...i-1]重新調整為大根堆
    }
}
View Code

(三)時間複雜度為O(nlog2n)

(四)空間複雜度為O(1)

(五)演算法特點

1.不穩定排序

2.只能用於順序結構,不能用於鏈式結構

3.初始建堆所需的比較次數較多,因此記錄較少時不宜採用,因為當記錄較多時較快速排序更高效

歸併排序

(一)方法:

假設初始序列含有n個記錄,則可看成時n個有序的子序列,每個子序列的長度為1,然後涼涼歸併,得到[n/2]個長度為2或1的有序子序列;再兩兩歸併,……,如此重複,直至得到一個長度為n的有序序列為止

(二)程式碼

void Merge(RedType R[],RedType &T[],int low,int high)
{//將有序表R[low...high]和R[mid+1...high]歸併為有序表T[low...high]
    i = low;
    j = mid + 1;
    k = low;
    while(i<=mid && j<=high)
    {   
        if(R[i].key <= R[i].key)    T[k] = R[i++];
        else T[k] = R[j++];
    }
    while(i <= mid)    T[k++] = R[i++];    //將剩餘的R[low...mid]複製到T中
    while(j <= high)    T[k++] = R[j++];    //將剩餘的R[j...high]複製到T中
}

void MSort(RedType R[],RedType &T[],int low,int high)
{//R[low...high]歸併排序後放入T[low...high]中
    if(low == high)    T[low] = R[low];
     else
    {
        mid = (low + high)/2;    //將當前序列一分為二,求分裂點mid
        MSort(R,S,low,mid);    //對子序列R[low...mid]遞迴歸併排序,結果放入S[low...mid]
        MSort(R,S,mid+1,high);    //對子序列R[mid+1...high]遞迴歸併排序,結果放入S[mid+1...high]
        Merge(S,T,low,mid,high);    //將S[low...mid]和S[mid+1...high]歸併到T[low...high]
    }
}

void MergeSort(SqList &L)
{
    MSort(L.r,L.r,1,L.length);
}
View Code

(三)時間複雜度為O(nlog2n)

(四)空間複雜度為O(n)

(五)演算法特點

1.穩定排序

2.可用於鏈式結構,且不需要附加儲存空間,但遞迴是現實仍需開闢相應的遞迴工作棧

外部排序

(一)方法

1.按可用記憶體大小,將外存上含n個記錄的檔案分成若干長度為l的子檔案或段,依次讀入記憶體並利用有效的內部排序方法對它們進行排序,並將排序後得到的有序子檔案重新寫入外存,通常稱這些有序子檔案為歸併段或順串

2.對這些歸併段進行逐趟歸併,使歸併段(有序的子檔案)逐漸由小至大,直至得到整個有序檔案為止