数据结构与算法之线性查找

线性查找

在一个无顺序的数组中找到第k大的元素是几.

这个问题最简单的解法是先将数组进行排序,然后返回下标k上的元素.如果使用上一节的归并排序则时间复杂度是O(nlogn)

那是否有更好的思路呢.同样我们还是使用分治策略,先从数组中找到一个合适的主元,围绕这个主元划分子数组.比主元小的

划分到左边,比主元大的划分到右边.

上图为例,假设k是6则要寻找数组中第六个小的元素.那left有4个加上主元1个已经有5个元素了.我们要找的是第六个,很显然,k在right里并且是right中第1小的元素.在right里就是k-left.length+1.此时再次递归

从right中寻找k=1.递归进来第一步依然是选择主元.以此类推

加入k是3则肯定要去left中寻找,并且依然k依然是3.将left进入递归,第一步还是选择主元,以此类推.

最巧的情况是我们选择的主元刚好=k也就是k=5的情况,left4个比主元小.right的3个比主元大,则主元就是k.

所以选择一个合理的主元是很重要的,或者说主元如何能使得left和right能够平衡,这样无论是到left还是right里寻找k都是均衡的.

以下代码选择主元的方式为将数组分为N个小数组,然后获取N个小数中的中位数,然后再在N个中位数中选择一个中位数.所以主元的选择方式为中位数中的中位数.

具体拆分为几个小数组取决于大数组的长度,以下代码大数组的长度为25,所以我拆分成了5*5的小数组.然后从5个小数组中寻找各自的中位数,然后再寻找中位数中中位数.此算法的时间复杂度为O(n)

public class BFPRT_DEMO {

    public static void swap(int[] A, int i, int j) {
        if (i == j)
            return;
        int temp = A[i];
        A[i] = A[j];
        A[j] = temp;
    }

    // 构造 subMedians 时,寻找每个小数组中的中位数
    // 小数组长度之多为 5
    public static int findMedianInAtMost5(int[] A, int left, int right) {
        // 使用插入排序得到每组中的中位数
        // 每组长度至多为 5,所以 findMedianInAtMost5 仍为 O(1)
        int i = left + 1;
        while (i < right) {
            int j = i;
            while (j > left && A[j - 1] > A[j]) {
                swap(A, j - 1, j);
                j--;
            }
            i++;
        }
        return A[(left + right) / 2];
    }

    // 计算 A 数组中的 Median of Medians
    public static int medianOfMedians(int[] A) {
        List<Integer> subMedians = new ArrayList<>();
        int i = 0;
        while (i < A.length) {
            int r = i + 5 > A.length ? A.length : i + 5;
            subMedians.add(findMedianInAtMost5(A, i, r));
            i += 5;
        }
        return select(subMedians.stream().mapToInt(Integer::intValue).toArray(), subMedians.size() / 2);
    }

    // 线性时间的 Select-K
    public static int select(int[] A, int k) {
        if (A.length == 1) {
            return A[0];
        }
        int pivot = medianOfMedians(A);
        // Partition 过程
        List<Integer> L = new ArrayList<>();
        List<Integer> R = new ArrayList<>();
        for (int i = 0; i < A.length; i++) {
            if (A[i] == pivot)
                continue;
            else if (A[i] < pivot)
                L.add(A[i]);
            else
                R.add(A[i]);
        }
        if (L.size() == k - 1) {
            return pivot;
        } else if (L.size() > k - 1) {
            return select(L.stream().mapToInt(Integer::intValue).toArray(), k);
        } else {
            return select(R.stream().mapToInt(Integer::intValue).toArray(), k - L.size() - 1);
        }
    }

    public static void main(String[] args) {
        // 1 - 25 乱序
        int[] A = {1, 14, 4, 18, 25, 6, 17, 9, 3, 5, 10, 16, 12, 23, 19, 13, 20, 8, 15, 24, 7, 21, 22, 2, 11};
        for (int i = 1; i <= 25; i++) {
            // 第 i 小元素为 i
            System.out.println(select(A, i));
        }
    }

}

 

 
posted @ 2022-04-11 22:37  顶风少年  阅读(47)  评论(0编辑  收藏  举报
返回顶部