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


由於本帖只是闡述幾種排序方法的原理、如何區分以及編寫幾種排序的簡單代碼,所以直接給定數組是 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()
{
    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 pivot=a[right],l=left,r=right;
    while(l<r)
    {
        swap(a[l],a[r]);
        while(l<r && a[r]>pivot) --r;
        while(l<r && a[l]<=pivot) ++l;
    }
    
    swap(a[left],a[r]);
    if(left<r-1) qsort(a,left,r-1);
    if(r+1<right) qsort(a,r+1,right);
}

其實快速排序不止這兩種方法,像百度還提供了其他方法,主要是原理懂了就可以了

至此,就總結完了選擇排序、冒泡排序、插入排序、快速排序,這四種排序方法,之后如果我學到了新的排序方法會繼續更新的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM