BFPTR算法

BFPTR算法,又称为中位数的中位数算法,它的最坏时间复杂度为O(n)

TOP-K问题

  1. 将n个数排序(比如快速排序或归并排序),选取排序后的第k个数,时间复杂度为O(nlogn)。
  2. 维护一个k个元素的最大堆,存储当前遇到的最小的k个数,时间复杂度为O(nlogk)。
  3. 部分的选择排序,即把最小的放在第1位,第二小的放在第2位,直到第k位为止,时间复杂度为O(kn)
  4. 部分的快速排序(快速选择算法),每次划分之后判断第k个数在左右哪个部分,然后递归对应的部分,平均时间复杂度为O(n)。但最坏情况下复杂度为O(n^2)。

固定选择首元素或尾元素作为主元。
随机选择一个元素作为主元。
三数取中,选择三个数的中位数作为主元。一般是首尾数,再加中间的一个数或者随机的一个数。

  1. BFPRT算法,修改快速选择算法的主元选取规则,使用中位数的中位数作为主元,最坏情况下时间复杂度为O(n)。

BFPTR算法

在BFPTR算法中,每次选择五分中位数的中位数作为pivot,这样做的目的就是使得划分比较合理,从而避免了最坏情况的发生。算法步骤如下:

  1. 将输入数组的 n 个元素划分为 n/5 组,每组5个元素,且至多只有一个组由剩下的 n%5 个元素组成。
  2. 寻找 n/5 个组中每一个组的中位数,首先对每组的元素进行插入排序,然后从排序过的序列中选出中位数。
  3. 对于(2)中找出的 n/5 个中位数,递归进行步骤(1)和(2),直到只剩下一个数即为这 n/5 个元素的中位数,找到中位数后并找到对应的下标 P。
  4. 进行Partion划分过程,Partion划分中的pivot元素下标为 P。
  5. 进行高低区判断即可。
/**
 * -在数组中选出第k大元素平均复杂度为O(N)的算法,俗称”中位数之中位数算法”
 */
public class BFPTR {
	int scope = 5;
	public static void main(String[] args) {
		BFPTR app = new BFPTR();
		int[] arr = new int[]{5,6,8,7,3,2,1,4,10,20,39,12,21,19,27,23,25,6,7};
		app.bfptrSort(arr);
        for(int i=0;i<arr.length;i++){
            System.out.print(arr[i] + " ");
        }
	}
	public int[] bfptrSort(int[] arr){
        return bfptrSort(arr,0,arr.length-1);
    }
	public int[] bfptrSort(int[] arr,int s,int e){
        if(s<e){
            int king = findKing(arr,s,e);
            int p = partition(arr,s,e,king);
            bfptrSort(arr,s,p-1);
            bfptrSort(arr,p+1,e);
        }
        return arr;
    }
	public int findKing(int[] arr,int s,int e){
        int[] medias = findMedias(arr,s,e);
        insertSort(medias,0,medias.length-1);
        int king = medias[(medias.length-1) >> 1];
        return king;
    }
	/**
	 * -对{arr[e]~arr[s]}这一段分组求中为数
	 */
	public int[] findMedias(int[] arr,int s,int e){
        //通过给定的start和end,先定义好有几个中位数
        int[] res = new int[(int)Math.ceil((e-s+1.0)/scope)];
        int k=0;

        //如果e-s<scope说明可以直接对arr的s~e这一段进行快排,从
        //而得到中位数;
        if(e-s<scope){
            insertSort(arr,s,e);
            res[k++] = arr[(s+e) >> 1];
        }
        else{
            //如果e-s>=scope,那么设置步长为scope的窗口,逐步进
            //行寻找每一段的中位数。
            int i=s,j=0,splitStart =0 ,splitEnd = 0;
            while(i++<=e){
                if(++j == scope){
                    // 窗口的起始位置;
                    splitStart = k*scope+s;  
                    //窗口的结束位置;
                    splitEnd = (k+1)*scope+s-1;                         
                    insertSort(arr,splitStart,splitEnd);
                    res[k++] = arr[(splitStart+splitEnd) >> 1];
                    j=0;
                }
            }
            //说明(e-s)%scope !=0, 需要把最后一小段加进去
            if(splitEnd < e){
                insertSort(arr,splitEnd+1,e);
                res[k++] = arr[(splitEnd+1+e) >> 1];
            }
        }
        return res;
    }
	/**
	 * -快排,对{arr[s]~arr[e]}这一段进行快排
	 */
	public int[] insertSort(int[] arr,int s,int e){
        for(int i=s;i<=e;i++){
            int key = i;
            for(int j=i-1;j>=s;j--){
                if(arr[key] < arr[j]){
                    swap(arr,key,j);
                    key --;
                }
                else{
                    break;
                }
            }
        }
        return arr;
    }
	public void swap(int[] arr,int i,int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] =tmp;
    }
	/**
	 * -给定一个king,对于{arr[s]~arr[e]}这一段进行partition
	 */
	public int partition(int[] arr,int s,int e,int king){
        int i = s;
        int j = e;
        int kingIndex = 0;

        for(int k =s;k<=e;k++){
            if(arr[k]==king){
                kingIndex = k;
                break;
            }
        }
        while(i<j){
            while(arr[j]>=king && j>kingIndex){
                j--;
            }
            swap(arr,j,kingIndex);
            kingIndex=j;
            while(arr[i]<= king && i<kingIndex){
                i++;
            }
            swap(arr,i,kingIndex);
            kingIndex=i;
        }
        return i;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值