BFPRT算法

BFPRT算法介绍

BFPRT算法(中位数数组的中位数算法),是由五位发明者共同创建,主要解决TopK的问题在使用快速选择方法的最坏情况下的时间复杂度变为O(n^2)的问题,使得在最坏情况下仍为O(n).
中心思想:将数组拆分成每个包含五个元素的数组,将每个数组进行排序选出中位数,这些选出中位数的数组调用BFPRT取得中位数,得出一个用于分区操作的划分值.得出的这个划分值就可以完美的避免在最坏的情况下时间为O(n^2)的情况.

以下编码为了更好的理解.Top 0是指 第一小的数,以此类推.

快速选择方法介绍

快速选择算法本质上和快排算法是一致的.是针对于TopK的问题的一种更好的解决方案.
快排有一种改进方案是:小于放左边,等于放中间,大于放右边.这种改进方案可以在数组当中含有重复值找出正确的TopK.

function quickSelect(list, left, right, k)

   if left = right
      return list[left]

   Select a pivotValue between left and right

   pivotRange := partition(list, left, right, 
                                  pivotValue)  //pivotRange[0] presents left position,pivotRange[1] presents right position
   if k <= pivotRange[1] && k >= pivotRange[0]
      return list[k]
   else if k < pivotRange[0]
      right :=  pivotRange[0] - 1
      quickSelect(list, left, right, k)
   else
      left := pivotRange[1] + 1
      quickSelect(list, left,  right, k)

以上方法快速排序在最坏的情况下退化成了O(n^2)为了更好的解决这种情况,BFPRT算法提出了一个更优的方案.

BFPRT算法实现思路

1.将数组拆分成每个包含五个元素的拆分数组,不够五个元素的单独成为一组
2.将拆分数组进行排序,每个拆分数组选出中位数构成中位数数组,如果拆分数组不够五个且为偶数时选择下中位数.
3.递归调用BFPRT算法求出中位数数组的中位数.(调用BFPRT求出TopK为count(arr)/2的数)
4.得出中位数数组的中位数以该值作为分区的值,进行分区.分区时大于放右边,小于放左边,等于放中间.
5.如果K在返回分区的左右边界范围内,返回数组中K位置的值.如果K小于分区的左边界,那么在分区左边界与当前的左边界继续递归调用BFPRT算法.否则,那么在分区右边界与当前的右边界继续递归调用BFPRT算法

实现源码:



class BFPRT
{
    /**
     * @param $arr array  
     * @param $l int Left position
     * @param $r int Right position
     * @param $pv int Pivot value of partition
     * @return array
     */
    public function partition(&$arr, $l, $r, $pv)
    {
        $left = $l - 1;
        $right = $r + 1;
        $cur = $l;
        while ($cur < $right) {
            if ($arr[$cur] < $pv) {
                $this->swap($arr[++$left], $arr[$cur++]);
            } else if ($arr[$cur] > $pv) {
                $this->swap($arr[--$right], $arr[$cur]);
            } else {
                $cur++;
            }
        }
        return [$left + 1, $right - 1];
    }

    /**
     * 获取中位数数组的中位数
     * @param $arr
     * @return mixed
     */
    public function medianOfMedians($arr)
    {
        $length = count($arr);
        $i = 0;
        $mod = 5;
        $newMidArr = [];
        do {
            $newArr = array_slice($arr, $i, $mod); //这段代码可以优化下
            if ($i + $mod > $length) { //最后一组不足五个的时候slice会产生空元素,要把空元素去掉
                $newArr = array_filter($newArr, function ($val) {
                    return $val !== null;
                });
            }
            sort($newArr);  //排序
            $newMidArr[] = $newArr[intdiv(count($newArr), 2)];//取中位数
        } while (($i+=$mod) && $i < $length);
        return $this->find($newMidArr, 0, count($newMidArr) - 1, intdiv(count($newMidArr), 2));//去中位数数组中第count($newMidArr)/2小的数,也就是中位数数组中的中位数
    }

    /**
     * @param $arr array
     * @param $l int Left position
     * @param $r int Right position
     * @param $k int top k num -> [0, count(arr))
     * @return mixed
     */
    public function find(&$arr, $l, $r, $k)
    {
        if ($l == $r) {
            return $arr[$l];
        }
        $median = $this->medianOfMedians(array_slice($arr, $l, $r - $l + 1));//获取数组的中位数数组的中位数
        $pivotRange = $this->partition($arr, $l, $r, $median);//以median作为划分值
        if ($pivotRange[0] <= $k && $pivotRange[1] >= $k) {//第k小在左右边界内
            return $arr[$k];
        } else if ($k < $pivotRange[0]) {//k小于左边界
            return $this->find($arr, $l, $pivotRange[0] - 1, $k);
        } else {//k大于右边界
            return $this->find($arr, $pivotRange[1] + 1, $r, $k);
        }
    }

    private function swap(&$left, &$right)
    {
        $tmp = $left;
        $left = $right;
        $right = $tmp;
    }
}

你可能感兴趣的:(算法,PHP)