归并排序(详情讲解)

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...

  • 步骤:

    1. 根据长度s,进行两两归并

    2. 归并操作时,如果最后两个序列长度不等,将剩余内容归并到有序表中

    3. 归并操作时,如果有未参加归并操作的内容,直接复制到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)

  • 二路归并是一个==稳定==的排序算法

猜你喜欢

转载自blog.csdn.net/lcshen1234/article/details/124866571