1.0 概述
-
归并排序,Merging Sort
-
归并排序:将两个或两个以上的有序表合并成一个新的有序表。
-
归并排序分类:
-
二路归并排序
-
多路归并排序
-
-
二路归并排序:将两个有序表合并成一个有序表的归并排序,称为二路归并排序。也就是将两个相邻的记录有序子序列归并为一个记录的有序序列。
-
二路归并排序基本思想:
-
将待排序记录r[0]到r[n-1]看成是n个长度为1的有序子表
-
把这些子表依次两两归并,便得到[n/2]个有序子表
-
然后,再把这[n/2]个有序的子表进行两两归并
-
如此重复,直到最后得到一个长度为n的有序表为止
-
2.0 两个相邻有序序列归并:分析
-
假设前后两个有序序列分别存放在一维数组人的r[h..m] 和 r[m+1..t]中,同时,提供另一个有序数组order,用于存放归并后的数据
-
首先在两个有序序列中,分别从第1个记录开始进行对应关键字的比较,将关键字值较小的记录放入有序数据order中
-
然后,依次对两个有序序列中剩余记录进行相同操作,直到两个有序序列中的所有记录都加入到有序数组order中为止
-
最后,这个有序数组order中存放的记录序列就是归并排序后的结果。
2.1两个相邻有序序列归并:算法
- 代码
/**
*
* @param r 待排序的数组(多个有序子序列)
* @param order 已经排序号的数组
* @param h 第一个子序列开始的位置
* @param m 第一个子序列结束的位置,第二个子序列开始的位置为m+1
* @param t 第二个子序列结束的位置
*/
//把r数组中两个相邻的有序表r[h]~r[m]和r[m+1]~r[t]归并为一个有序表order[h]~order[t]
public void merge(RecordNode[] r, RecordNode[] order, int h, int m, int t) {
int i = h, j = m + 1, k = h;
while (i <= m && j <= t) { // 将r中两个相邻子序列归并到order中
if (r[i].key.compareTo(r[j].key) <= 0) {// 较小值复制到order中
order[k++] = r[i++];
} else {
order[k++] = r[j++];
}
}
while (i <= m) { // 将前一个子序列剩余元素复制到order中
order[k++] = r[i++];
}
while (j <= t) { // 将后一个子序列剩余元素复制到order中
order[k++] = r[j++];
}
}
- 测试
public class TestSeqList13_merge {
public static void main(String[] args) throws Exception {
int[] arr = {39,52,67,95,8,25,56,70};
SeqList seqList = new SeqList(arr.length);
System.out.print("序列号:");
for (int i = 0; i < arr.length; i++) {
System.out.print(" " + i);
seqList.insert(i, new RecordNode(arr[i]));
}
System.out.println();
System.out.print("归并前:");
seqList.display();
// 归并操作
System.out.print("归并后:");
RecordNode[] temp = new RecordNode[arr.length];
// 待归并的数据 {39,52,67,95}和 {8,25,56,70}
seqList.merge(seqList.r,temp,0,3, 7);
// 输出归并后的数据
for (int i = 0; i < temp.length; i++) {
String str = temp[i].key.toString().length() == 1 ? " " : " ";
System.out.print(str + temp[i].key.toString());
}
System.out.println();
}
}
//归并操作
//序列号: 0 1 2 3 4 5 6 7
//归并前: 39 52 67 95 8 25 56 70
//归并后: 8 25 39 52 56 67 70 95
4.0 一趟归并:分析
-
假设r为待排序的数组,n为待排序列的长度,s为待归并的有序子序列的长度,一趟归并排序的结果存放在数组order中。
-
两个相邻的有序序列,第一趟处理的长度为1,第二趟为2,第三趟为4 ... ,也就是说s的取值1/2/4/8...
-
步骤:
-
根据长度s,进行两两归并
-
归并操作时,如果最后两个序列长度不等,将剩余内容归并到有序表中
-
归并操作时,如果有未参加归并操作的内容,直接复制到order中
-
4.1 一趟归并:算法
- 代码
//把数组r[n]中每个长度为s的有序表两两归并到数组order[n]中
//s 为子序列的长度,n为排序序列的长度
public void mergepass(RecordNode[] r, RecordNode[] order, int s, int n) {
System.out.print("子序列长度s=" + s + " ");
int p = 0; //p为每一对待合并表的第一个元素的下标,初值为0
while (p + 2 * s - 1 <= n - 1) { //两两归并长度均为s的有序表
merge(r, order, p, p + s - 1, p + 2 * s - 1);
p += 2 * s;
}
if (p + s - 1 < n - 1) { //归并最后两个长度不等的有序表
merge(r, order, p, p + s - 1, n - 1);
} else {
for (int i = p; i <= n - 1; i++) //将剩余的有序表复制到order中
{
order[i] = r[i];
}
}
}
- 测试
public class TestSeqList14_mergepass2 {
public static void main(String[] args) throws Exception {
int[] arr = {8,25,39,52,52,67,70,95,56};
SeqList seqList = new SeqList(arr.length);
System.out.print("序列号:\t\t ");
for (int i = 0; i < arr.length; i++) {
System.out.print(" " + i);
seqList.insert(i, new RecordNode(arr[i]));
}
System.out.println();
System.out.print("初始值:\t\t ");
seqList.display();
// 一趟归并算法
RecordNode[] temp = new RecordNode[arr.length];
int s = 8;
seqList.mergepass(seqList.r, temp, s, arr.length);
for (int i = 0; i < temp.length; i++) {
String str = temp[i].key.toString().length() == 1 ? " " : " ";
System.out.print(str + temp[i].key.toString());
}
System.out.println();
}
}
//一趟归并算法
//序列号: 0 1 2 3 4 5 6 7 8
//初始值: 8 25 39 52 52 67 70 95 56
//子序列长度s=8 8 25 39 52 52 56 67 70 95
5.0 二路归并:分析
-
设置待排序的n个记录保存在数组r[n]中,归并过程中需要引入辅助数组temp[n],
-
第1趟由r归并到temp,第2趟由temp归并到r
-
如此反复,直到n个记录成为一个有序表为止。
-
在归并过程中,为了将最后的排序结果仍置于数组中,需要进行的归并趟数为偶数,
-
如果实际上只需奇数趟即可生成,那么最后还要进行一趟,正好此时temp中的n个有序记录,为一个长度不大于s的表,将会背直接复制r。
5.1 二路归并: 算法
- 代码
public class TestSeqList14_mergepass {
public static void main(String[] args) throws Exception {
int[] arr = {52,39,67,95,70,8,25,52,56};
SeqList seqList = new SeqList(arr.length);
System.out.print("序列号:\t\t ");
for (int i = 0; i < arr.length; i++) {
System.out.print(" " + i);
seqList.insert(i, new RecordNode(arr[i]));
}
System.out.println();
System.out.print("初始值:\t\t ");
seqList.display();
// 一趟归并算法
RecordNode[] temp = new RecordNode[arr.length];
int s = 1;
seqList.mergepass(seqList.r, temp, s, arr.length);
for (int i = 0; i < temp.length; i++) {
String str = temp[i].key.toString().length() == 1 ? " " : " ";
System.out.print(str + temp[i].key.toString());
}
System.out.println();
}
}
//一趟归并算法
//序列号: 0 1 2 3 4 5 6 7 8
//初始值: 52 39 67 95 70 8 25 52 56
//子序列长度s=1 39 52 67 95 8 70 25 52 56
5.2 性能分析
-
时间复杂度:
-
归并趟数:log2n
-
每一趟时间复杂度:O(n)
-
二路归并排序:O(nlog2n)
-
-
空间复杂度:O(n)
-
二路归并是一个==稳定==的排序算法