zoukankan      html  css  js  c++  java
  • 求中位数,O(n)的java实现【利用快速排序折半查找中位数】

    查找无序数组的中位数,要想时间复杂度为O(n)其实用计数排序就能很方便地实现,在此讨论使用快速排序进行定位的方法。

    1、中位数定义

    2、算法思想

    3、Java代码实现

    4、时间复杂度分析

    5、附录

    中位数一般两种定义:

    第一种:

    排序后数组的中间位置的值,如果数组的个数是偶数个,则返回排序后数组的第N/2个数。

    第一种(官方):

    排序后数组的中间位置的值,如果数组的个数是偶数个,则返回最中间两个数的平均数。

    例如:{ 7, 9, 4, 5}   第一种输出5;第二种输出6.0

    算法思想:大家应该都知道,快速排序每一躺都能定位一个数在这个数组的最终位置,所以可以利用此特性对数组中任意一个位置进行二分法定位。

        方法就是:一趟快排的partition结束之后,将此时定位的位置与欲定位位置进行比较,如果不等于,则将partition的区间折半,直到等于为止,返回这个位置的值

    Java代码实现

    因为第二种中位数定义需要定位两个位置,在第一种上扩展即可,所以先讨论第一种:

     1     public static int getMedian(int[] nums) {
     2         return partition(nums, 0, nums.length - 1);
     3     }
     4 
     5     private static int partition(int[] nums, int start, int end) {
     6         /***快排partition函数原代码——start***/
     7         int left = start;
     8         int right = end + 1;
     9 
    10         int point = nums[start];
    11         while (true) {
    12             while (left < right && nums[--right] >= point)
    13                 ;
    14             while (left < right && nums[++left] <= point)
    15                 ;
    16             if (left == right) {
    17                 break;
    18             } else {
    19                 int tmp = nums[left];
    20                 nums[left] = nums[right];
    21                 nums[right] = tmp;
    22             }
    23         }
    24         nums[start] = nums[left];
    25         nums[left] = point;
    26         /***快排partition函数原代码——end***/
    27         
    28         /***定位判断***/
    29         if (left == (nums.length - 1) / 2) {
    30             return nums[left];
    31         } else if (left > (nums.length - 1) / 2) {
    32             return partition(nums, start, left - 1);
    33         } else {
    34             return partition(nums, left + 1, end);
    35         }
    36     }

     其实就是在原来的partition结束后加了一个定位判断,此时left指向的就是已经本趟定位的那一个数,如果没有定位成功则将上下界调整折半。

    【注意】:“如果数组的个数是偶数个,则返回排序后数组的第N/2个数”这句话需要用 (nums.length - 1) / 2 来实现这句描述的下标,并满足奇数时取最中间下标的效果。

    时间复杂度分析

    由于此方法采用的也是递归,那么必定符合递归的复杂度通项表达式: T(n) = aT(n/b) + f(n) 

    其中a为每次递归会分成几个需要计算的下一层,(n/b)为下一层计算的元素个数,f(n)为本层的计算复杂度

    由于是折半查找,所以有:a=1、b=2(平均)、f(n)=n(每次的遍历比较交换)

    所以有

    T(n) = T(n/2) +n
            =  T(n/4) + n/2 +n
            ……
            = T(1) + 2 + …… + n/2 +n    // T(1)≈1 等比数列求和
            = (1 - n * 2)/(1 - 2)
            = 2n - 1

    所以最后平均时间复杂度为O(n)

    【最优情况下b=n复杂度O(n);

     最坏情况下b=n-1/n,也就是(n/b)=(n-1),此时复杂度为O(n²),请自行计算哈】

    附录——第二种求中位数的实现

    思路:第一种已经解决了定位一个数字,而第二种就是定位两个数字,由于定位一个数字的时候不能保证另一个数字已经排序好,所以还需重新调用方法

      那么就把方法中定位判断的部分单独移出来做一个getByQuickSort(int[] nums,int stop)

    java代码实现:

     1     public static double getByQuickSort(int[] nums, int stop) {
     2         if (stop < 0 || stop >= nums.length) {
     3             throw new IndexOutOfBoundsException();
     4         }
     5         
     6         int start = 0;
     7         int end = nums.length -1;
     8         int par = 0;
     9         
    10         while (start <= end) {
    11             par = partition(nums, start, end);
    12             if (par == stop) {
    13                 break;
    14             } else if (par > stop) {
    15                 end = par - 1;
    16             } else {
    17                 start = par + 1;
    18             }
    19         }
    20         return nums[par];
    21     }

    此处的partition(...)方法就是上一段代码中的partition方法中把 /***定位判断***/ 以下都去掉,然后加一个 return left; 即可。

    而找中位数就再写一个方法getMedian2(...)判断一下奇偶,再调用getByQuickSort(....)就可以了:

    1     public static double getMedian2(int[] nums) {
    2         if (nums.length % 2 == 1) {
    3             return getByQuickSort(nums, nums.length / 2);
    4         } else {
    5             return (getByQuickSort(nums, nums.length / 2 - 1) + 
    6                     getByQuickSort(nums, nums.length / 2)) / 2.0;
    7         }
    8     }
  • 相关阅读:
    How To Compile Qt with Visual Studio 2010
    VCL线程的同步方法 Synchronize(用消息来同步)
    Delphi中怎么结束线程(这个线程是定时执行的)(方案二)
    编程之美 寻找数组中的最大值和最小值
    Delphi中怎么结束线程(这个线程是定时执行的)(方案一)
    Delphi线程同步(临界区、互斥、信号量,包括详细代码)
    Delphi管理多线程之线程局部存储:threadvar
    Delphi之通过代码示例学习XML解析、StringReplace的用法(异常控制 good)
    Delphi的文件操作(定义,关联,打开,读写,关闭)
    Android 中单位讲解
  • 原文地址:https://www.cnblogs.com/Xieyang-blog/p/8360948.html
Copyright © 2011-2022 走看看