算法分析-TOP-K问题-BFPRT算法

本文介绍了解决Top-K问题的有效算法——BFPRT算法(中位数的中位数算法)。该算法通过分组求中位数的方式,在最坏情况下也能达到线性时间复杂度O(n)。文章详细解释了算法的具体步骤,并提供了Java实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于Top-K问题,我们最容易想到的常规算法是是先排序,再返回第K个元素,快速排序的平均复杂度为O(nlogn),最坏时间复杂度为O(n2),不能始终保证较好的复杂度。这里我们只需要Top-K个元素或者是第k个元素,对其他元素并不关心,对n个数全部进行排序显然是不合理的,那么有没有一种更快的算法呢?

目前解决TOP-K问题最有效的算法即是BFPRT算法,其又称为中位数的中位数算法,该算法由Blum、Floyd、Pratt、Rivest、Tarjan提出,最坏时间复杂度为O(n)


具体步骤:

1、将n个数分成n/5个组,每组5个元素,剩余的元素舍弃,对这五个元素进行排序(数量较少一般采用插入排序)

2、对每组的5个数求中位数,然后求中位数的中位数,实现起来通常将每组的中位数与前面的n/5个数交换,这样中位数又是连续的了,可以递归继续求中位数,直到最后求得一个数,记住这个数及其位置

3、运用快速排序的思想以这个中位数作为主元(pivot),进行快速交换排序,这样此数组刚好被这个主元分成两部分,一部分大于这个主元,一部分小于这个主元,记住主元的位置pos。

4、如果这个pos == k,则直接返回pos对应的值

     如果pos<k,则 返回到第1步,递归调用,只用算数组left~pos-1的部分

     如果pos>k,则 返回到第1步,递归调用,只用算数组pos+1~right的部分

import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
 * 
 * 
7 2
2 3 4 7 5 9 1
 *
 */
public class TopK_BFPRT {


  static int N;
  static int K;
  static int[] array;
  public static void main(String[] args) throws Exception {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String line = br.readLine();
    String[] sa = line.split(" ");
    N = Integer.valueOf(sa[0]);
    K = Integer.valueOf(sa[1]);
    line = br.readLine();
    sa = line.split(" ");
    array = new int[N];
    for(int i=0; i<N; i++)
      array[i] = Integer.valueOf(sa[i]);
    System.out.println(bfprt(0, N-1, K));
  }
 /**
 * bfprt中位数的中位数算法
 * @param left
 * @param right
 * @param k
 * @return
 */
  private static int bfprt(int left, int right, int k){
    int midPos = getMidPos(left, right);
    int value = partition(left, right, midPos)+1;
    if(value == k){
      return array[k-1];
    }else if(value > k){
      return bfprt(left, value-2, k);
    }else{
      return bfprt(value, right, k);
    }
  }
  /**
   * 快速交换
   * @param left
   * @param right
   * @param midpos
   * @return
   */
  private static int partition(int left, int right, int midpos){
    int pivot = array[midpos];
    swap(midpos, left);
    int l = left;
    int r = right;
    while(l<r){
      if(array[l] < array[r]){
        r--;
      }else{
        swap(l, r);
        if(pivot == array[l])
          r--;
        else
          l++;
      }
    }
    return l;
  }
  /***
   * 求中位数的中位数
   * @param left
   * @param right
   * @return
   */
  private static int getMidPos(int left, int right){
    if(right - left <= 5)
      return insertSort(left, right);
    int bleft = left;
    for(int i=left; i+4<=right; i+=5){
      int midv = insertSort(i, i+4);
      swap(bleft++, midv);
    }
    return getMidPos(left, bleft);
  }
  /**
   * 插入排序
   * @param left
   * @param right
   * @return
   */
  private static int insertSort(int left, int right){
    for(int i = left + 1; i<=right; i++){
      for(int j = i; j > left; j--){
        if(array[j] < array[j-1]){
          swap(j, j-1);
        }
      }
    }
    return (left+right)/2;
  }
  /**
   * 数据交换
   * @param pos1
   * @param pos2
   */
  private static void swap(int pos1, int pos2){
    int temp = array[pos1];
    array[pos1] = array[pos2];
    array[pos2] = temp;
  }
}

时间复杂度分析:

求中位数的中位数,首先用到了插入排序,插入排序5个数的时间复杂度为10,分成n/5组,约为O(2n+2n/5+2n/25+.....+1)约等于O(2.5n)

交换快速排序:O(n)

一次递归时间为O(2.5n)+O(n) = O(3.5n)然后二分求值O(3.5n)+O(3.5n/2)+O(3.5n/4)+......+1 约等于O(5n)由于5是常数所有时间复杂度为O(n).



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值