什么是归并排序?
“ 归并排序的思想是分治,先将原始数组分解成若干子数组,对子数组使用归并方法进行排序,最后合并子数组成原数组产生已经排序的数列**”**
举个例子
在桌面上有十三张扑克牌分成两组,一组6张,一组7张,两组都是按从小到大顺序排好的,并且牌面向上,两堆牌中最小的都在上面,现在要将两堆牌合成一堆,并且按顺序从小到大排列。
对于这个问题,我们可以做的很自然,过程如下示意
最初的两堆牌,如下,都是最上面的最小
第一次抓取,将两堆牌上面对比,将小的牌也就是1,拿走,放入第三堆中,此时如下所示,
这时在第二堆中的3,成为该堆牌中最上面的牌。
第二次抓取,重复第一次的逻辑,将2拿走,放入第三堆紧挨1的位置,如下所示
第三次、第四次…,依次重复上面的步骤,直至桌面上的两堆牌被抓取完,所在的牌都放入到了新的牌堆儿中,也就是第三堆,示意如下:
此时先前的两堆牌中已经没有牌了,所有牌都已经有序的放入了新的牌堆中。
归并排序
在上面的例子中,其实就是利用了归并排序的中心思想–归并,将两个已经排好序的子数组,合并成一个大的父数组。这个例子,可以认为是归并算法的归并过程。
什么是分治?
将原来大规模的问题,分解成几个小规模的与原问题类似的问题,递归的求出子问题的结果,再将子问题的结果合并出原来问题结果的的过程。
那算法中归并的过程是怎么样的呢?
现在再看,给定数列
按照分治,归并的规则,可以将它分为两个数列
这两个子数列都是按从小到大的顺序排列好的,因此可以直接进行归并操作
假定
原数列为A,其初始下标为 k=1,
左子数列为L,其初始下标 i=1,
右子数列为R,其初始下标 j=1,如下所示
第一步,比较两个子数列中的i,k两个位置上数的大小,如果L[i]<=R[j],则将L[i]插入到原数列A的k位置,同时k和i的位置右移,
否则将R[j]插入到A的k位置,同时k和j的位置右移,操作完成后结果如下
第二步,重复第一步的逻辑,结果
第三步
第四步
第五步
第六步
第七步
第八步,此时R中已经没有数据,直接将L中的剩余数据插入A中,过程结束
这个过程称可以叫做Merge,回忆一下整个过程,可以将这个过程分为三个步骤:
1、将原数列拆分
2、新建了两个子数列,并将原数列中的前半部分和后半部分分别存入两个子数列中
3、对子数列进行归并过程
将这个过程实现为一个函数,如下
public static void merge(int[] array, int p, int q, int r) {
// 计算左右子数列的长度
int nLeft = q - p + 1;
int nRight = r - q;
// 分配子数列
int[] leftArray = new int[nLeft];
int[] rightArray = new int[nRight];
// 左子列初始化为 [p...q-p]
for (int i = 0; i < nLeft; i++) {
leftArray[i] = array[p + i];
}
// 左子列初始化为 [q+1...r]
for (int j = 0; j < nRight; j++) {
rightArray[j] = array[q + j + 1];
}
int i = 0; int j = 0;
for (int k = p; k <= r; k++) {
int lValue, rValue;
if (i < nLeft) {
lValue = leftArray[i];
} else {
// 如果左子列没有数据时
if (j < nRight) {
array[k] = rightArray[j];
}
j++;
continue;
}
if (j < nRight) {
rValue = rightArray[j];
} else {
// 如果右子列没有数据时
if (i < nLeft) {
array[k] = leftArray[i];
}
i++;
continue;
}
if (lValue <= rValue) {
array[k] = lValue;
i++;
} else {
array[k] = rValue;
j++;
}
}
}
下面再看这样一个数列,怎样能够将上面提到的分治和归并思想用到此数列的排序中呢?
也许你已经想到了,将这个大数列,拆分成两个小数列呀。对的,是这么一个思路,于是数列成功被拆分成了这样:
但是注意到两小子数列中的数还是无序的,不满足对其进行归并排序的条件,那怎么办呢,难道要对两个子数列进行排序?其实不然,可以继续对两个子数据进行拆分,直到不能再拆分为止,也就是每个子数列中只有一个数值时,就可以不用再拆分了,因为只有一个数时,我们可以将其认为是有序的。
于是继续拆分工作:
这样在最后一行中各个子数列中,只有一个元素,它们都是有序的,于是我们就可以对其两两进行归并排序,并一层层向上进行归并计算,如下示意:
归并排序的完整实现
import java.util.Arrays;
public class MergeSort {
public static void merge(int[] array, int p, int q, int r) {
// 计算左右子数列的长度
int nLeft = q - p + 1;
int nRight = r - q;
// 分配子数列
int[] leftArray = new int[nLeft];
int[] rightArray = new int[nRight];
// 左子列初始化为 [p...q-p]
for (int i = 0; i < nLeft; i++) {
leftArray[i] = array[p + i];
}
// 左子列初始化为 [q+1...r]
for (int j = 0; j < nRight; j++) {
rightArray[j] = array[q + j + 1];
}
int i = 0;
int j = 0;
for (int k = p; k <= r; k++) {
int lValue, rValue;
if (i < nLeft) {
lValue = leftArray[i];
} else {
// 如果左子列没有数据时
if (j < nRight) {
array[k] = rightArray[j];
}
j++;
continue;
}
if (j < nRight) {
rValue = rightArray[j];
} else {
// 如果右子列没有数据时
if (i < nLeft) {
array[k] = leftArray[i];
}
i++;
continue;
}
if (lValue <= rValue) {
array[k] = lValue;
i++;
} else {
array[k] = rValue;
j++;
}
}
}
public static void mergeSort(int[] array, int p, int r) {
if (p < r) {
int q = (int) Math.floor((p + r) / 2);
mergeSort(array, p, q);
mergeSort(array, q + 1, r);
merge(array, p, q, r); }
}
public static void main(String[] args) {
int[] array = new int[] { 5, 4, 9, 3, 2, 8, 7, 9, 1 };
System.out.println(Arrays.toString(array));
mergeSort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
}
}
ring[] args) {
int[] array = new int[] { 5, 4, 9, 3, 2, 8, 7, 9, 1 };
System.out.println(Arrays.toString(array));
mergeSort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
}
}
------结束------