1. 程式人生 > >歸併排序——史上最詳細圖解教程!!!

歸併排序——史上最詳細圖解教程!!!

題目大意:把n個數,分成若干份,然後每一份暴力排序一下,然後遞迴地合起來。

為什麼要這樣做?這樣有個球用?

核心問題就在於,每兩份之間你是怎麼合起來的。

我們舉個例子。


一個比較呆萌的思路就是,2二分插入,形成新的序列,再繼續用4插入。。。這樣的話,確實沒什麼球用。

正確思路:2在1,3中插入以後,記下插入的這個位置的index,然後下一次,就是從3開始找了,然後4插入,接下來就是從5開始找了。。。請注意哦,這個情況已經是最複雜的情況了哦!另外兩種極端情況,找遍前面的陣列都沒找到,直接在最後一股腦加進去。或者說後面的數都沒前面的某個數大,一股腦加進去。這兩種極端情況的時間複雜度是O(N)。中間會略高一點點,最差情況O(n^2),注意,是所有數都插進去的時間複雜度,可以說是很快很快很快了。

弄明白上面歸併排序的核心思想,就可以開始動手了!

接下來我們需要分析總體的思路。

1.拆分n個數為根號n組(後面會分析具體怎麼分成這個奇怪的份數的)

2.給每個組進行排序

3.把當前所有的組,兩兩合併,如果發現合併以後,剩下的組數並沒有達到1,那就繼續合併,直到合併到1組為止。

4.合併的過程又要細化,要單獨寫一個兩兩合併的方法。

5.兩兩合併的過程又要細化,要單獨寫一個方法:單個值併到陣列中,返回一個標記,下次從這個標記開始找。

所以我們用虛擬碼串聯起這個程式

// TODO: 2017/11/29 把一個數組分成根號n// TODO: 2017/11/29 把每一份都排好序,存入List<List<Integer>>
中(二維陣列也可以) // TODO: 2017/11/29 寫一個遞迴方法,功能是把List<List<Integer>>中的List合併成一個 // TODO: 2017/11/29 寫一個兩個List<Integer>之間合併的方法,後一個List<Integer>賦給前一個 // TODO: 2017/11/29 寫一個一個值在List<Integer>插入的方法,返回index記錄下次開始的位置

這樣才是正確的做法,上來就想啃掉這整個流程肯定不現實,把他拆分成一個個模組,不僅容易成功,出現bug的時候還便於debug。

拆分這個陣列成根號n段

其思維是這樣的,假設是5,根號5向下取整就是2,那就要分成兩份,一份2,一份3。

再舉一個11的例子,根號11向下取整就是3,那就要分成3份,3,3,5。

int len = a.length;//陣列長度
int k = (int) Math.sqrt(len);//根號後向下取整的值,即份數
int num = a.length / k;//把最後一份區別對待,前面每份的個數

把每一份都排好序賦值(挺複雜的,需要好好理解下的 )

for (int row = 0; row < k - 1; row ++) {//0 1
List<Integer> list = new ArrayList<>();
    for (int col = 0; col < num; col ++) {

        //index 是你開始點,其實就是col的變形
int index = row * num + col;//0 1 2//3 4 5
int min = Integer.MAX_VALUE;
        int q = -1;
        for (int i = index; i < row * num + num; i ++) {
            if (a[i] < min) {
                min = a[i];
q = i;
}
        }
        if (q != -1) {
            int t = a[q];
a[q] = a[index];
a[index] = t;
}
        list.add(a[index]);
}
    this.list.add(list);
}

遞迴,奇陣列跟偶陣列區別對待。每兩組一起處理,後一組加到前一組中,刪除後一組。直到合併到只有一組。

void circle(List<List<Integer>> list) {
    int size = list.size();//取得你有幾組數
if (size == 1) {
        return;
}

    if (size % 2 == 0) {//如果你有2 4 6組數,假設4for (int i = 0; i < size; i += 2) {//2組併到1組中,4組併到3組中
gather(list.get(i), list.get(i + 1));
}
        int f = 0;
        for (int i = 1; i < size; i += 2) {//1 3 兩組刪除
list.remove(i - f);
f ++;
}
    } else {//如果你有5組數
int newSize = size - 1;//只處理前4組數
for (int i = 0; i < newSize; i += 2) {
            gather(list.get(i), list.get(i + 1));//2組併到1組中,4組併到3組中
}
        int f = 0;
        for (int i = 1; i < newSize; i += 2) {//1 3 兩組刪除
list.remove(i);
f ++;
}
    }

    circle(list);
}
把後面的併到前面的集合中,把後面的集合每個點都插入進去,更新搜尋區間
//b併到avoid gather(List<Integer> a, List<Integer> b) {
    int aSize = a.size();
    int bSize = b.size();
    int start = 0;//取得a的第一個
int end = aSize - 1;//取得a的最後一個
for (int i = 0; i < bSize; i ++) {
        int num = b.get(i);//把每個b取一遍
start = insert(a, start, end, num);//把這個數在a的可插區間內插入,把插入點返回
end = a.size() - 1;//插入成功,結束點也要變化了
//如果迴圈完了也沒找到大於等於n的數,上面已經是插入在最後了
if (start == 999) {
            if (i + 1 < bSize) {//看看下一個數是否還在區間內
for (int j = i + 1; j < bSize; j ++) {//如果在
a.add(b.get(j));//全加進去
}
            }
            break;
}
    }
}
如果找得到那就返回index,找不到就一股腦全加在後面
int insert(List<Integer> k, int start, int end, int n) {
    for (int i = start; i <= end; i ++) {//給定搜尋區間
int num = k.get(i);//取得a中的值
if (n <= num) {
            k.add(i, n);
            return i;
}
    }
    k.add(n);
    return 999;
}

全部程式碼

import java.util.ArrayList;
import java.util.List;
public class Test {
    List<List<Integer>> list = new ArrayList<>();
Test(int[] a) {
        int len = a.length;//11
int k = (int) Math.sqrt(len);//3
int num = a.length / k;//3
for (int row = 0; row < k - 1; row ++) {//0 1
List<Integer> list = new ArrayList<>();
            for (int col = 0; col < num; col ++) {

                //index 是你開始點,其實就是col的變形
int index = row * num + col;//0 1 2//3 4 5
int min = Integer.MAX_VALUE;
                int q = -1;
                for (int i = index; i < row * num + num; i ++) {
                    if (a[i] < min) {
                        min = a[i];
q = i;
}
                }
                if (q != -1) {
                    int t = a[q];
a[q] = a[index];
a[index] = t;
}
                list.add(a[index]);
}
            this.list.add(list);
}

        List<Integer> list = new ArrayList<>();
        for (int i = (k - 1) * num; i < len; i ++) {
            int min = Integer.MAX_VALUE;
            int index = -1;
            for (int j = i; j < len; j ++) {
                if (a[j] < min) {
                    min = a[j];
index = j;
}
            }
            if (index != -1) {
                int t = a[i];
a[i] = a[index];
a[index] = t;
}
            list.add(a[i]);
}

        this.list.add(list);
circle(this.list);
        for (int i = 0; i < this.list.size(); i ++) {
            List<Integer> mList = this.list.get(i);
            for (int j = 0; j < mList.size(); j ++) {
                System.out.print(mList.get(j) + " ");
}
            System.out.println();
}
    }

    // TODO: 2017/11/29 心得 可以寫的蠢一點 效能差一點 後期再優化
void circle(List<List<Integer>> list) {
        int size = list.size();//取得你有幾組數
if (size == 1) {
            return;
}

        if (size % 2 == 0) {//如果你有2 4 6組數,假設4for (int i = 0; i < size; i += 2) {//2組併到1組中,4組併到3組中
gather(list.get(i), list.get(i + 1));
}
            int f = 0;
            for (int i = 1; i < size; i += 2) {//1 3 兩組刪除
list.remove(i - f);
f ++;
}
        } else {//如果你有5組數
int newSize = size - 1;//只處理前4組數
for (int i = 0; i < newSize; i += 2) {
                gather(list.get(i), list.get(i + 1));//2組併到1組中,4組併到3組中
}
            int f = 0;
            for (int i = 1; i < newSize; i += 2) {//1 3 兩組刪除
list.remove(i);
f ++;
}
        }

        circle(list);
}

    //b併到avoid gather(List<Integer> a, List<Integer> b) {
        int aSize = a.size();
        int bSize = b.size();
        int start = 0;//取得a的第一個
int end = aSize - 1;//取得a的最後一個
for (int i = 0; i < bSize; i ++) {
            int num = b.get(i);//把每個b取一遍
start = insert(a, start, end, num);//把這個數在a的可插區間內插入,把插入點返回
end = a.size() - 1;//插入成功,結束點也要變化了
//如果迴圈完了也沒找到大於等於n的數,上面已經是插入在最後了
if (start == 999) {
                if (i + 1 < bSize) {//看看下一個數是否還在區間內
for (int j = i + 1; j < bSize; j ++) {//如果在
a.add(b.get(j));//全加進去
}
                }
                break;
}
        }
    }

    int insert(List<Integer> k, int start, int end, int n) {
        for (int i = start; i <= end; i ++) {//給定搜尋區間
int num = k.get(i);//取得a中的值
if (n <= num) {
                k.add(i, n);
                return i;
}
        }
        k.add(n);
        return 999;
}


    public static void main(String[] args) throws Exception {
        int[] a = new int[17];
a[0] = 7;
a[1] = 10;
a[2] = 8;
a[3] = 11;
a[4] = 50;
a[5] = 9;
a[6] = 6;
a[7] = 70;
a[8] = 1;
a[9] = 4;
a[10] = 34;
a[11] = 34;
a[12] = 4;
a[13] = 4;
a[14] = 4;
a[15] = 4;
a[16] = 4;
Test test = new Test(a);
}
}

// TODO: 2017/11/29 把一個數組分成根號n// TODO: 2017/11/29 把每一份都排好序,存入List<List<Integer>>中(二維陣列也可以)
// TODO: 2017/11/29 寫一個遞迴方法,功能是把List<List<Integer>>中的List合併成一個
// TODO: 2017/11/29 寫一個兩個List<Integer>之間合併的方法,後一個List<Integer>賦給前一個
// TODO: 2017/11/29 寫一個一個值在List<Integer>插入的方法,返回index記錄下次開始的位置