zoukankan      html  css  js  c++  java
  • 归并排序及数组中逆序对的数量

    归并排序

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

    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 }

     

     

     

  • 相关阅读:
    Java第四十二天,会话内容(二),Cookie(一)
    Java第四十二天,Http协议,Response
    Java第四十二天,Http协议,Request
    Java第四十二天,Http协议,响应消息的数据格式
    Java第四十二天,Http协议,请求消息的数据格式
    Java第四十二天,Servlet系列(五),Servlet 体系
    Java第四十二天,Servlet系列(四),IDEA中 的 Tomcat 配置
    8 pandas实战-美国大选数据分析
    7 Pandas-替换,映射,分组,透视表
    6 pandas人口分析案例
  • 原文地址:https://www.cnblogs.com/dogeLife/p/11428558.html
Copyright © 2011-2022 走看看