归并排序及数组中逆序对的数量

时间:2019-08-29
本文章向大家介绍归并排序及数组中逆序对的数量,主要包括归并排序及数组中逆序对的数量使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

归并排序

首先我们来回忆一下归并排序的主要思想:

  1. 首先考虑如何让将两个有序子序列进行合并?这个方法就是下面代码中的 merge 方法,如果我们能把两个子有序序列合并,那么就可以将短子序列合并为更长的子序列,直到完成源数组的排序。
  2. 如何获取最开始合并的有序子序列呢?通过不断将数组一分为二,直到每个子序列只剩一个元素的时候,子序列就是有序的了,这样一来,我们就可以实现子有序序列的合并了。

具体的理解推荐学习这个视频讲解,老师讲的很仔细,跟着理解一遍自己就能利用熟悉的语言进行实现。

在这个算法里我们需要注意几点:

  1. 我们为了将原数组排序,需要借助一个临时数组 tmp ,两个有序子序列,每次都需要将排好序的元素放在 tmp 里,然后再将元素从 tmp 中捣回原数组,我们可以在合并开始就将 tmp 作为参数传递进去,不要在合并方法里面新建 tmp,那样的话整个算法运行期间会不断声明数组,销毁数组。
  2. 对两个有序子序列进行合并,常规思想都是从头往后比较,谁小就放谁放到 tmp 中,tmp的 下标从头开始;但是对于一些变种,我们就是需要从后往前比较,谁大就把谁放在 tmp 的后面,tmp 下标从尾部开始,比如我们之后介绍的扩展题:求数组中逆序对的数量。就会利用到这个细节。
  3. 算法的最好和最坏时间复杂度都是 O(nlgn),且是稳定的排序。

Java实现

 1 public class MyMergeSort {
 2     public static void mergeSort(int[] nums) {
 3         mSort(nums, new int[nums.length], 0, nums.length-1);
 4     }
 5 
 6     /**
 7      * 先分:一直划分划分到只剩一个元素为一个子序列
 8      * @param nums      原始数组
 9      * @param tmp       临时数组
10      * @param leftStart 左边子序列起始下标
11      * @param rightEnd  右边子序列结束下标
12      */
13     private static void mSort(int[] nums, int[] tmp, int leftStart, int rightEnd) {
14         if (leftStart == rightEnd) {
15             return;
16         }
17         // 一分为二
18         int mid = leftStart + (rightEnd-leftStart)/2;
19         // 分左边子数组
20         mSort(nums, tmp, leftStart, mid);
21         // 分右边子数组
22         mSort(nums, tmp, mid+1, rightEnd);
23         // 合并。
24         merge(leftStart, mid+1, rightEnd, tmp, nums);
25     }
26 
27     /**
28      * 再治:对两个有序子序列进行合并。
29      * @param leftStart  左边子序列的开始下标
30      * @param rightStart 右边子序列的开始下标
31      * @param rightEnd   右边子序列的结束下标
32      * @param tmp         临时数组,前后两个子数组的结果排序结果会保存在 tmp 中
33      * @param nums       原始数组
34      */
35     private static void merge(int leftStart, int rightStart, int rightEnd, int[] tmp, int[] nums) {
36         int leftEnd = rightStart-1;
37         // tmpStart 代表此次元素放在 tmp 的哪个下标位置,
38         int tmpStart = leftStart;
39         int length = rightEnd-leftStart+1;
40         while (leftStart <= leftEnd && rightStart <=rightEnd) {
41             // 降序
42             //tmp[tmpStart ++] = nums[leftStart] > nums[rightStart] ? nums[leftStart++] : nums[rightStart++];
43 
44             // 升序
45             tmp[tmpStart ++] = nums[leftStart] < nums[rightStart] ? nums[leftStart++] : nums[rightStart++];
46         }
47         while (leftStart <= leftEnd) {
48              tmp[tmpStart ++] = nums[leftStart ++];
49         }
50         while (rightStart <= rightEnd) {
51             tmp[tmpStart ++] = nums[rightStart ++];
52         }
53         // 将 tmp 里有序的元素捣回到原数组。
54         for (int i=0; i<length; i++, rightEnd--) {
55             nums[rightEnd] = tmp[rightEnd];
56         }
57     }
58     
59     public static void main(String[] args) {
60         int[] nums = new int[] { 9, 8, 7, 6, 5, 4, 3, 2, 10 };
61         mergeSort(nums);
62         for (int x : nums) {
63             System.out.print(x+ " ");
64         }
65     }
66 }

拓展

求数组中的逆序对

  什么是逆序对呢,对于数组 nums ,若满足

i < j && nums[i] > nums[j]

则称 nums[i] 和 nums[j] 就构成了一对逆序对。举个例子:对于数组:[ 1, 5, 3, 2, 6 ],共存在 3 组逆序对,分别是 [5, 3]、[5,2]、[3,2] 。对于最简单的思路就是用两次 for 循环对每个元素查找其构成的逆序对数量,时间复杂度为O(n^2)。如果借用归并排序的思想,可以达到和归并排序一样的时间复杂度O(nlgn)。

  对原数组进行归并排序,在合并操作的时候就可以获得前后两个子序列的逆序对。这里是从子序列的尾部往前移动的。

Java实现

 1 public class CountReverse {
 2     public static int inversePairs(int[] nums){
 3         if( nums == null ||nums.length <= 1) {
 4             return 0;
 5         }
 6         int[] tmp = new int[nums.length];
 7 
 8         return mergeCount(nums, tmp, 0, nums.length-1);
 9     }
10 
11     public static int mergeCount(int[] nums, int[] tmp, int leftStart, int rightEnd){
12         if(leftStart == rightEnd){
13             tmp[leftStart] = nums[leftStart];
14             return 0;
15         }
16         int mid = leftStart + (rightEnd - leftStart)/2;
17         
18         int leftCount = mergeCount(nums, tmp, leftStart, mid);
19         int rightCount = mergeCount(nums, tmp, mid+1, rightEnd);
20 
21         int leftEnd = mid;// i 初始化为前半段最后一个数字的下标
22 
23         int tmpEnd = rightEnd;//辅助数组复制的数组的最后一个数字的下标
24         
25         int count = 0; //计数--逆序对的数目
26 
27         while(leftStart <= leftEnd && mid+1 <= rightEnd){
28             if(nums[leftEnd] > nums[rightEnd]){
29                 tmp[tmpEnd --] = nums[leftEnd --];
30                 // 因为 是两个有序的子序列。
31                 count += rightEnd - mid;
32             }else{
33                 tmp[tmpEnd--] = nums[rightEnd--];
34             }
35         }
36 
37         while (leftEnd >= leftStart) {
38             tmp[tmpEnd --] = nums[leftEnd --];
39         }
40 
41         while (rightEnd >= mid + 1) {
42             tmp[tmpEnd --] = nums[rightEnd --];
43         }
44         
45         return leftCount + rightCount + count;
46     }
47 
48     public static void main(String[] args) {
49         int[] nums = new int[] {1, 5, 3, 2, 6};
50         System.out.println(inversePairs(nums));
51     }
52 }

原文地址:https://www.cnblogs.com/dogeLife/p/11428558.html