1. 程式人生 > >幾種排序方法詳解(選擇排序、氣泡排序、插入排序、快速排序)

幾種排序方法詳解(選擇排序、氣泡排序、插入排序、快速排序)

由於本帖只是闡述幾種排序方法的原理、如何區分以及編寫幾種排序的簡單程式碼,所以直接給定陣列是 a[ ]={6,2,8,5,1},需要把以上5個數字按升序排列

1. 選擇排序法

(如果不想看解釋分析,直接往後拉看程式碼)

實質

第一輪:通過對比陣列中前一個元素和後一個元素的大小,先找到陣列中最小的數字,並且記錄它的下標。如果標記的下標不是第一個元素的下標,則交換兩個元素

第二輪從第二個元素開始(因為第一個元素已經是第一輪排好的“有序”元素),對比陣列中前一個元素和後一個元素的大小,遍歷尋找陣列中第二小的數字,並記錄它的下標。如果標記的下標不是第二個元素的下標,則交換兩個元素。

第三輪從第三個元素開始(因為第一和第二個元素已經是第一、二輪排好的“有序”元素),對比陣列中前一個元素和後一個元素的大小,遍歷尋找陣列中第三小的數字,並記錄它的下標。如果標記的下標不是第三個元素的下標,則交換兩個元素。

第四輪從第四個元素開始(因為第一、第二、第三個元素已經是第一、二、三輪排好的“有序”元素),對比陣列中前一個元素和後一個元素的大小,遍歷尋找陣列中第四小的數字,並記錄它的下標。如果標記的下標不是第四個元素的下標,則交換兩個元素。

第五輪沒有第五輪(因為一共就五個數,排好四個數,自然就全部排好了,再多排一輪浪費了)

形象排序

原始資料:  |6   2   8   5   1
第一輪:     1  |2   8   5   6
第二輪:     1   2  |8   5   6
第三輪:     1   2   5  |8   6
第四輪:     1   2   5   6  |8

具體解釋
先假設第一個數字6為最小的數字,那麼記錄它的下標(index=0)

第一輪中:
第一次:先比較6和2(即標記的數和後面一個元素比較),發現2更小,則記錄2的下標(index=1)
第二次:比較2和8(標記的數和後面一個元素比較),發現還是2小,則下標還是2的下標不變(index=1)
第三次:比較2和5(標記的數和更後面的數比較),發現還是2小,則下標還是2的下標不變(index=1)
第四次:比較2和1(標記的數和更後面的數比較),發現1比2小,則標記改為1的下標(index=4)
最後:index並不等於第一個元素的下標(0),則交換第一個元素和被標記的元素

第一個元素已經有序

,則從第二個元素開始(並且把標記index初始化為1,代表第二個元素2)
第二輪中:
第一次:先比較2和8,還是2小,則下標還是2的下標不變(index=1)
第二次:比較2和5,還是2小,則下標還是2的下標不變(index=1)
第三次:比較2和6,還是2小,則下標還是2的下標不變(index=1)
最後:index等於第二個元素的下標(1),則不用交換第二個元素和被標記的元素

第一、二個元素(1和2)已經有序,則從第三個元素開始(並且把標記index初始化為2,代表第三個元素8)
第三輪中:
第一次:先比較8和5,發現5比8小,則標記改為5的下標(index=3)
第二次:比較5和6,還是5小,則下標還是5的下標不變(index=3)
最後:index並不等於第三個元素的下標(2),則交換第三個元素和被標記的元素

第一、二、三個元素(1和2和5)已經有序,則從第四個元素開始(並且把標記index初始化為3,代表第四個元素8)
第四輪中:
第一次:先比較8和6,發現6比8小,則標記改為6的下標(index=4)
最後:index並不等於第四個元素的下標(3),則交換第四個元素和被標記的元素

程式碼1(每輪都輸出,看怎麼變化):

#include <iostream>
using namespace std;

void swap(int &a,int &b);//宣告一個交換函式

int main()
{
    int a[]={6,2,8,5,1};
    for(int i=0;i<5;++i)
        cout<<a[i];//列印原陣列
    cout<<endl<<endl;//空一行

    for(int i=0;i<4;++i)//只要進行4輪比較就可以了
    {
        int index=i;//執行到第幾輪就初始化index為第幾個元素的下標
        for(int j=i+1;j<5;++j)//從被標記的後一個數開始遍歷
            if(a[index]>a[j])//找到最小元素的下標
                index=j;
        if(index!=i) swap(a[index],a[i]);//交換

        for(int k=0;k<5;++k)
            cout<<a[k];
        cout<<endl;
    }
    return 0;
}

void swap(int &a,int &b)//交換函式的定義
{
    int t;
    t=a;
    a=b;
    b=t;
}

程式碼2(排序完再輸出,直接看結果):

#include <iostream>
using namespace std;

void swap(int &a,int &b){int t=a;a=b;b=t;};//想縮短程式碼,直接宣告和定義合在一起了

int main()
{
    int a[]={6,2,8,5,1};
    for(int i=0;i<5;++i)
        cout<<a[i];//列印原陣列
    cout<<endl<<endl;//空一行

    for(int i=0;i<4;++i)//只要進行4輪比較就可以了
    {
        int index=i;//執行到第幾輪就初始化index為第幾個元素的下標
        for(int j=i+1;j<5;++j)//從被標記的後一個數開始遍歷
            if(a[index]>a[j])//找到最小元素的下標
                index=j;
        if(index!=i) swap(a[index],a[i]);//交換
    }
    for(int k=0;k<5;++k)
        cout<<a[k];
    cout<<endl;
    return 0;
}

2. 氣泡排序法

原理

  1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  2. 每一輪對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。第一輪結束,最後的元素應該會是最大的數。
  3. 針對所有的元素重複以上的步驟n-1輪,分別倒序排好倒數第二大、倒數第三大……的元素。直到沒有任何一對數字需要比較。

形象排序

原始資料:  6   2   8   5   1
第一輪:    2   6   5   1  |8
第二輪:    2   5   1  |6   8
第三輪:    2   1  |5   6   8
第四輪:    1  |2   5   6   8

具體解釋

第一輪中:
第一次:比較第一個和第二個元素(即6和2),發現6比2大,則交換6和2(目前數字排列為 2 6 8 5 1)
第二次:比較第二個和第三個元素(即6和8),發現6比8小,則保持原樣(目前數字排列為 2 6 8 5 1)
第三次:比較第三個和第四個元素(即8和5),發現8比5大,則交換8和5(目前數字排列為 2 6 5 8 1)
第四次:比較第四個和第五個元素(即8和1),發現8比1大,則交換8和1(目前數字排列為 2 6 5 1 8)
最後:這樣,第一輪就把最大的元素放到了最右邊

最後一個元素已經有序,則第二輪不用比較最後一個元素和倒數第二個元素的大小(目前數字排列為 2 6 5 1 |8)
第二輪中:
第一次:比較第一個和第二個元素(即2和6),發現2比6小,則保持原樣(目前數字排列為 2 6 5 1 |8)
第二次:比較第二個和第三個元素(即6和5),發現6比5大,則交換6和5(目前數字排列為 2 5 6 1 |8)
第三次:比較第三個和第四個元素(即6和1),發現6比1大,則交換6和1(目前數字排列為 2 5 1 6 |8)
最後:這樣,第二輪就把倒數第二大的元素放到了倒數第二的位置

倒數兩個元素已經有序,則第三輪不用比較倒數第三個元素和倒數第二個元素的大小(目前數字排列為2 5 1 |6 8)
第三輪中:
第一次:比較第一個和第二個元素(即2和5),發現2比5小,則保持原樣(目前數字排列為 2 5 1 |6 8)
第二次:比較第二個和第三個元素(即5和1),發現5比1大,則交換5和1(目前數字排列為 2 1 5 |6 8)
最後:這樣,第三輪就把倒數第三大的元素放到了倒數第三的位置

倒數三個元素已經有序,則第四輪不用比較倒數第四個元素和倒數第三個元素的大小(目前數字排列為2 1 |5 6 8)
第四輪中:
第一次:比較第一個和第二個元素(即2和1),發現2比1大,則交換2和1(目前數字排列為 1 2 |5 6 8)
最後:這樣,第四輪就把倒數第四大的元素放到了倒數第四的位置,所有的元素就按升序排好了

程式碼1(每輪都輸出,看怎麼變化):

#include <iostream>
using namespace std;

void swap(int &a,int &b);//宣告一個交換函式

int main()
{
    int a[]={6,2,8,5,1};
    for(int i=0;i<5;++i)
        cout<<a[i];//列印原陣列
    cout<<endl<<endl;//空一行

    for(int i=0;i<4;++i)//只要進行4輪比較就可以了
    {
        for(int j=0;j<5-i-1;++j)//每輪進行5-i-1次比較
            if(a[j]>a[j+1]) swap(a[j],a[j+1]);//如果前一個比後一個大就交換

        for(int k=0;k<5;++k)
            cout<<a[k];
        cout<<endl;
    }
    return 0;
}

void swap(int &a,int &b)//交換函式的定義
{
    int t;
    t=a;
    a=b;
    b=t;
}

程式碼2(排序完再輸出,直接看結果):

#include <iostream>
using namespace std;

void swap(int &a,int &b){int t=a;a=b;b=t;};

int main()
{
    int a[]={6,2,8,5,1};
    for(int i=0;i<5;++i)
        cout<<a[i];//列印原陣列
    cout<<endl<<endl;//空一行

    for(int i=0;i<4;++i)//只要進行4輪比較就可以了
    {
        for(int j=0;j<5-i-1;++j)//每輪進行5-i-1次比較
            if(a[j]>a[j+1]) swap(a[j],a[j+1]);//如果前一個比後一個大就交換
    }
    
    for(int k=0;k<5;++k)
        cout<<a[k];
    cout<<endl;
    return 0;
}

3. 插入排序法

原理
插入排序法通過把陣列中的元素插入到適當的位置來進行排序:

  1. 先假設第一個元素有序,則第一輪從第二個元素開始,作為待插入的數,往前依次比較,看往哪裡插
  2. 第二輪把下一個元素(第三個)插入到其對應於已排序元素的排序位置
  3. 對於陣列中的每個元素重複2步驟。即把第四個元素插入到適當位置,然後是第5個元素,等等。

形象排序

原始資料:  6|  2   8   5   1
第一輪:    2   6|  8   5   1
第二輪:    2   6   8|  5   1
第三輪:    2   5   6   8|  1
第四輪:    1   2   5   6   8|

具體解釋

假設第一個元素6是有序的,並且定義待插入的數int inserter=a[i],和定義下標index=i-1,用此下標來讓插入點與對應數比較

因為第一個數假設是有序的,則從第二個數開始作為待插入的數(inserter=a[1])
第一輪中:
第一次:把inserter與第一個元素比較(即2與6),發現2比6小,則把第一個元素後挪一個位置(目前數字排列為 6 6| 8 5 1)
最後:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 2 6| 8 5 1)

前面兩個元素已經有序,則第二輪把第三個元素插到有序元素中的適當位置,則實現前三個元素有序(目前數字排列為 2 6| 8 5 1)
第二輪中:(儲存第三個元素inserter=a[2])
第一次:把inserter與第二個元素比較(即8與6),發現8比6大,則把第二個元素不做後挪(目前數字排列為 2 6 8| 5 1)
第二次:由於8比6大,所以肯定比2大,所以不需要再比了
最後:把inserter中保留的待插入的數插入到相應位置(對於本題,則還是原位置)(目前數字排列為 2 6 8| 5 1)

前面三個元素已經有序,則第三輪把第四個元素插到有序元素中的適當位置,則實現前四個元素有序(目前數字排列為 2 6 8| 5 1)
第三輪中:(儲存第四個元素inserter=a[3])
第一次:把inserter與第三個元素比較(即5與8),發現5比8小,則把第三個元素後挪一個位置(目前數字排列為 2 6 8 8| 1)
第二次:把inserter與第二個元素比較(即5與6),發現5比6小,則把第二個元素後挪一個位置(目前數字排列為 2 6 6 8| 1)
第三次:把inserter與第一個元素比較(即5與2),發現5比2大,則把第一個元素不做後挪(目前數字排列為 2 6 6 8| 1)
最後:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 2 5 6 8| 1)

前面四個元素已經有序,則第四輪把第五個元素插到有序元素中的適當位置,則實現前五個元素有序(目前數字排列為 2 5 6 8| 1)
第五輪中:(儲存第五個元素inserter=a[4])
第一次:把inserter與第四個元素比較(即1與8),發現1比8小,則把第四個元素後挪一個位置(目前數字排列為 2 5 6 8 8|)
第二次:把inserter與第三個元素比較(即1與6),發現1比6小,則把第三個元素後挪一個位置(目前數字排列為 2 5 6 6 8|)
第三次:把inserter與第二個元素比較(即1與5),發現1比5小,則把第二個元素後挪一個位置(目前數字排列為 2 5 5 6 8|)
第四次:把inserter與第一個元素比較(即1與2),發現1比2小,則把第一個元素後挪一個位置(目前數字排列為 2 2 5 6 8|)
最後:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 1 2 5 6 8|),完成排序

程式碼1(每輪都輸出,看怎麼變化):

#include <iostream>
using namespace std;

int main()
{
    int a[]={6,2,8,5,1};
    for(int i=0;i<5;++i)
        cout<<a[i];//列印原陣列
    cout<<endl<<endl;//空一行

    for(int i=1;i<5;++i)//只要進行4輪比較就可以了
    {
        int inserter=a[i];
        int index=i-1;//插入點初始化是inserter前面的一個元素
        while(index>=0 && inserter<a[index])//尋找插入點
        {
            a[index+1]=a[index];
            --index;//符合插入條件,插入點在往前移
        }
        a[index+1]=inserter;//插入

        for(int k=0;k<5;++k)
            cout<<a[k];
        cout<<endl;
    }
    return 0;
}

程式碼2(排序完再輸出,直接看結果):

#include <iostream>
using namespace std;

int main()
{
    int a[]={6,2,8,5,1};
    for(int i=0;i<5;++i)
        cout<<a[i];//列印原陣列
    cout<<endl<<endl;//空一行

    for(int i=1;i<5;++i)//只要進行4輪比較就可以了
    {
        int inserter=a[i];
        int index=i-1;//插入點初始化是inserter前面的一個元素
        while(index>=0 && inserter<a[index])//尋找插入點
        {
            a[index+1]=a[index];
            --index;//符合插入條件,插入點在往前移
        }
        a[index+1]=inserter;//插入
    }
    for(int k=0;k<5;++k)
        cout<<a[k];
    cout<<endl;
    return 0;
}

4. 快速排序法

原理
通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。

  1. 先假設第一個元素為軸值,自右向左找一個比軸值小的數交換,再自左向右找一個比軸值大的數交換,再重複自右向左找一個比軸值小的數交換,自左向右找一個比軸值大的數交換,直到軸值左邊沒有比其大的數存在,右邊也沒有比其小的數存在,則第一輪結束。原來的一組資料被劃分為以軸值為界的兩組新資料
  2. 第二輪:取上一輪軸值左邊的一組新資料,重複1的操作;取上一輪軸值右邊的一組新資料,重複1的操作,則把最初的一組資料分成了四部分,這樣便產生一個遞迴的思想
  3. 一直重複操作,直到資料被分的不可再分為止。

形象排序

原始資料: |6|  2   8   5   1
第一輪:   |1|  2   5 | 6  |8|
第二輪:    1 ||2|  5 | 6 | 8
第三輪:    1 | 2 | 5 | 6 | 8

具體解釋
第一輪中:(先假設第一個元素6為軸值
第一次自右向左找一個比軸值(即6)小的數交換,正巧右邊的第一個數就比6小,則交換6和1(目前數字排列為 1 2 8 5 6)
第二次自左向右找一個比軸值(即6)大的數交換,左邊第一個數為1,不比6大,則找左邊第二個數;左邊第二個數為2,不必6大,找左邊第三個數;左邊第三個數為8,比6大,則交換6和8(目前數字排列為 1 2 6 5 8)
第三次:再自右向左找一個比軸值(即6)小的數交換,右邊第一個數為8,不比6小,則找右邊第二個數;右邊第二個數為5,比6小,則交換6和5(目前數字排列為 1 2 5 6 8)
第四次:再自左向右找一個比軸值(即6)大的數交換,左邊第一個數為1,不比6大,則找左邊第二個數;左邊第二個數為2,不必6大,則找左邊第三個數;左邊第三個數為5,不比6大,則找左邊第四個數,結果第四個書就是軸值本身,則一輪迴圈停止(目前數字排列為 1 2 5 6 8)
最後:這樣,第一輪就把最初的一組元素{ 6 2 8 5 1 }分為兩組元素{ 1 2 5 }和{ 8 }(6為軸值,經歷這幾次遍歷,便已經固定其正確位置了,以後不需要再考慮這個元素)

第二輪中:
先考慮第一輪軸值(即6)左邊的資料 { 1 2 5 }:
第二輪中左邊新資料的第一輪:(先假設新資料的第一個元素1為新的軸值自右向左找一個比軸值(即1)小的數交換,右邊第一個數為5,不比1小,則找右邊第二個數;右邊第二個數為2,不比1小,則找右邊第三個數,結果右邊第三個數就是軸值本身,則迴圈停止(目前數字排列為 1 2 5 ),同樣的迴圈已經固定軸值(即1)的位置
同時,軸值1的左邊沒有資料,即分到了不可再分的地步,那麼遞迴結束,而軸值1的右邊還有資料 { 2 5 },則繼續確立新的軸值為2,再進行如上操作,直到分到不可以再分,則遞迴終止,最後可以確保第一輪的軸值(即6)左邊的新資料 { 1 2 5 }每個都被固定,則左邊資料的遞迴結束。
再考慮第一輪軸值(即6)右邊的資料 { 8 }:
已經被分到不可再分,則它的位置就已經被確定了,右邊資料的遞迴結束。
最終整組資料就排列完畢

程式碼1(按我如上分析的快速排序法):

#include <iostream>
using namespace std;

void qsort(int[],int,int);//宣告排序函式
void swap(int &a,int &b){ int t=a;a=b;b=t;}//直接定義交換函式

int main()
{
    int a[]={6,2,8,5,1};
    int len=sizeof(a)/sizeof(int);//計算陣列中元素的個數
    for(int i=0;i<len;++i)
        cout<<a[i];//列印原陣列
    cout<<endl<<endl;//空一行

    qsort(a,0,len-1);//呼叫排序函式

    for(int i=0;i<len;++i)
        cout<<a[i];
    cout<<endl;
}

void qsort(int a[],int left,int right)
{
    int index=left;//初始化軸值的下標為要排序陣列的第一個元素
    int pivot=a[left];//記錄軸值初始化為一組資料的第一個元素
    int l=left,r=right;//因為要從右向左,從左向右遍歷,所以定義l,r作為可移動的指向判斷數的下標,可以想成移動的指標
                       //而left、right則為每個函式的最左最右的固定下標值

    while(l<r)//這個迴圈是用於,當一個數據不可再分的時候就停止遞迴用的,
    {         //比如{ 8 },它不可再分(即已經固定),它的l和它的r相等,不滿足迴圈條件,即停止遞迴
        for(;l<r && a[r]>pivot;--r);//因為要從右向左遍歷,如果右邊的數字比軸值大,則r往前移一位,再比較
        swap(a[r],a[index]);
        index=r;//因為每次是交換a[r]與a[index]的值,所以要求index每次交換完要變為相應的下標值
        for(;l<r && a[l]<=pivot;++l);//因為要從左向右遍歷,如果左邊的數字比軸值小,則l往後移一位,再比較
        swap(a[l],a[index]);
        index=l;//因為每次是交換a[l]與a[index]的值,所以要求index每次交換完要變為相應的下標值
    }

    if(left<index-1) qsort(a,left,index-1);//如果上一輪軸值前面的新陣列可以再分,則重複呼叫函式進行遞迴
    if(right>index+1) qsort(a,index+1,right);//如果上一輪軸值後面的新陣列可以再分,則重複呼叫函式進行遞迴
}

程式碼2(快速排序法的其他實現方法):

#include <iostream>
using namespace std;

void qsort(int[],int,int);//宣告排序函式
void swap(int &a,int &b){ int t=a;a=b;b=t;}//直接定義交換函式

int main()