先说明一下,这里所说的是“线性查找”,而不是“顺序查找”。线性查找算法又叫BFPRT算法,即Blum、Floyd、Pratt、Rivest、Tarjan等5个作者的名字的缩写,Blum、Floyd、Rivest、Tarjan这四个作者都是图灵奖得主,可见这个算法的份量。
在具体介绍线性查找算法之前,我们先来看看它的用途:解决Top-K问题。
Top-K问题:如果数组arr中的数据不是有序的,怎么样才能找第k大的数?下面是三种解决方案:
1、用选择排序的思想,对数组进行部分排序,重复k次之后找出最大的k个数据,则arr[k]就是第k大的数。这样做的时间复杂度为O(N * K)。
2、直接对整个数组进行排序,arr[k]就是第k大的数。这样做的时间复杂度为O(N * logN)。这种方式对其余n - k个数据进行排序,但这些数据是没有必要排序的。对这些数据的排序浪费了很多时间。
3、借助于堆排序,建立一个有k个数组成的小顶堆。然后遍历剩余元素,跳过比堆顶元素小的数,如果被遍历的元素比堆顶元素大,就用这个数取代堆顶元素,并重新维护这个堆。这样做的时间复杂度为O(N * logK)。这种方式只对k个元素进行排序,所以时间复杂度就比较小了。这种方式中,k个元素的堆仍然是处于总体有序的状态。
上面这三种方法都进行了排序,或者部分排序。所以导致算法的时间复杂度都比较大。有没有办法让top-K问题的时间复杂度降低到O(N) 呢?这时,BFPRT等五位大神就出场了。
在讨论BFPRT算法之前,我们先回忆一下原始的快速排序算法:快速排序算法最神奇的地方是在一轮扫描之后可以让基准数找到它的正确位置 (standardIndex),而且这个基准数据可以不是最大值或者最小值。这样,待排序的数组就被分成了三块。接着分别在左边和右边两段数组中继续递归查找基准数的正确位置即可。
如果我们把快速排序法用在top-K问题里该怎么做:取一个基准数,然后扫描整个数组,找到这个基准数的正确位置 (standardIndex),如果standardIndex正好是整个数组中的第k个位置,那么这个基准数就是我们希望找的第k大的值。如果standardIndex < k,那就继续在standardIndex右边找;如果standardIndex > k,那就继续在standardIndex左边找。先不考虑这种查找方式的效率怎么样,我们至少能找到第k大的数了。实现代码如下:
#include <stdio.h>
#include <stdlib.h>
int arr[] = {
3, 6, 1, 8, 2, 5, 9, 4, 7 }; //无序的数组
int size = 0; //数组的大小,需要重新计算
void swap(int a, int b)
{
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//快速排序的划分过程
int Partion(int low, int high)
{
if (low > high) //递归的出口
{
return -1;
}
int standard = arr[low]; //最左边的元素作为基准
int i = low; //确定范围
int j = high;
// 将大于基准数的元素交换到基准数的左边,小于基准数的元素交换到基准数的右边
while (i < j)
{
while (i < j && arr[j] <= standard)
{
j--;
}
while (i < j && arr[i] >= standard)
{
i++;
}
if(i < j) //东东和虫虫没有相遇
{
swap(i, j);
}
}
//东东和虫虫相遇后
arr[low] = arr[i];
arr[i] = stand