多種基礎排序方法

一、插入排序

 

1.直接插入排序

依次將待排序序列中每一個記錄插入到有序序列中,直到全部記錄都排好序。

#include <bits/stdc++.h>

using namespace std;

void insertsort(int r[], int n)
{
    for(int i = 2; i <= n; i++)
    {
        int j;
        r[0] = r[i];//r[0]是作爲哨兵 
        //哨兵作用:1.進入查找(插入位置)循環之前,它保存了r[i]的副本,使不致於因記錄後移而丟失r[i]的內容 2.在查找循環中"監視"下標變量j是否越界。一旦越界(即j=0),因爲r[0].可以和自己比較,循環判定條件不成立使得查找循環結束,從而避免了在該循環內的每一次均要檢測j是否越界(即省略了循環判定條件"j>=1")
        for(j = i-1; r[0] < r[j]; j--)
        {
            r[j+1] = r[j];
        }
        r[j+1] = r[0];
    }
}

int main()
{
    int n;
    int r[105];

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &r[i]);
    }
    insertsort(r, n);
    for(int i = 1; i <= n; i++)
    {
        printf("%d%c", r[i], " \n"[i==n]);
    }
    return 0;
}
/*
7
12 15 9 20 6 31 24
*/

複雜度分析:

最好情況下,待排序爲正序,每次只和有序序列最末位比較一次,並只移動兩次。複雜度O(n)
最壞情況下,待排序爲逆序,第i次插入要和i-1個數字進行比較,每比較一次都要移動一次。複雜度O(n²)
平均:O(n²)
輔助空間:O(1)
直接插入排序是穩定的排序方法。
當序列中的記錄基本有序或者待排序列記錄較少時,它是最佳的排序方法。
 

2.希爾排序

先將整個待排序列分爲若干子序列,在子序列內進行直接插入排序,待整個序列基本有序時,再將全體記錄進行一次插入排序。

void shellsort(int r[], int n)
{
    for(int d = n/2; d >= 1; d /= 2)//此處d就是以d爲間隔來達成逐段分割
    {
        for(int i = d+1; i <= n; i++)
        {
            int j;
            r[0] = r[i];
            for(j = i-d; j > 0 && r[0] < r[j]; j -= d)
            {
                r[j+d] = r[j];
            }
            r[j+d] = r[0];
        }
    }
}
複雜度分析:

時間性能在O(n²)和O(nlog2n{log_2{n}})之間。n在特定範圍內,時間性能在O(n^1.3)。
最好:O(n^1.3)
最壞:O(n²)
平均:O(nlog2n{log_2{n}})~O(n²)
輔助空間:O(1)
希爾排序是一種不穩定的排序方法。
 

二、交換排序

 

1.起泡排序

兩兩比較相鄰的數字,反序則交換,依次比較直到沒有反序爲止。

void bubblesort(int r[], int n)
{
    int flag = n;//flag是用來判斷在遍歷無序區是否交換過,若交換過則必然不爲0,則繼續while
    while(flag != 0)
    {
        int last = flag;//last是標記作爲無序區的最後一位的位置
        flag = 0;
        for(int i = 1; i < last; i++)
        {
            if(r[i] > r[i+1])
            {
                int tmp = r[i];
                r[i] = r[i+1];
                r[i+1] = tmp;
                flag = i;
            }
        }
    }
}
複雜度分析:

最好情況下:待排序爲正序,則執行一次,無交換,複雜度爲O(n)
最壞情況下:待排序爲逆序,則每次只有無序序列中最大值交換到了最終位置,執行了n-1次,第i次執行了n-i次的比較與交換。複雜度爲O(n²)
平均:O(n²)
輔助空間:O(1)
起泡排序是一種穩定的排序方法。
 

2.快速排序

快排是選擇一個軸值,將待排根據軸值分爲兩部分,左邊均小於軸值,右邊均大於軸值,然後對左右兩邊都進行這樣的操作,直到整個序列有序。

#include <bits/stdc++.h>

using namespace std;

int onesort(int r[], int s, int e)//對當前區間進行劃分,s爲起始位置,e爲結束位置
{
    int i = s;
    int j = e;
    while(i < j)//判斷左右兩邊的下標,
    {
        while(i < j && r[i] <= r[j])//先對右邊進行掃描,掃描到左邊大於右邊則停止循環並進行交換,下次從左邊掃描
        {
            j--;
        }
        if(i < j)
        {
            int tmp = r[i];
            r[i] = r[j];
            r[j] = tmp;
            i++;
        }
        while(i < j && r[i] <= r[j])//對左邊進行掃描,掃描到左邊大於右邊則停止掃描並進行交換,下次從右邊掃描
        {
            i++;
        }
        if(i < j)
        {
            int tmp = r[i];
            r[i] = r[j];
            r[j] = tmp;
            j--;
        }
    }
    return i;//返回當前區間軸值的位置
}

void quicksort(int r[], int s, int e)
{
    if(s < e)
    {
        int mid = onesort(r, s, e);//對當前區間進行劃分並取得當前區間的軸值
        quicksort(r, s, mid-1);//對軸值左邊區間進行劃分
        quicksort(r, mid+1, e);//對軸值右邊區間進行劃分
    }
}

int main()
{
    int n;
    int r[105];

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &r[i]);
    }
    quicksort(r, 1, n);
    for(int i = 1; i <= n; i++)
    {
        printf("%d%c", r[i], " \n"[i==n]);
    }
    return 0;
}
/*
7
12 15 9 20 6 31 24
8
50 13 55 97 27 38 49 65
7
23 13 49 6 31 19 28
*/

快速排序的次數取決於遞歸的深度。

複雜度分析:

最好情況下:每次劃分的序列左邊區間與右邊區間長度相等,對每次劃分要掃描整個序列,所需O(n),共需劃分log2n{log_2{n}}次,故複雜度爲O(nlog2n{log_2{n}})
最壞情況下:待排序列爲正序或者逆序,則劃分左邊或者右邊總會有一邊長度爲空,則需調用n-1次遞歸才能完全排好序。故複雜度爲O(n²)
平均O(nlog2n{log_2{n}})
輔助空間:O(log2n{log_2{n}})~O(n)
快速排序是一種不穩定的排序方法。
快速排序平均性能是迄今爲止排序算法中最好的一種。
 

三、選擇排序

 

1.簡單選擇排序

在r[i]-r[n]中最小的與r[i]進行交換作爲有序序列中第i個。

void selectsort(int r[], int n)
{
    for(int i = 1; i <= n; i++)
    {
        int flag = i;
        for(int j = i+1; j <= n; j++)
        {
            if(r[flag] > r[j])
            {
                flag = j;
            }
        }
        if(flag != i)
        {
            int tmp = r[flag];
            r[flag] = r[i];
            r[i] = tmp;
        }
    }
}
複雜度分析:

無論情況怎樣,都會進行n-1次比較,且需要進行n-1次排序。故最好情況最壞情況下複雜度均爲O(n²)
輔助空間:O(1)。
簡單選擇排序是一種不穩定的排序方法。
 

2.堆排序

利用二叉樹的邏輯思想,將最大值放到根節點(大根堆),然後將最大值放到有序序列中第一個位置,依次下去便可排好序。

void sift(int r[], int s, int e)//每次調整堆,當前節點爲s,堆中最後一個節點爲e (大根堆)
{
    int i = s;
    int j = i*2;//左節點
    while(j <= e)//還在這個堆裏
    {
        if(j < e && r[j] < r[j+1])//將j指向左節點與右節點中的大者
        {
            j++;
        }
        if(r[i] > r[j])//若當前節點大於左右節點,則不用調整
        {
            break;
        }
        else//調整這個堆並且把i與j往下,達到完全調整好以s爲節點的大根堆
        {
            int tmp = r[i];
            r[i] = r[j];
            r[j] = tmp;
            i = j;
            j = i*2;
        }
    }
}

void heapsort(int r[], int n)
{
    for(int i = n/2; i >= 1; i--)//從最後一個分支節點開始往根節點 都要進行調整 初始堆
    {
        sift(r, i, n);//調整以i爲節點的大根堆
    }
    for(int i = 1; i < n; i++)//對所有節點都進行 把大根堆的頂點r[1]放到r[n-i+1]相對應的位置,並調整1~n-i的堆,因爲n-i以後都是有序的
    {
        int tmp = r[1];
        r[1] = r[n-i+1];
        r[n-i+1] = tmp;
        sift(r, 1, n-i);
    }
}
複雜度分析:

運行時間主要在初始堆與調整堆上,初始堆需要的時間爲O(n),第i次調整需要的時間爲O(log2i{log_2{i}}),且需要n-1次調整堆,故最好情況與最壞情況都是時間複雜度爲O(nlog2n{log_2{n}})
堆排序是一種不穩定的排序方法。
由於堆排交換與比較次數比較多,平均而言比快速排序慢。
 

四、歸併排序

 

1.二路歸併排序

把整個序列遞歸分下去分成若干小塊,將兩兩相鄰的小塊進行排序,之後在一層層向上整合。

void Merge(int r[], int s, int m, int e)//將s~m與m+1~e的兩小塊整合排序到s~e中
{
    int i = s;
    int j = m+1;
    int l = s;
    int tmp[105];
    while(i <= m && j <= e)//將左右小塊中的小值放到輔助數組中
    {
        if(r[i] <= r[j])
        {
            tmp[l++] = r[i++];
        }
        else
        {
            tmp[l++] = r[j++];
        }
    }
    while(i <= m)//若右邊小塊完了,就把左邊小塊剩餘的放到輔助數組中
    {
        tmp[l++] = r[i++];
    }
    while(j <= e)
    {
        tmp[l++] = r[j++];
    }
    for(int i = s; i <= e; i++)//把輔助數組給傳參進來的數組
    {
        r[i] = tmp[i];
    }
}

void Mergesort(int r[], int s, int e)
{
    if(s == e)//起始位置和結束位置相等表示這小塊只有一個數字,不用排序了
    {
        return;
    }
    else
    {
        int m = (s+e)>>1;//找中值並分別對左右小塊進行遞歸
        Mergesort(r, s, m);
        Mergesort(r, m+1, e);
        Merge(r, s, m, e);//整合排序左右小塊
    }
}

複雜度分析:

一次歸併排序需要將待排小塊掃描一遍,故時間複雜度爲O(n),且歸併需要進行log2n{log_2{n}}次,故最好情況最壞情況時間複雜度爲O(nlog2n{log_2{n}})
輔助空間需要一個等大的輔助數組來支持,故空間複雜度爲O(n)
二路歸併排序時一種穩定的排序方法。
 

五、分配排序

 

1.桶排序

桶排序思路就是將序列中的數字放到哈希表中,再遍歷這個哈希表依次將元素取出便成爲有序的序列。
可以用鏈表與隊列進行實現。

複雜度分析:

由於是放入哈希表中,再遍歷,故初始化放入就需要O(n)的時間,再者序列中可能會有相同元素,設相同元素有m個,故遍歷這些相同元素就需要O(m)的時間,故總時間爲O(n+m)
空間複雜度爲O(m),表示儲存m個靜態隊列表示的桶。
桶排序是穩定的排序方法。
 

2.基數排序

基數排序是將序列中所有數字補位補到等長,數位短的就在前面補0以此達到目的。之後從最低位開始往高位進行排序。就會變成有序序列。
設待排序列爲n個,d爲等長的長度,取值範圍爲m,每一次分配的時間爲O(n),每一次收集需要的時間爲O(n+m),需要執行d次,故時間複雜度O(d(n+m))
空間複雜度,因爲需要m個隊列,故空間複雜度爲O(m)
 

六、大概總結

 

1.各排序的複雜度

在這裏插入圖片描述
對於基本有序的序列,直接插入排序最合適!
快速排序目前被認爲是最快的排序方法!
對於序列裏記錄多的情況下,歸併排序較堆排序更快!
最好情況下:直接插入排序和起泡排序最快
最壞情況下:堆排序和歸併排序最快
平均情況下:快速排序最快

 

2.各排序的穩定性

穩定的:直接插入排序、起泡排序、歸併排序、桶排序和基數排序。
不穩定的:希爾排序、快速排序、簡單選擇排序、堆排序。

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