逆序对的归并求解

什么是逆序对?

数组中任意两个元素,凡是满足左边数字大于右边的,就构成一个逆序对。例如【5,9,3,6,8,10,1,4】 (10,1)就构成一个逆序对。
本文将介绍使用归并算法来求解逆序对的相关问题。

如何求逆序对呢?

方法一:暴力法

最先想到的就是暴力法,双重for循环判断即可,这种方式就不在介绍了。

方法二:归并算法

求之前,我们先理解一下归并排序算法。一定要先理解归并排序算法,能理解归并排序算法了,那么求逆序对的问题就很简单,就是在归并排序算法上加上几行代码。

归并排序算法

归并排序算法,主要就是采用分治法的思想,将数组分解,分解的子序列长度为1时,这个子序列就是有序的,然后将分解之后的子序列两两进行合并即可。
详细的归并排序算法可以查看其他资料。
上代码:

//使用递归的方式,将待排序的数组进行分解
    public void mergeSort(int[] a,int low,int high){
       if (low < high) {
           //递归分解,右移一位,相当除2
           int mid = (low + high) >> 1;
           mergeSort(a, low, mid);
           mergeSort(a, mid + 1, high);
           //合并
           merge(a, low, mid, high);
       }
   }
   //将两个子序列归并成一个子序列
   public void merge(int[] a,int low,int mid,int high){
       int[] temp = new int[a.length];
       int pa = low;
       int pb = mid+1;
   	//新的数组,用来临时存放两个子序列的归并结果
       int pt = 0;

       while(pa <= mid && pb <= high){
//左侧子序列 取出来一个值 插入到新数组
           if (a[pa] < a[pb]){
               temp[pt] = a[pa];
               pa++;
               pt++;
           }else{
//左侧子序列 取出来一个值 插入到新数组
               temp[pt] = a[pb];
               pb++;
               pt++;
           }
       }
       //处理数据有剩余
       //左侧子序列有剩余
       while(pa <= mid){
           temp[pt] = a[pa];
           pa++;
           pt++;
       }
       //右侧子序列有剩余
       while(pb <= high){
           temp[pt] = a[pb];
           pb++;
           pt++;
       }
//将临时存放结果 保存到原数组中
       for (int i = 0 ; i < pt ; i++){
           a[low+i] = temp[i];
       }
   }

//主函数测试 注:Print是自定义的一个打印数组元素的方法
public static void main(String[] args) {
       int[] b = {5,9,3,6,8,10,1,4};
       TestSort testSort = new TestSort();
       System.out.println("排序前:");
       Print(a);
   testSort.mergeSort(a,0,a.length-1);
       System.out.println("排序后:");
       Print(a);
   }

分析:归并算法其实就是分解然后在合并,这其中有一点,就是准备合并的两个子序列是已经有序的,合并的时候,将两个子序列中的最小值放进新的临时数组中,这样就能完成两个子序列合并之后的序列是有序的。于是我们可以利用这一点来求解逆序对的问题。

归并算法求解逆序对

问题分析

回归正题,我们相求逆序对,那就是左边的数值大于右边的数值就行,那我们看一下当归并排序算法在合并两个子序列的时候。如下图:
归并排序算法合并两个子序列分析
我们发现,当合并两个子序列的时候,当右侧子序列的值能插入临时数组时,那么左侧子序列中的值都要比右侧子序列准备插入的值要大,这样左侧子序列的所有值与右侧当前这个值一组合,那不就构成了逆序对嘛(左大右小)。
比如上图:右侧子序列此时能插入1到临时数组中,那么此时,左侧序列的所有值3、4、6、9都比1大,这样一组合,那逆序对不就是(3,1)(4,1)(6,1)(9,1) 。请结合上图仔细思考,只要能理解这一点,那么逆序对的问题就好理解了。

问题解决

通过上面的分析,在合并两个子序列时,我们可以在右侧子序列插入临时数组直接加上一些操作,比如,使用for循环将左侧子序列的值打印出来。
以下就是我们新增加的代码。完整代码见代码实例。

//遍历左侧子序列的所有值
 for (int i = pa; i <= mid; i++) {
	System.out.println("("+a[i]+","+a[pb]+")");
 }

代码实例

求数组中所有的逆序对代码:

    //递归(分解序列)
    public void mergeSort(int[] a,int low,int high){
        if (low < high) {
            //递归分解,右移一位,相当除2,小了一倍
            int mid = (low + high) >> 1;
            mergeSort(a, low, mid);
            mergeSort(a, mid + 1, high);
            //合并
            merge(a, low, mid, high);
        }
    }
    //归并(合并序列)
    public void merge(int[] a,int low,int mid,int high){
        int[] temp = new int[a.length];
        //左侧子序列的头部
        int pa = low;
        //左侧子序列的头部
        int pb = mid+1;
        //临时数组的头部
        int pt = 0;

        while(pa <= mid && pb <= high){
            if (a[pa] < a[pb]){
                temp[pt] = a[pa];
                pa++;
                pt++;
            }else{
                //可以求出所有的逆序对。
                for (int i = pa; i <= mid; i++) {
                System.out.println("("+a[i]+","+a[pb]+")");
                }

                temp[pt] = a[pb];
                pb++;
                pt++;
            }
        }
        //处理数据有剩余
        //pa有剩余
        while(pa <= mid){
            temp[pt] = a[pa];
            pa++;
            pt++;
        }
        //pb有剩余
        while(pb <= high){
            temp[pt] = a[pb];
            pb++;
            pt++;
        }
        for (int i = 0 ; i < pt ; i++){
            a[low+i] = temp[i];
        }
    }

扩展内容

文章到了这里,就求出了数组中的所有逆序对了。
那么为什么写这篇文章呐,我遇到一个面试题,是求给定的一个数组中的最大逆序对的。因此写这篇文章记录一下。

请写一段函数代码,输入一个数组,比如【5,9,3,6,8,10,1,4】找到数组中“加和”最大的“逆序对”。

其实思想是一样的,只不过是不需要将逆序对全输出出来了,求个加和最大就行了。
上代码:

while(pa <= mid && pb <= high){
            if (a[pa] < a[pb]){
                temp[pt] = a[pa];
                pa++;
                pt++;
            }else{
//                遍历所有的逆序对。
//                for (int i = pa; i <= mid; i++){ 
//					System.out.println("("+a[i]+","+a[pb]+")");
                }
                //优化,直接加上左序列的末尾元素(末尾元素最大)
                if (a[mid]+ a[pb] > (Integer) map.get("max")){
                    map.put("max",a[mid]+ a[pb]);
                    map.put("value","("+a[mid]+","+a[pb]+")");
                }
                temp[pt] = a[pb];
                pb++;
                pt++;
            }
        }

总结

以上就是我所遇到的问题,希望能对大家有所帮助,其实重点就是向弄清楚归并排序算法,剩下的就是该算法的演进和扩展。
仓促成文,如有纰漏,还请各位读者指出,如有其他问题,可以在下方留言,我看到了会和大家讨论解决的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值