求两个有序数组合并后的上中位数的非递归算法实现 - JAVA版
作者:互联网
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