介绍
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。那么什么是归并操作,就是将两个顺序序列合并成一个顺序序列的方法。
分治法是将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
- 自下而上的迭代;
所以在这里还需要了解一下什么是递归:递归就是 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。
通常递归有两个特点:
- 递归方法一直会调用自己直到某些条件满足,也就是说一定要有出口;
- 递归方法会有一些参数,而它会把这些新的参数值传递给自己;(自己调自己);
算法步骤
1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2、设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4、重复步骤 3 直到某一指针达到序列尾;
5、将另一序列剩下的所有元素直接复制到合并序列尾。
排序原理
假设序列共有n个元素,将序列每相邻两个数字进行归并操作(merge),形成floor(n/2+n%2)个序列,排序后每个序列包含两个元素
将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
重复步骤2,直到所有元素排序完毕
代码
具体代码,个人建议最好还是用VS自己打断点,然后逐代码运行,知道跳到了哪一步,这样能更加清晰明了。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
public static int count = 0;
public static int[] o_arr = new int[] { 3, 21, 47, 19, 13, 48, 1, 49, 21, 33 };
private static void Merge(int[] arr, int left, int mid, int right, int[] temp)
{
// 第一次进来是arr = {3,21,47,19,13,48,1,49,21,33}, left = 0, mid = 0, right = 1,temp = {0,0,0,0,0,0,0,0,0,0}
// 第二次进来是arr = {3,21,47,19,13,48,1,49,21,33}, left = 0, mid = 1, right = 2,temp = {3,21,0,0,0,0,0,0,0,0}
// 左序列指针
int i = left;
// 右序列指针
int j = mid + 1;
// 临时数组指针
int t = 0;
// 第一次i = 0, j = 1, t = 0
// 第二次i = 0, j = 2, t = 0
// 此while循环第一.1次,mid = 0, right = 1, i = 0, j = 1, t = 0满足要求
// 此while循环第一.2次,mid = 0, right = 1, i = 1, j = 1, t = 1不满足要求,退出循环
// 此while循环第二.1次,mid = 1, right = 2, i = 0, j = 2, t = 0满足要求
// 此while循环第二.2次,mid = 1, right = 2, i = 1, j = 2, t = 1满足要求
// 此while循环第二.3次,mid = 1, right = 2, i = 2, j = 2, t = 2不满足要求,退出循环
while(i<=mid && j <= right)
{
// 第一.1次, arr[0] = 3, arr[1] = 21,符合要求
// 第二.1次, arr[0] = 3, arr[2] = 47,符合要求
// 第二.2次, arr[1] = 21, arr[2] = 47,符合要求
if (arr[i] < arr[j])
{
// 第一.1次, t = 0, temp[0] = arr[0] = 3,赋值执行之后,t = 1, i = 1
// 第二.1次, t = 0, temp[0] = arr[0] = 3,赋值执行之后,t = 1, i = 1
// 第二.2次, t = 1, temp[1] = arr[1] = 21,赋值执行之后,t = 2, i = 2
temp[t++] = arr[i++];
}
else
{
temp[t++] = arr[j++];
}
}
//将左边剩余元素填充进temp中
// 此while循环第一.1次,mid = 0, right = 1, i = 1, j = 1, t = 0不满足要求,退出循环
// 此while循环第二.1次,mid = 1, right = 2, i = 2, j = 2, t = 2不满足要求,退出循环
while (i <= mid)
{
temp[t++] = arr[i++];
}
//将右序列剩余元素填充进temp中
// 此while循环第一.1次,mid = 0, right = 1, i = 1, j = 1, t = 0满足要求
// 此while循环第一.2次,mid = 0, right = 1, i = 1, j = 2, t = 0不满足要求,退出循环
// 此while循环第二.1次,mid = 1, right = 2, i = 2, j = 2, t = 2满足要求
// 此while循环第二.2次,mid = 1, right = 2, i = 2, j = 3, t = 3不满足要求,退出循环
while (j <= right)
{
// 第一.1次, t = 1, temp[1] = arr[1] = 21,赋值执行之后,t = 2, j = 2
// 第二.1次, t = 2, temp[2] = arr[2] = 47,赋值执行之后,t = 3, j = 3
temp[t++] = arr[j++];
}
t = 0;
// 将temp中的元素全部拷贝到原数组中
// 此while循环第一.1次,left = 0, right = 1 满足要求
// 此while循环第一.2次,left = 1, right = 1 满足要求
// 此while循环第一.3次,left = 2, right = 1 满足要求
// ....
// 以此类推,都会把temp现有的元素全部拷贝到原数组中,因为以上操作都只会对right下标之前的元素进行操作
while (left <= right)
{
// 第一.1次, t = 0, arr[0] = temp[0] = 3,赋值执行之后,left = 1, t = 1
// 第一.1次, t = 0, arr[1] = temp[1] = 21,赋值执行之后,left = 2, t = 2
arr[left++] = temp[t++];
}
// 第一次结束,arr = {3, 21, 47, 19, 13, 48, 1, 49, 21, 33},没有改变
// 第一次结束,arr = {3, 21, 47, 19, 13, 48, 1, 49, 21, 33},没有改变
}
public static void StartSort(int[] arr, int left, int right, int[] temp)
{
// 先判断左序列起始位置是不是比右序列起始位置小,否则就不满足左右序列的定义
// 第一次进入这个方法,left = 0,right = 9,从86行进来
// 第二次从114行进来,left = 0, right = mid = 4
// 第三次还是从114行进来,left = 0, right = mid = 2
// 第四次还是从114行进来,left = 0, right = mid = 1
// 第五次还是从114行进来,left = 0, right = mid = 0,不满足要求,这次递归结束
// 第六次从124行进来,left = 1,right = 1,不满足要求,这次递归结束
// 第七次从124行进来,left = 1,right = 1,不满足要求,这次递归结束
// 第八次从124行进来,left = 3,right = 4,满足要求
// 第九次从114行进来,left = 3,right = 3,不满足要求,这次递归结束
if (left < right)
{
// 第一次进来,mid = 4
// 第二次进来,mid = (0+4)/2 = 2
// 第三次进来,mid = (0+2)/2 = 1
// 第四次进来,mid = (0+2)/2 = 0, 明显不满足要求,所以递归结束(也就是第五次递归结束)
// 第五次进来,mid = (3+4)/2 = 3,
int mid = (left + right) / 2;
Console.WriteLine("左边归并排序\n");
//左边归并排序,使得左子序列有序
// 然后进行从左序列起始位置到中间位置的排序
// 这是第一次left = 0,mid = 4
// 这是第二次left = 0,mid = 2
// 这是第三次left = 0,mid = 1
// 这是第四次left = 0,mid = 0,明显不符合要求
// 这是第五次left = 3,mid = 3,明显不符合要求
StartSort(arr, left, mid, temp);
// 经历了四次,终于把左边排序好了
Console.WriteLine("右边归并排序\n");
//右边归并排序,使得右子序列有序
// 然后进行从中间位置到右序列起始位置的排序
// 第一次(也就是左序列第四次), mid = 0,mid+1 = 1,right = 1, 明显不满足要求,所以递归结束(也就是第六次递归结束)
// 第二次(也就是左序列第三次), mid = 1,mid+1 = 2,right = 2, 明显不满足要求,所以递归结束(也就是第七次递归结束)
// 第三次(也就是左序列第二次), mid = 1,mid+1 = 3,right = 4, 满足要求
// 第四次(也就是左序列第五次), mid = 3,mid+1 = 4,right = 4, 明显不满足要求,所以递归结束
StartSort(arr, mid+1, right, temp);
Console.WriteLine("将两个有序子数组合并操作\n");
//将两个有序子数组合并操作
// 第五次(左序列递归最后一次结束)、第六次(右序列递归结束)递归结束之后,又回到了第四次递归,然后开始将左序列子数组合并操作
// 第七次(右序列递归)递归结束之后,又回到了第三次递归,然后开始将左序列子数组合并操作
// 第九次递归结束之后,又回到了第八次递归,然后开始将左序列子数组合并操作
Merge(arr,left,mid,right,temp);
}
}
public static void Sort()
{
//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
int[] temp = new int[o_arr.Length];
// 刚开始将原始数组,左序列起始位置从0开始,右序列起始位置从9开始,然后把临时的空数组传入
StartSort(o_arr, 0, o_arr.Length - 1, temp);
}
public static void Print(int index)
{
string str = "第"+(index)+"次:";
for (int i = 0; i < o_arr.Length; i++)
{
str += o_arr[i].ToString() + ", ";
}
Console.WriteLine(str);
}
static void Main(string[] args)
{
Sort();
Console.ReadKey();
}
}
}
总结
这个归并排序说实话,感觉并不是那么容易的,尤其是知道了算法逻辑以及步骤也很难写出来,最后还是去抄了大神的代码,才写成了这篇文章,大家如果看我的文章理解不了,就可以去看看大神写的博客。
点击这里跳转