这几天接触到归并排序这个概念,由于平时的工作中涉及到使用算法的业务场景比较少,所以对于归并排序这个不了解,既然接触到了所以去搜索研究了一下,其原理还是挺有意思的,使用的是分治的思想,通过递归的方式达到一种快速提高排序效率的目的。
我们设想一个场景,要将100w个整数进行从小到大排序,怎样能达到一个能够快速得到排序结果的目的呢?归并排序就能很好的解决这个问题。
分治的思想就是将大问题化成小问题进行解决,如果还不够小那就继续分。
假设我们有100w个人需要从矮到高进行排队,按照分治的思想,我们需要将我们的问题规模进行缩小,思考一下如果我们只需要将50w人进行排队呢?如果是25w人呢,......直到最后如果是两个人排队呢?
当然这个时候可以直接对比这两个人的身高进行排队就行了,但是这是我们将问题规模缩小之后才可以一眼就对比出来,如果回到原来的规模,那么直接对比很显然不现实,所以我们将问题规模缩小到最小后,需要抽象出一种对比的方法,不管问题的规模大小,我们都可以用这种对比方法进行比较,这就是我们在问题规模细分到最小后需要思考的“治”。
1、我们将两个人分成左右两个队伍,那么不用比较,左右两边就都已经是从高到矮的队伍了,因为都只有1个人,从程序角度来说,左右两边都是一个有序的集合了。然后将左右两边的人合并到一个队伍,把左边和右边的人进行对比,左边高则右边人先进入合并队伍,否则左边人先进;
2、如果我们的规模是4个人,采用上面的思路,我们分为左右两边各2个人,然后将左右两边队伍先变成有序集合,然后把左右两边的人依次进行对比进入合并的队伍,左边高,则右边先进,右边的下一个人继续与左边上一轮的人进行对比,反之亦然。至于如何把左右两边
队伍中的两人都变成有序集合,我们已经有了第一步帮我们实现;
如是,规律找到了:
将队伍分为左右两边分别进行排队,等待两边都变成有序的队伍之后再将左边与右边的人依次进行对比后进入合并后的队伍,合并的过程中对比按照如下规则:
若左边高于右边,则右边人先进入合并的队伍,然后右边队伍的下一个人继续与左边队伍上一轮对比的人进行对比;
若右边高于左边,则左边人先进入合并的队伍,然后左边队伍的下一个人继续与右边队伍上一轮对比的人进行对比;
若左边队伍的人先比完,则右边队伍的人全部直接进入合并的队伍,反之若右边队伍的人先比完,左边队伍的人全部直接进入合并队伍。
那么回归我们原来的问题,我们需要将100w人进行排队,像将其分为左右两边各50w,然后左边的队伍继续分为左右两边各25w,....分到最后左右两边各1人;右边的队伍依然用此方式进行拆分。最后所有的队伍就都变成了1个人的队伍这种最简单的情况了。
以上就是归并排序的原理和思路了。下面我们使用代码来实现上述的分治思想。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //生成一个1000w个整数的数组,并将其顺序打乱 6 int[] arr = Enumerable.Range(1, 10000000).OrderBy(p => Guid.NewGuid()).ToArray(); 7 8 DateTime date1 = DateTime.Now; //记录开始排序前的时间 9 Sort(0, arr.Length - 1, arr); 10 DateTime date2 = DateTime.Now; //记录排序完成后的时间 11 12 Console.WriteLine("timespan:" + date2.Subtract(date1)); 13 Console.ReadLine(); 14 } 15 16 /// <summary> 17 /// 排序 18 /// </summary> 19 /// <param name="start"></param> 20 /// <param name="end"></param> 21 /// <param name="arr"></param> 22 private static void Sort(int start, int end, int[] arr) 23 { 24 if (start < end) 25 { 26 //将arr逻辑上一分为二,记录中间位置的索引号 27 int mid = (start + end) / 2; 28 Sort(start, mid, arr); //左边半段进行排序 29 Sort(mid + 1, end, arr); //右边半段进行排序 30 //排序完成后,arr的左边和右边都变成了有序集合 31 Merge(start, mid, end, arr); //左右两边进行合并 32 } 33 } 34 35 /// <summary> 36 /// 合并 37 /// </summary> 38 /// <param name="start"></param> 39 /// <param name="mid"></param> 40 /// <param name="end"></param> 41 /// <param name="arr"></param> 42 private static void Merge(int start, int mid, int end, int[] arr) 43 { 44 int i = start, j = mid + 1; 45 //定义一个每次进行合并的两个数组总长度的一个临时数组用于存放每次左右对比后的结果 46 int[] temp = new int[end - start + 1]; 47 for (var k = 0; k < temp.Length; k++) 48 { 49 if (j > end) //右边已经对比完,则左边剩下的全部进入temp 50 { 51 temp[k] = arr[i++]; 52 //if (i > mid) break; 53 } 54 else if (i > mid) //左边已经对比完,则右边剩下的全部进入temp 55 { 56 temp[k] = arr[j++]; 57 //if (j > end) break; 58 } 59 else //左右两边都没对比完时 60 { 61 if (arr[j] < arr[i]) //右边小则右边进入temp数组,且右边索引+1 62 { 63 temp[k] = arr[j]; 64 j++; 65 } 66 else //否则左边进入temp数组,且左边索引+1 67 { 68 temp[k] = arr[i]; 69 i++; 70 } 71 } 72 } 73 74 //将左右两边对比后的合并数组替换到arr原始数组中去 75 for (var k = 0; k < temp.Length; k++) 76 { 77 arr[start++] = temp[k]; 78 } 79 } 80 }
由于数据量太多,我就不进行排序结果的输出了,我们看看排序的时间,1000w条数据排序只需3~4s:
我们将数据缩减到100条看看排序前后的结果: