您的位置:首页 > 其它

归并排序——史上最详细图解教程!!!

2017-11-29 23:57 519 查看


题目大意:把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组数,假设4组
for (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并到a中
void 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组数,假设4组 for (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并到a中 void 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记录下次开始的位置
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: