ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

求两个有序数组合并后的上中位数的非递归算法实现 - JAVA版

2019-06-21 10:38:28  阅读:303  来源: 互联网

标签:JAVA 递归 start1 tempArr1 中位数 tempArr2 数组 mid2


package test;

public class FindMedian {
    // 求两个有序数组合并后的上中位数。折半方法(二分查找),时间复杂度为O(logN),其中N是小数组的长度
    // 中位数特性:1、数组一半不超过该值,一半不小于该值;2、从首尾各删除相同个数元素,中位数不变
    public static int findMedianNum(int[] arr1, int[] arr2) throws RuntimeException{
        // 判断存在空数组的情况,直接返回结果
        if (arr1 == null && arr2 == null) throw new RuntimeException("null arr");
        // 因为数组下标是从0开始,所以上中位数的下标为(startIndex + endIndex)/2,如一个长度为12的数组,中位数下标为(0+11)/2=5,即第六个元素
        else if (arr1 == null || arr1.length == 0) return arr2[(arr2.length - 1)/2];
        else if (arr2 == null || arr2.length == 0) return arr1[(arr1.length -1 )/2];
        // 将长度小的数组放在前面,便于判断和二分,因为每次两个数组都要减掉小数组长度的二分之一
        int[] tempArr1 = arr1, tempArr2 = arr2;
        if (arr1.length > arr2.length) {tempArr1 = arr2; tempArr2 = arr1;} 
        int length1 = tempArr1.length, length2 = tempArr2.length;
        int start1 = 0, end1 = length1 - 1, start2 = 0, end2 = length2 - 1;
        int mid1, mid2, subLength;
        while (start1 < end1) { // 每次都会缩小数组比对的范围
            mid1 = (start1 + end1)/2; mid2 = (start2 + end2)/2; // 待比对数组范围的中位数下标
            subLength = length1/2; // 缩减的范围长度:小数组长度的二分之一
            if (tempArr1[mid1] == tempArr2[mid2]) {
                return tempArr1[mid1]; // 如果两个数组的中位数相等,那么这个值也是整个合并后数组的中位数
            } else if (tempArr1[mid1] < tempArr2[mid2]) { // 说明合并后的中位数在tempArr1的后半部分或者tempArr2的前半部分
                // 虽然可以确定在两个数组的哪半段,但是不能单纯各减一半,而是两个数组缩小相同的长度(小数组长度的一半),否则中位数的位置就发生偏移了。
                // 注:是从中位数不在的那半边中减去对应长度。
                start1 = start1 + subLength; end2 = end2 - subLength;
                length1 = length1 - subLength; length2 = length2 - subLength;
            } else if (tempArr1[mid1] > tempArr2[mid2]) { // 说明合并后的中位数在tempArr1的前半部分或者tempArr2的后半部分
                end1 = end1 - subLength; start2 = start2 + subLength; 
                length1 = length1 - subLength; length2 = length2 - subLength;
            }
        }
        // 当start1=end1时,退出循环,即短的数组里只剩下了一个数。此时用这个数跟长数组剩余元素的中位数做比对,以确定最终中位数
        mid2 = (start2 + end2)/2;
        if (length1 == length2) { // 两个数组长度都剩1,元素值小的那个是前中位数
            return tempArr1[start1] < tempArr2[start2] ? tempArr1[start1] : tempArr2[start2];
        } else if (length2 % 2 == 0) { 
            // 如果长数组剩余长度为偶数,则合并后长度是奇数,所以应在tempArr1[start1]、tempArr2[mid2]、tempArr2[mid2+1]三个数中找中间值,其中后两者之间有序
            if (tempArr1[start1] >= tempArr2[mid2+1]) return tempArr2[mid2+1];
            else if (tempArr1[start1] <= tempArr2[mid2]) return tempArr2[mid2];
            else return tempArr1[start1];
        } else { // length2 % 2 == 1的时候
            // 如果长数组剩余长度为奇数,则合并后长度是偶数,所以应在tempArr1[start1]、tempArr2[mid2-1]、tempArr2[mid2]三个数中找中间值,其中后两者之间有序
            if (tempArr1[start1] >= tempArr2[mid2]) return tempArr2[mid2];
            if (tempArr1[start1] <= tempArr2[mid2-1]) return tempArr2[mid2-1];
            else return tempArr1[start1];
        }
    }
    
    // 求两个有序数组合并后的上中位数。采用合并排序找第n小的方法,时间复杂度为O((length1 + length2)/2)
    public static int findMedianNum2(int[] arr1, int[] arr2) throws RuntimeException{
        // 判断存在空数组的情况,直接返回结果
        if (arr1 == null && arr2 == null) throw new RuntimeException("null arr");
        // 因为数组下标是从0开始,所以上中位数的下标为(startIndex + endIndex)/2,如一个长度为12的数组,中位数下标为(0+11)/2=5,即第六个元素
        else if (arr1 == null || arr1.length == 0) return arr2[(arr2.length - 1)/2];
        else if (arr2 == null || arr2.length == 0) return arr1[(arr1.length -1 )/2];
        
        int length1 = arr1.length, length2 = arr2.length;
        int[] conArr = new int[length1 + length2]; // 合并后的数组容器,如果单纯求中位值,这个容器可有可无
        int midIndex = (arr1.length + arr2.length - 1)/2; // 因为index从0开始,所以分子需要减1
        int i = 0, j = 0, k = 0; // i为arr1下标,j为arr2下标,k为合并数组下标
        while (i < length1 && j < length2) {
            if (arr1[i] <= arr2[j]) { // 如果有合并后数组容器,此时应将arr1[i]放入数组
                conArr[k] = arr1[i];
                if (k == midIndex) return arr1[i]; // k为本次遍历完成后,合并数组的下标
                i++; k++; // 下次遍历,合并数组和分数组的下标
            } else { // 如果有合并后数组容器,此时应将arr2[j]放入数组
                conArr[k] = arr2[j];
                if (k == midIndex) return arr2[j];
                j++; k++;
            }
        }
        // 如果其中一个数组已经遍历完还累计到midIndex,则继续遍历剩下的那个数组
        if (i == length1) {
            while (j < length2) {
                conArr[k] = arr1[i];
                if (k == midIndex) return arr2[j];
                j++; k++;
            }
        } else {
            while (i < length1) {
                conArr[k] = arr2[j];
                if (k == midIndex) return arr1[i];
                i++; k++;
            }
        }
        // 按照逻辑,上面的循环内一定会方法返回。此处的return只是为了让代码能够编译通过,实际不会执行到。
        return 0;
    }

    public static void main(String[] args) {
        int[] arr1 = new int[] {0,1,2,3,4,7,10};
        int[] arr2 = new int[] {5,6,7,8,9,11};
        System.out.println(FindMedian.findMedianNum(arr1, arr2));
        System.out.println(FindMedian.findMedianNum2(arr1, arr2));
    }

}

算法思想参考了:

https://www.cnblogs.com/TenosDoIt/p/3554479.html

https://blog.csdn.net/zuochao_2013/article/details/80031530

这两位博主都是使用递归方式实现的,我是使用非递归的方式实现的,但其实算法底层的逻辑是一样的。以上代码经过了简单测试,但由于本人水平有限,有可能会有没考虑到的细节或者漏洞,欢迎大家交流指正!

标签:JAVA,递归,start1,tempArr1,中位数,tempArr2,数组,mid2
来源: https://www.cnblogs.com/oceanbaxia/p/11063131.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有