1. 程式人生 > >【紫書】演算法競賽之排序演算法筆記

【紫書】演算法競賽之排序演算法筆記

一:氣泡排序(O(n^2))

每組每個位置的數與它後面的數比較,前面數大於後面的數就交換數值,交換n-1組。

每次每組交換的時候,都會把最大的排到後面去,就類似與在水底泡泡慢慢的向上浮出。

特性:

穩定。

動圖演示:

詳細解析程式碼:

#include<stdio.h>

int main() {
    int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
    //為了方便大家理解,我將數值設為與動圖一樣的數值。

    int n = 15;
    //氣泡排序
    for(int i=0;i<n-1;i++)//代表的是要做多少組比對,為什麼是n-1呢?
        //因為計數從0開始而且第一個數和自身沒有比對的必要性。
        for(int j=0;j<n-1-i;j++)
        //代表組裡面的序號,因為要與後面數做比較,而最後一個數沒有與之比較的(可能會越界)所以n-1
        //為什麼還要-i呢?
        //因為每排過一遍,這一組最大值就排到後面去了,也就沒必要再去比較了。
            if (a[j] > a[j + 1]) {
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }

    for (int i = 0;i < n;i++)
        printf("%d ", a[i]);

    return 0;
}

 

二、選擇排序(O(n^2))

設定兩重迴圈,第一重迴圈做為比較的基準,第二重迴圈找它後面比它小的數,然後與之交換。

就是選擇一個它後面的所有比他小的數中的最小的數進行交換,氣泡排序是固定後面的數,而選擇排序是固定前面的數。

特性:

移動資料的次數已知(n-1 次)。

動圖演示:

 

  

詳細解析程式碼:

#include<stdio.h>

int main() {
    int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
    //為了方便大家理解,我將數值設為與動圖一樣的數值。

    int n = 15;
    //選擇排序
    for (int i = 0;i < n - 1;i++) // 代表的是要做基數數值的下標,為什麼是n - 1呢?
        //因為計數從0開始而且最後一個數沒得數和它進行比較了。
    {
        int k = i;
        for (int j = i + 1;j < n;j++)//找到比基數小的數中的最小數的下標
            if (a[k] > a[j]) k = j;

        if (k != i) {
            int temp = a[k];
            a[k] = a[i];
            a[i] = temp;
        }
    }

    for (int i = 0;i < n;i++)
        printf("%d ", a[i]);

    return 0;
}

 

三、插入排序(O(n^2))

從第二個數開始,將數字抽出來與前面的數依次比較,大於它的統統向後移動一格。

也就是將抽出來的數放到它該在的位置。

特性:

在大多數元素已經有序的情況下,插入排序的工作量較小。

動圖演示:

詳細解析程式碼:

#include<stdio.h>

int main() {
    int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
    //為了方便大家理解,我將數值設為與動圖一樣的數值。

    int n = 15;
    //選擇排序
    for (int i = 1;i < n ;i++) // 代表的是要放回原位數數值的下標
    {
        int front = i- 1;//初始化前一個數的下標
        int sum = a[i];//放回原位數數值
        while (front >= 0 && a[front] > sum)
            //防止陣列越界並且前面數比後面的數大才能繼續
            a[front + 1] = a[front--];
        //把前面數向後移動,前一個數的下標-1,,想象動圖中空格的變化

        //上個迴圈結束了,說明空格不能再變化了,將其賦值回去就好了
        a[++front] = sum;
    }

    for (int i = 0;i < n;i++)
        printf("%d ", a[i]);

    return 0;
}

四、歸併排序 O(nlog(2)n)

特性:

可順便處理逆序對的問題。

動圖演示:

這個演算法用動圖充分體現了它的思想,放在下面的是輔助陣列,將每部分以兩段兩段的分開進行排序,分開的排好了,再進行總的排序。

詳細解析程式碼

#include<stdio.h>

int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
//為了方便大家理解,我將數值設為與動圖一樣的數值。
int t[20];//輔助陣列,中轉站的意思。

void merge_sort(int l, int r) {
 //如果兩個陣列下標相鄰,就不必要再進行二分了,再分就只有一個了,無法比較了。
    if (r - l > 1) {
        int m = l + (r - l >> 1), i = l, j = m, k = l;
        //m為分開的的這一段的中間下標,i為左邊起始點,j為右邊起始點,k為輔助陣列起始點。
        //將總的一段二分。
        merge_sort(l, m);
        merge_sort(m, r);

        //然後再將這兩段歸併到一起進行排序。
        while (i < m || j < r)//防止下標越界

            //先放到輔助陣列中的肯定是小的,所以我們只需要將兩邊較小的放到輔助陣列中就好了。
            if (j >= r || (i < m && a[i] <= a[j])) t[k++] = a[i++];
        //注意下標越界問題。
            else t[k++] = a[j++];//這裡處理逆序對問題。


        for (i = l;i < r;i++) a[i] = t[i];
    }
}

int main() {
    int n = 15;

    merge_sort(0, n);
    for (int i = 0;i < n;i++) printf("%d ", a[i]);

    return 0;
}

 

 

逆序對問題    程式碼:

#include<stdio.h>

int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
//為了方便大家理解,我將數值設為與動圖一樣的數值。
int t[20];//輔助陣列,中轉站的意思。
int ans;//逆序對數量

void merge_sort(int l, int r) {
    //如果兩個陣列下標相鄰,就不必要再進行二分了,再分就只有一個了,無法比較了。
    if (r - l > 1) {
        int m = l + (r - l >> 1), i = l, j = m, k = l;
        //m為分開的的這一段的中間下標,i為左邊起始點,j為右邊起始點,k為輔助陣列起始點。
        //將總的一段二分。
        merge_sort(l, m);
        merge_sort(m, r);

        //然後再將這兩段歸併到一起進行排序。
        while (i < m || j < r)//防止下標越界

            //先放到輔助陣列中的肯定是小的,所以我們只需要將兩邊較小的放到輔助陣列中就好了。
            if (j >= r || (i < m && a[i] <= a[j])) t[k++] = a[i++];
        //注意下標越界問題。
            else t[k++] = a[j++],ans+=m-i;//這裡處理逆序對問題。


        for (i = l;i < r;i++) a[i] = t[i];
    }
}

int main() {
    int n = 15;

    merge_sort(0, n);
    printf("%d\n", ans);

    return 0;
}

 

只要你把

else t[k++] = a[j++];成else t[k++]=a[j++],ans+=m-i;就好了,記得宣告ans變數。

逆序對的定義如下:對於數列的第 i 個和第 j 個元素,如果滿足 i < j 且 a[i] > a[j],則其為一個逆序對;否則不是。

而else恰好符合這個條件只要有一個是從右邊的數加進來的,就說明當前(左邊)下標的  i到m-1都是大於這個數的。

 

五、快速排序 O(nlog(2)n)

以中間數作為基數,同時從左右邊開始比較,因為基數是在中間,所以左邊的數要比基數小,右邊的數要比基數大,否則就交換數值,分支遞迴反覆便得出正確的順序。

特性:

可以處理求第幾大的問題;極快,資料移動少;

缺點:

不穩定。

 

詳細解析程式碼:

#include<stdio.h>

int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
//為了方便大家理解,我將數值設為與之前一樣的數值。

void quick_sort(int l, int r) {
    if (l < r) {//防止越界
        int i = l - 1, j = r + 1, x = a[l + r >> 1];
        //-1、+1的原因是因為下面我們要用++i、--j。
        //為什麼不用i++、j++呢?因為我們要比較數值,要讓i、j與我們比較的數值下標同步,提前+1或者-1會導致後面的交換髮生錯誤。
        //前者是先使用i+1再將i=i+1,後者是先使用i再將i=i+1。
        while (i < j) {
            //雙指標移動不符合就暫停
            while (a[++i] < x);
            while (a[--j] > x);

            //兩邊都不符合了,交換數值。
            if (i < j) {//學了c++後可直接用swap函式。
                int t = a[i];
                a[i] = a[j];
                a[j] = t;
            }
        }
        //再分治,左右兩邊遞迴排序。
        quick_sort(l, j);quick_sort(j + 1, r);
    }
}

int main() {
    int n=15;

    quick_sort(0, n - 1);
    for (int i = 0;i < n;i++) printf("%d ", a[i]);
    return 0;
}

 

尋找第k小的數    程式碼:

#include<stdio.h>

int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
//為了方便大家理解,我將數值設為與之前一樣的數值。

int quick_sort(int l, int r, int k) {
    if (l == r) return a[l];
    int i = l - 1, j = r + 1, x = a[l + r >> 1];
    //-1、+1的原因是因為下面我們要用++i、--j。
    //為什麼不用i++、j++呢?因為我們要比較數值,要讓i、j與我們比較的數值下標同步,提前+1或者-1會導致後面的交換髮生錯誤。
    //前者是先使用i+1再將i=i+1,後者是先使用i再將i=i+1。
    while (i < j) {
        //雙指標移動不符合就暫停
        while (a[++i] < x);
        while (a[--j] > x);

        //兩邊都不符合了,交換數值。
        if (i < j) {//學了c++後可直接用swap函式。
            int t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    }

    int s = j - l + 1;//判斷左邊有多少數,是否包含第k個數
    //分治,不斷縮小範圍,直到l==r,輸出答案。
    if (k <= s) return quick_sort(l, j, k);
    //k>s的話說明第K個值在右邊的第k-s 個數中,那麼在右邊的k=k-s;
    return quick_sort(j + 1, r, k - s);
}

int main() {
    int n = 15, k = 5;

    int ans = quick_sort(0, n - 1, k);
    printf("%d \n", ans);
    return 0;
}

&n