簡體   English   中英

使用堆查找第 K 個最大元素的時間復雜度

[英]Time complexity of using heaps to find Kth largest element

我有一些不同的代碼實現,用於在未排序的數組中查找第 K 個最大元素。 我使用的三個實現都使用最小/最大堆,但我無法確定其中一個的運行時復雜性。

實施1:

int findKthLargest(vector<int> vec, int k)
{
    // build min-heap
    make_heap(vec.begin(), vec.end(), greater<int>());

    for (int i = 0; i < k - 1; i++) {
        vec.pop_back();
    }

    return vec.back();
}

實施2:

int findKthLargest(vector<int> vec, int k)
{
    // build max-heap
    make_heap(vec.begin(), vec.end());

    for (int i = 0; i < k - 1; i++) {
        // move max. elem to back (from front)
        pop_heap(vec.begin(), vec.end()); 
        vec.pop_back();
    }

    return vec.front();
}

實施3:

int findKthLargest(vector<int> vec, int k)
{
    // max-heap prio. q
    priority_queue<int> pq(vec.begin(), vec.end());

    for (int i = 0; i < k - 1; i++) {
        pq.pop();
    }

    return pq.top();
}

根據我的閱讀,我假設第二個的運行時間是 O(n) + O(klogn) = O(n + klogn)。 這是因為構建最大堆是在 O(n) 中完成的,如果我們這樣做“k”次,彈出它需要 O(logn)*k。

但是,這就是我感到困惑的地方。 對於第一個,使用最小堆,我假設構建堆是 O(n)。 由於它是一個最小堆,較大的元素在后面。 然后,彈出后面的元素“k”次將花費 k*O(1) = O(k)。 因此,復雜度為 O(n + k)。

同樣,對於第三個,我假設復雜度也是 O(n + klogn),與我對最大堆的推理相同。

但是,一些消息來源仍然說這個問題不能比使用 heaps/pqs 的 O(n + klogn) 更快地完成! 但是,在我的第一個示例中,我認為這種復雜性是 O(n + k)。 如我錯了請糾正我。 需要幫助。

正確實現,從最小堆中獲取第 k 個最大元素是 O((nk) * log(n))。 從最大堆中獲取第 k 個最大元素是 O(k * log(n))。

您的第一個實現根本不正確。 例如,如果您想從堆中獲取最大元素 (k == 1),則永遠不會執行循環體。 您的代碼假定向量中的最后一個元素是堆上的最大元素。 這是不正確的。 例如,考慮堆:

 1
3 2

這是一個完全有效的堆,將由向量[1,3,2]表示。 您的第一個實現無法從該堆中獲取第一個或第二個最大元素。

第二種解決方案看起來可行。

您的前兩個解決方案最終會從vec中刪除項目。 那是你的意圖嗎?

第三種解決方案是正確的。 構建堆需要 O(n),刪除 (k-1) 個最大的項需要 O((k - 1) log n)。 然后 O(1) 訪問最大的剩余項目。

還有另一種方法可以做到這一點,這在實踐中可能更快。 這個想法是:

build a min-heap of size k from the first k elements in vec
for each following element
    if the element is larger than the smallest element on the heap
        remove the smallest element from the heap
        add the new element to the heap
return element at the top of the heap

這是 O(k) 來構建初始堆。 那么剩下的項目在最壞的情況下是 O((nk) log k)。 最壞的情況發生在初始向量按升序排列時。 這並不經常發生。 實際上,一小部分項目被添加到堆中,因此您不必執行所有這些刪除和插入操作。

一些堆實現有一個heap_replace方法,它結合了移除頂部元素和添加新元素這兩個步驟。 這將復雜性降低了一個常數因子。 (即不是在 O(log k) 刪除之后再進行 O(log k) 插入,而是對頂部元素進行恆定時間替換,然后是 O(log k) 將其從堆中篩選出來)。

這是java的堆解決方案。 我們從最小堆中刪除所有小於第 k 個元素的元素。 之后,我們將在最小堆頂部擁有第 k 個最大元素。

class Solution {
    int kLargest(int[] arr, int k) {
        
        PriorityQueue<Integer> heap = new PriorityQueue<>((a, b)-> Integer.compare(a, b));
        for(int a : arr) {
            heap.add(a);
            if(heap.size()>k) {
                // remove smallest element in the heap
                heap.poll();
            }
        }
        // return kth largest element
        return heap.poll();
    }
}

最壞情況的時間復雜度將是 O(N logK),其中 N 是元素的總數。 在堆中插入初始 k 個元素時,您將使用 1 個 heapify 操作。 之后,您將使用 2 次操作(1 次插入和 1 次刪除)。 所以這使得最壞情況的時間復雜度為 O(N logK)。 您可以使用其他一些方法對其進行改進,並將堆更新的平均案例時間復雜度提高到 Θ(1)。 閱讀內容以獲取更多信息。


快速選擇:Θ(N)

如果您正在尋找平均速度更快的解決方案。 基於快速排序的快速選擇算法是一個不錯的選擇。 它提供了 O(N) 和 O(1) 空間復雜度的平均案例時間復雜度。 當然,最壞情況的時間復雜度是 O(N^2),但是隨機樞軸(在下面的代碼中使用)在這種情況下產生的概率非常低。 以下是用於查找第 k 個最大元素的快速選擇算法的代碼。

class Solution {
    public int findKthLargest(int[] nums, int k) {
        return quickselect(nums, k);
    }
     
    private int quickselect(int[] nums, int k) {
        int n = nums.length;
        int start = 0, end = n-1;
        while(start<end) {
            int ind = partition(nums, start, end);
            if(ind == n-k) {
                return nums[ind];
            } else if(ind < n-k) {
                start = ind+1;
            } else {
                end = ind-1;
            }
        }
        return nums[start];
    }
    
    private int partition(int[] nums, int start, int end) {
        int pivot = start + (int)(Math.random()*(end-start));
        swap(nums, pivot, end);
        
        int left=start;
        for(int curr=start; curr<end; curr++) {
            if(nums[curr]<nums[end]) {
                swap(nums, left, curr);
                left++;
            }
        }
        swap(nums, left, end);
        return left;
    }
    
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM