zoukankan      html  css  js  c++  java
  • 经典算法 BFPRT算法详解

    内容:

    1、原始问题     =》  O(N*logN)

    2、BFPRT算法    =》 O(N)

    1、原始问题

    问题描述:给你一个整型数组,返回其中第K小的数

    普通解法:

    这道题可以利用荷兰国旗改进的 partition 和随机快排的思想:随机选出一个数,将数组以该数作比较划分为 <,=,> 三个部分,

    则 = 部分的数是数组中第几小的数不难得知,接着对 < (如果第K小的数在 < 部分)或 > (如果第K小的数在 > 部分)部分的数

    递归该过程,直到 = 部分的数正好是整个数组中第K小的数。这种做法不难求得时间复杂度的数学期望为 O(NlogN) (以2为底)。

    但这毕竟是数学期望,在实际工程中的表现可能会有偏差(最坏情况下的时间复杂度会达到O(N^2))

    BFPRT算法可以说是这种算法的一种优化吧,故在此就不写这种解法的代码了

    另外一种普通解法:

    用堆去做,时间复杂度是靠谱的O(N*logk)

    代码如下:

     1     // 大根堆比较器
     2     public static class MaxheapComparator implements Comparator<Integer> {
     3         @Override
     4         public int compare(Integer o1, Integer o2) {
     5             return o2 - o1; 
     6         }
     7     }
     8 
     9     // O(N*logk)的解法
    10     public static PriorityQueue getMinKNumsByHeap(int[] arr, int k) {
    11         if (k < 1 || k > arr.length) {
    12             return null;
    13         }
    14         PriorityQueue<Integer> kHeap = new PriorityQueue<Integer>(k,
    15                 new MaxheapComparator());
    16         for (int i = 0; i != k; i++) {
    17             kHeap.add(arr[i]);
    18         }
    19         for (int i = k; i != arr.length; i++) {
    20             if (arr[i] < kHeap.peek()) {
    21                 kHeap.poll();
    22                 kHeap.add(arr[i]);
    23             }
    24         }
    25         return kHeap;
    26     }
    27 
    28     public static void main(String[] args) {
    29         int[] arr = { 1, 3, 2, 5, 9 };
    30         // 测试普通方法
    31         System.out.println(getMinKNumsByHeap(arr, 1).peek());
    32         System.out.println(getMinKNumsByHeap(arr, 2).peek());
    33         System.out.println(getMinKNumsByHeap(arr, 3).peek());
    34         System.out.println(getMinKNumsByHeap(arr, 4).peek());
    35         System.out.println(getMinKNumsByHeap(arr, 5).peek());
    36     }

    2、BFPRT算法

    BFPRT算法能够做到时间复杂度就是 O(N)    BFPRT算法,接收一个数组和一个K值,返回数组中的一个数

    1. 数组被划分为了 N/5 个小部分,每个部分的5个数排序需要 O(1) ,所有部分排完需要 O(N/5)=O(N)

    2. 取出每个小部分的中位数,一共有 N/5 个,递归调用BFPRT算法得到这些数中第 (N/5)/2 小的数(即这些数 的中位数),记为 pivot

    3. 以 pivot 作为比较,将整个数组划分为 <pivot , =pivot , >pivot 三个区域

    4. 判断第K小的数在哪个区域,如果在 = 区域则直接返回 pivot ,如果在 < 或 > 区域,则将这个区域的数递 归调用BFPRT算法

    5. base case :在某次递归调用BFPRT算法时发现这个区域只有一个数,那么这个数就是我们要找的数

     1     // O(N)的解法
     2     public static int getMinKthNum(int[] arr, int k){
     3         if(arr==null||k>arr.length){
     4             return Integer.MIN_VALUE;
     5         }
     6         int[] copyArr = Arrays.copyOf(arr, arr.length);
     7         return BFPRT(copyArr, 0, arr.length-1, k-1);
     8     }
     9     
    10     private static int[] partition(int[] arr, int begin, int end, int pivot){
    11         int L = begin-1;
    12         int R = end + 1;
    13         int cur = begin;
    14         while(cur!=R){
    15             if(arr[cur]>pivot){
    16                 swap(arr, cur, --R);
    17             } else if(arr[cur]<pivot){
    18                 swap(arr, cur++, ++L);
    19             } else{
    20                 cur++;
    21             }
    22         }
    23         return new int[]{L+1, R-1};
    24     }
    25     
    26     private static int BFPRT(int[] arr, int begin, int end, int i) {
    27         if (begin == end) {
    28             return arr[begin];
    29         }
    30         int pivot = medianOfMedians(arr, begin, end);
    31         int[] pivotRange = partition(arr, begin, end, pivot);
    32         if(i>=pivotRange[0]&&i<=pivotRange[1]){
    33             return arr[i];
    34         } else if(i<pivotRange[0]){
    35             return BFPRT(arr, begin, pivotRange[0]-1, i);
    36         } else{
    37             return BFPRT(arr, pivotRange[1] + 1, end, i);
    38         }
    39     }
    40 
    41     private static int medianOfMedians(int[] arr, int begin, int end) {
    42         int num = end - begin + 1;
    43         int offset = num % 5 == 0 ? 0 : 1;
    44         int[] medians = new int[num / 5 + offset];
    45         for (int i = 0; i < medians.length; i++) {
    46             int beginI = begin + i * 5;
    47             int endI = beginI + 4;
    48             medians[i] = getMedian(arr, beginI, Math.min(endI, end));
    49         }
    50         return BFPRT(medians, 0, medians.length - 1, medians.length / 2);
    51     }
    52     
    53     private static int getMedian(int[] arr, int begin, int end){
    54         insertionSort(arr, begin, end);
    55         int sum = end + begin;
    56         int mid = (sum/2) + (sum%2);
    57         return arr[mid];
    58     }
    59     
    60     private static void insertionSort(int[] arr, int begin, int end){
    61         if(begin>=end){
    62             return;
    63         }
    64         for(int i=begin+1;i<=end;i++){
    65             for(int j=i;j>begin;j--){
    66                 if(arr[j]<arr[j-1]){
    67                     swap(arr, j, j-1);
    68                 } else{
    69                     break;
    70                 }
    71             }
    72         }
    73     }
    74     
    75     private static void swap(int[]arr , int i, int j){
    76         int tmp = arr[i];
    77         arr[i] = arr[j];
    78         arr[j] = tmp;
    79     }
  • 相关阅读:
    LeetCode(65) Valid Number
    LeetCode(57) Insert Interval
    Python 之scrapy框架58同城招聘爬取案例
    Python 之12306网站验证码校验案例
    Python 之selenium+phantomJS斗鱼抓取案例
    Python 之pytesseract模块读取知乎验证码案例
    Python 之糗事百科多线程爬虫案例
    Python 之beautifulSoup4解析库
    Python 之lxml解析库
    把一张的数据添加到另一张中
  • 原文地址:https://www.cnblogs.com/wyb666/p/10269461.html
Copyright © 2011-2022 走看看