线性查找算法
定义
BFPRT 算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分析,BFPRT 可以保证在最坏情况下仍为线性时间复杂度。该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂度,五位算法作者做了精妙的处理。
步骤
1.将n个元素每 5 个一组,分成n/5(上界)组。
2. 取出每一组的中位数,任意排序方法,比如插入排序。
3. 递归的调用 selection 算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。
4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。
5. 若i==k,返回x;若i<k,在小于x的元素中递归查找第i小的元素;若i>k,在大于x的元素中递归查找第i-k 小的元素。
终止条件:n=1 时,返回的即是i小元素。
时间复杂度
O(n)
代码
public class BFPRT {
public int find(int[] a, int j) {
System.out.println("================================");
System.out.println("待查找的数组 : ");
print(a);
System.out.println("找出第 " + j + "小的数");
if (a.length / 5 == 1) {
return a[a.length - 1];
}
int x = getCenterForArray(a);
// 4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。
List<Integer> left = new ArrayList<Integer>();
List<Integer> right = new ArrayList<Integer>();
for (int i = 0; i < a.length; i++) {
if (a[i] <= x) {
left.add(a[i]);
} else {
right.add(a[i]);
}
}
// 5. 若i==k,返回x;若i<k,在小于x的元素中递归查找第i小的元素;若i>k,在大于x的元素中递归查找第i-k 小的元素。
int K = left.size();
if (j == K) {
return x;
} else if (j < K) {
int[] leftArray = new int[left.size()];
for (int i = 0; i < left.size(); i++) {
leftArray[i] = left.get(i);
}
return find(leftArray, j);
} else {
int[] rightArray = new int[right.size()];
for (int i = 0; i < right.size(); i++) {
rightArray[i] = right.get(i);
}
return find(rightArray, j - K);
}
}
// 得到整个数组的分割数
private int getCenterForArray(int[] a) {
// 1.将n个元素每5个一组,分成n/5(上界)组,最后的一个组的元素个数为n%5,有效的组数为n/5。
int length = a.length;
int num = length / 5;
int[] centers = new int[num];
for (int i = 0; i < num; i++) {
// 取每一组中的中位数
int center = getCenterPerArray(a, 5 * i, 5 * (i + 1) - 1);
centers[i] = center;
System.out.println("该数组的中位数 : " + center);
}
System.out.println("中位数的集合 : ");
print(centers);
// 3. 递归的调用 selection 算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。
new Selectionsort().sort(centers);
System.out.println("经过选择排序后的中位数数组 : ");
print(centers);
int x = 0;
if (centers.length % 2 == 0) {
// 偶数个中位数
x = min(centers[centers.length / 2 - 1],
centers[centers.length / 2]);
} else {
x = centers[centers.length / 2];
}
System.out.println("定义的x 为 : " + x);
return x;
}
private int min(int a, int b) {
if (a >= b) {
return b;
} else {
return a;
}
}
// 得到每个子数组中的中位数
private int getCenterPerArray(int[] a, int start, int end) {
// 2.取出每一组的中位数,最后一个组的不用计算中位数,任意排序方法,这里的数据比较少只有5个,可以用简单的冒泡排序或是插入排序。
int[] b = new int[end - start + 1];
int index = 0;
for (int i = start; i <= end; i++) {
b[index++] = a[i];
}
System.out.println("被分割的数组 : ");
print(b);
new Insertsort().sort(b);
return b[b.length / 2];
}
public static void main(String[] args) {
// 求数组中第7小的数
int[] datas = { 4, 1, 2, 56, 24, 5, 6, 97, 8, 0, 4, 8, 6, 2, 3, 6, 1,
9, 3, 4, 6, 2 };
int index = 8;
int findX = new BFPRT().find(datas, index);
datas = new QuickSort().sort(datas);
print(datas);
System.out.println("第" + index + "小的数为 : " + findX);
}
public static void print(int[] datas) {
for (int i = 0; i < datas.length; i++) {
System.out.print(datas[i] + " ");
}
System.out.println("");
}
}
输出
================================
待查找的数组 :
4 1 2 56 24 5 6 97 8 0 4 8 6 2 3 6 1 9 3 4 6 2
找出第 8小的数
被分割的数组 :
4 1 2 56 24
该数组的中位数 : 4
被分割的数组 :
5 6 97 8 0
该数组的中位数 : 6
被分割的数组 :
4 8 6 2 3
该数组的中位数 : 4
被分割的数组 :
6 1 9 3 4
该数组的中位数 : 4
中位数的集合 :
4 6 4 4
经过选择排序后的中位数数组 :
4 4 4 6
定义的x 为 : 4
================================
待查找的数组 :
4 1 2 0 4 2 3 1 3 4 2
找出第 8小的数
被分割的数组 :
4 1 2 0 4
该数组的中位数 : 2
被分割的数组 :
2 3 1 3 4
该数组的中位数 : 3
中位数的集合 :
2 3
经过选择排序后的中位数数组 :
2 3
定义的x 为 : 2
================================
待查找的数组 :
4 4 3 3 4
找出第 2小的数
0 1 1 2 2 2 3 3 4 4 4 5 6 6 6 6 8 8 9 24 56 97
第8小的数为 : 4