1. 程式人生 > >資訊學奧賽中常用的六種排序演算法

資訊學奧賽中常用的六種排序演算法

https://blog.csdn.net/qq_37220238/article/details/82933525

六種排序演算法
一、氣泡排序
1.基本思想
2.排序過程
3.程式實現
4.改進後的氣泡排序
二、選擇排序
1.基本思想
2. 排序過程
3.程式實現
三、插入排序
1.基本思想
2.排序過程
3.程式實現
四、桶排序
1.基本思想
2.程式實現
五、快速排序
1.基本思想
2.排序過程
3.程式實現
程式1(以最左邊的元素為基準數)
程式2(以中點元素為基準數)
六、歸併排序
1.基本思想
2.排序過程
3.程式實現
七、各種排序演算法的比較
1.穩定性比較
2.時間複雜性比較
3.輔助空間的比較
4.其它比較
一、氣泡排序
1.基本思想
氣泡排序(Bubble Sort),是一種電腦科學領域的較簡單的排序演算法。
它重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果他們的順序(如從大到小、首字母從A到Z)錯誤就把他們交換過來。走訪元素的工作是重複地進行直到沒有相鄰元素需要交換,也就是說該元素已經排序完成。

2.排序過程
有6個元素需要排序: 6 5 3 4 1 2

第一趟排序:

第二趟排序:

第三趟排序:

第四趟排序

第五趟排序:

  五趟結束後,6個整數就已經排序完成。排序過程中,大數慢慢的往後,相當於氣泡上升,所以叫氣泡排序。
3.程式實現
**程式實現方法:**用兩層迴圈完成演算法,外層迴圈i控制每輪要進行多少次的比較,第1輪比較n-1次,第2輪比較n-2次,……,最後一輪比較1次。內層迴圈j控制每輪i次比較相鄰兩個元素是否逆序,若逆序就交換這兩個元素。

程式輸入:
第一行:一個整數n(0<n<=500)
第二行:n個帶排列的數(用空格隔開)

#include<iostream>
using namespace std;
const int maxn=505;
int n,a[maxn];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=n-1;i>=1;i--)//第i輪排序
    {
        for(int j=1;j<=i;j++)
            if(a[j]>a[j+1])//比較相鄰元素大小
                swap(a[j],a[j+1]);
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4.改進後的氣泡排序
  對於有些資料,我們發現,不一定要n-1次才能排完。例如1 5 2 3 4 6,我們發現只需一趟排序就可以將整個序列排完,於是,我們可以設定一個布林變數,判斷是否有進行交換,如果沒有交換,說明已經排序完成,進而減少幾趟排序。

#include<iostream>
using namespace std;
const int maxn=505;
int n,a[maxn];
bool flag;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=n-1;i>=1;i--)//第i輪排序
    {
        flag=0;
        for(int j=1;j<=i;j++)
            if(a[j]>a[j+1])//比較相鄰元素大小
            {
                flag=1;
                swap(a[j],a[j+1]);
            }
        if(flag==0)//沒有發生交換,說明已經排好序
            break;
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
二、選擇排序
1.基本思想
  每一趟從待排序的資料元素中選出最小(或最大)的一個元素,順序放在待排序的數列的最前,直到全部待排序的資料元素排完。

2. 排序過程
對以下序列進行排序:
49 38 65 97 76 13 27 49
第一趟排序後 13 [38 65 97 76 49 27 49]
第二趟排序後 13 27 [65 97 76 49 38 49]
第三趟排序後 13 27 38 [97 76 49 65 49]
第四趟排序後 13 27 38 49 [76 97 65 49]
第五趟排序後 13 27 38 49 49 [97 65 76]
第六趟排序後 13 27 38 49 49 65 [97 76]
第七趟排序後 13 27 38 49 49 65 76 [97]
最後排序結果 13 27 38 49 49 65 76 97

3.程式實現
程式輸入:
第一行:一個整數n(0<n<=500)
第二行:n個帶排列的數(用空格隔開)

#include<iostream>
using namespace std;
const int maxn=505;
int n,a[maxn],t,MIN,MINA;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n-1;i++)
    {
        MIN=i,MINA=a[i];
        for(int j=i+1;j<=n;j++)
        {
            if(a[j]<MINA)
            {
                MIN=j;//找出後面的最小值和最小值的座標
                MINA=a[j];
            }
        }
        swap(a[i],a[MIN]);//把最小值放在前面
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
三、插入排序
1.基本思想
當讀入一個元素時,在已經排序好的序列中,搜尋它正確的位置,再放入讀入的元素。但不該忽略一個重要的問題:在插入這個元素前,應當先將將它後面的所有元素後移一位,以保證插入位置的原元素不被覆蓋。

2.排序過程
例如:設n=8,陣列a中8個元素是: 36,25,48,12,65,43,20,58,執行插入排序程式後,其資料變動情況:
第0步:[36] 25 48 12 65 43 20 58
第1步:[25 36] 48 12 65 43 20 58
第2步:[25 36 48] 12 65 43 20 58
第3步:[12 25 36 48] 65 43 20 58
第4步:[12 25 36 48 65] 43 20 58
第5步:[12 25 36 43 48 65] 20 58
第6步:[12 20 25 36 43 48 65] 58
第7步:[12 20 25 36 43 48 58 65]

3.程式實現
程式輸入:
第一行:一個整數n(0<n<=500)
第二行:n個帶排列的數(用空格隔開)

#include<iostream>
using namespace std;
const int maxn=505;
int n,a[maxn],tmp;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        int j;
        for(j=i-1;j>=1;j--)
            if(a[j]<a[i])//找到第一個比a[i]小的元素位置j,插入位置為j+1
                break;
        if(j!=i-1)
        {
            tmp=a[i];//儲存插入元素的值
            for(int k=i-1;k>=j+1;k--)//j+1到i-1的元素後移
                a[k+1]=a[k];
            a[j+1]=tmp;//插入
        }
    }
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
四、桶排序
1.基本思想
桶排序的思想是若待排序的值在一個明顯有限範圍內(整型)時,可設計有限個有序桶,待排序的值裝入對應的桶(當然也可以裝入若干個值),桶號就是待排序的值,順序輸出各桶的值,將得到有序的序列。

2.程式實現
程式輸入:
第一行:一個整數n(0<n<=500)
第二行:n個帶排列的數a[i](-10000<=a[i]<=10000)(用空格隔開)

#include<iostream>
using namespace std;
const int maxn=10005;
int n,a[2*maxn],MAXA=-1,num;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>num;
        a[num+maxn]++;//把相對應的整數放在對應的桶中
        MAXA=max(MAXA,num+maxn);//求出所有桶中數的最大值,排除空桶
    }
    for(int i=0;i<=MAXA;i++)
    {
        while(a[i]!=0)
        {
            cout<<i-maxn<<" ";
            a[i]--;
        }
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
五、快速排序
1.基本思想
快速排序是對氣泡排序的一種改進。它的基本思想是,通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。

2.排序過程


3.程式實現
程式輸入:
第一行:一個整數n(0<n<=500)
第二行:n個帶排列的數(用空格隔開)

程式1(以最左邊的元素為基準數)
#include <cstdio>
int a[505],n;//定義全域性變數,這兩個變數需要在子函式中使用
void quicksort(int left,int right)
{
    int i,j,t,temp;
    if(left>right)
       return;

    temp=a[left]; //temp中存的就是基準數
    i=left;
    j=right;
    while(i!=j)
    {
                   //順序很重要,要先從右邊開始找
                   while(a[j]>=temp && i<j)
                            j--;
                   //再找右邊的
                   while(a[i]<=temp && i<j)
                            i++;
                   //交換兩個數在陣列中的位置
                   if(i<j)
                   {
                            t=a[i];
                            a[i]=a[j];
                            a[j]=t;
                   }
    }
    //最終將基準數歸位
    a[left]=a[i];
    a[i]=temp;

    quicksort(left,i-1);//繼續處理左邊的,這裡是一個遞迴的過程
    quicksort(i+1,right);//繼續處理右邊的 ,這裡是一個遞迴的過程
}
int main()
{
    int i,j,t;
    //讀入資料
    scanf("%d",&n);
    for(i=1;i<=n;i++)
                   scanf("%d",&a[i]);
    quicksort(1,n); //快速排序呼叫

    //輸出排序後的結果
    for(i=1;i<=n;i++)
        printf("%d ",a[i]);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
程式2(以中點元素為基準數)
#include<iostream>
using namespace std;
const int maxn=505;
int n,a[maxn];
void qsort(int l,int r)
{
    int i,j,mid;
    i=l,j=r;//i、j為當前序列的左右端點
    mid=a[(l+r)/2];//mid為此序列的基準數
    while(i<j)
    {
        while(a[i]<mid)i++;//從左往右找到第一個大於等於基準數的元素a[i]
        while(a[j]>mid)j--;//從右往左找到第一個小於等於基準數的元素a[j]
        if(i<=j)
        {
            swap(a[i],a[j]);//交換兩個數
            i++;//繼續尋找
            j--;
        }
    }
    if(l<j)//基準數左邊序列
        qsort(l,j);
    if(i<r)//基準數右邊序列
        qsort(i,r);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    qsort(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
六、歸併排序
1.基本思想
歸併排序是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為二路歸併。

2.排序過程
有8個數據需要排序:10 4 6 3 8 2 5 7
歸併排序主要分兩大步:分解、合併。

合併過程為:比較a[i]和a[j]的大小,若a[i]≤a[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素a[j]複製到r[k]中,並令j和k分別加上1,如此迴圈下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元。歸併排序的演算法我們通常用遞迴實現,先把待排序區間[s,t]以中點二分,接著把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸併操作合併成有序的區間[s,t]。

3.程式實現
程式輸入:
第一行:一個整數n(0<n<=500)
第二行:n個帶排列的數(用空格隔開)

#include<iostream>
using namespace std;
const int maxn=505;
int n,a[maxn],b[maxn];//b為輔助陣列
void msort(int l,int r)
{
    if(l==r)//遞迴出口
        return;
    int mid=(l+r)/2;
    msort(l,mid);//左邊序列
    msort(mid+1,r);//右邊序列
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)//左右序列進行合併
    {
        if(a[i]<=a[j])//把小的元素放在前面
            b[k]=a[i++];
        else
            b[k]=a[j++];
        k++;
    }
    while(i<=mid)//如果有剩餘序列,直接加在輔助陣列最後面
        b[k++]=a[i++];
    while(j<=r)
        b[k++]=a[j++];
    for(int i=l;i<=r;i++)//賦值已經排好序的序列給a陣列
        a[i]=b[i];
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    msort(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
七、各種排序演算法的比較
1.穩定性比較
**穩定排序:**插入排序、氣泡排序、二叉樹排序、二路歸併排序及其他線形排序是穩定的;
**不穩定排序:**選擇排序、希爾排序、快速排序、堆排序是不穩定的。

2.時間複雜性比較
  插入排序、氣泡排序、選擇排序的時間複雜性為O(n^2);快速排序、堆排序、歸併排序的時間複雜性為O(nlog2n);桶排序的時間複雜性為O(n);

  若從最好情況考慮,則直接插入排序和氣泡排序的時間複雜度最好,為O(n),其它演算法的最好情況同平均情況相同;若從最壞情況考慮,則快速排序的時間複雜度為O(n2),直接插入排序和氣泡排序雖然平均情況相同,但係數大約增加一倍,所以執行速度將降低一半,最壞情況對直接選擇排序、堆排序和歸併排序影響不大。

  由此可知,在最好情況下,直接插入排序和氣泡排序最快;在平均情況下,快速排序最快;在最壞情況下,堆排序和歸併排序最快。

3.輔助空間的比較
  桶排序、二路歸併排序的輔助空間為O(n),快速排序的輔助空間為O(log2n),最壞情況為O(n),其它排序的輔助空間為O(1);

4.其它比較
  插入、氣泡排序的速度較慢,但參加排序的序列區域性或整體有序時,這種排序能達到較快的速度。反而在這種情況下,快速排序反而慢了。

  當n較小時,對穩定性不作要求時宜用選擇排序,對穩定性有要求時宜用插入或氣泡排序。

  若待排序的記錄的關鍵字在一個明顯有限範圍內時,且空間允許是用桶排序。

  當n較大時,關鍵字元素比較隨機,對穩定性沒要求宜用快速排序。

  當n較大時,關鍵字元素可能出現本身是有序的,對穩定性沒有要求時宜用堆排序

  快速排序是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;

  堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。
--------------------- 
作者:柚子將軍 
來源:CSDN 
原文:https://blog.csdn.net/qq_37220238/article/details/82933525 
版權宣告:本文為博主原創文章,轉載請附上博文連結!