簡體   English   中英

從 std::heap 中間移除一個元素

[英]Remove an element from the middle of an std::heap

我使用優先級隊列作為具有一項額外要求的調度程序。 我需要能夠取消預定項目。 這等同於從優先級隊列的中間刪除一個項目。

我不能使用std::priority_queue因為對 top 以外的任何元素的訪問都受到保護。

我正在嘗試使用algorithm的堆函數。 但我仍然缺少我需要的部分。 當我從堆中間刪除一個元素時,我希望它有效地重建自己。 C++ 提供了這些堆函數:

  • std::make_heap O(3n)
  • std::push_heap O(lg(n))
  • std::pop_heap O(2 lg(n))

我想要一個像std::repair_heap這樣的新函數, std::repair_heap有一個 big-O < 3n 我會為它提供被取消的項目曾經駐留的孔的位置,它會正確地調整堆。

不提供std::repair_heap函數似乎是一個巨大的疏忽。 我錯過了一些明顯的東西嗎?

是否有提供符合 stl 標准的std::repair_heap

是否有更好的數據結構來建模調度程序?

注意
我沒有使用std::map有幾個原因。

  • 堆具有恆定的內存開銷。
  • 堆具有很棒的緩存位置。

我猜您知道要刪除堆容器(索引 n)中的哪個元素。

  1. 設置值v[n] = BIG; BIG確實比堆中的任何其他值都大。
  2. 調用std::push_heap( v.begin(), v.begin()+n+1 );
  3. 調用std::pop_heap( v.begin(), v.end() );
  4. 調用v.pop_back();
  5. 完成

操作是 O(ln(n))

RE:要求提供證明

首先,一個限定符:這個方法假定一些關於 std push_heap 使用的算法。
具體來說,它假設 std push_heap( v.begin(), v.begin()+n+1 ) 只會改變范圍 [0, n]
對於 n 的上升元素,即以下集合中的索引:

A(n)={n,(n-1)/2,((n-1)/2-1)/2....0}.  

這是 std push_heap 的典型規范:

http://www.cplusplus.com/reference/algorithm/push_heap/ "給定一個堆范圍 [first,last-1),這個函數通過將值放在 (last) -1) 放入其對應的位置。”

它是否保證使用您在教科書中讀到的“正常堆算法”? 你告訴我。

無論如何,這是您可以運行並根據經驗查看它是否有效的代碼。 我正在使用 VC 2005。

#include <algorithm>
#include <vector>
#include <iostream>

bool is_heap_valid(const std::vector<int> &vin)
{
    std::vector<int> v = vin;
    std::make_heap(v.begin(), v.end());
    return std::equal(vin.begin(), vin.end(), v.begin());
}


int _tmain(int argc, _TCHAR* argv[])
{
    srand(0);
    std::vector<int> v;
    for (int i=0; i<100; i++)
    {
        v.push_back( rand() % 0x7fff );
    }
    std::make_heap(v.begin(), v.end());

    bool bfail = false;
    while( v.size() >= 2)
    {
        int n = v.size()/2;
        v[n] = 0x7fffffff;
        std::push_heap(v.begin(), v.begin()+n+1);
        std::pop_heap(v.begin(), v.end());
        v.resize(v.size()-1);
        if (!is_heap_valid(v))
        {
            std::cout << "heap is not valid" << std::endl;
            bfail = true;
            break;
        }
    }
    if (!bfail)
        std::cout << "success" << std::endl;

    return 0;

}

但是我還有一個問題,就是如何知道需要刪除的索引“n”。 在使用 std push_heap 和 std pop_heap 時,我看不到如何跟蹤它(知道堆中的位置)。 我認為您需要編寫自己的堆代碼,並且每次在堆中移動對象時將堆中的索引寫入對象。 嘆。

不幸的是,標准缺少這個(相當重要的)功能。 使用 g++,您可以使用非標准函數std::__adjust_heap來執行此操作,但沒有簡單的可移植方法——而且__adjust_heap在不同版本的 g++ 中略有不同,因此您甚至無法移植超過 g++ 版本。

您的repair_heap()如何工作的? 這是我的猜測:

如果您的堆由某個迭代器范圍定義,請說 (heapBegin, heapEnd)。 要刪除的元素是堆的某個子樹的根,它由某個子范圍(subHeapBegin、subHeapEnd)定義。 使用std::pop_heap(subHeapBegin, subHeapEnd) ,然后如果subHeapEnd != heapEnd ,交換*(subHeapEnd-1)*(heapEnd-1) ,這應該將您刪除的項目放在堆容器的末尾。 現在您必須在您的子堆中向上滲透 *(subHeapEnd-1) 處的元素。 如果我沒有遺漏任何東西(這是可能的),那么剩下的就是從堆容器中切出結束元素。

在嘗試正確編碼之前(我跳過了一些細節,如計算 subHeapBegin 和 subHeapEnd),我會運行一些測試來確定make_heap()真的會減慢你的速度。 Big-O 很有用,但它與實際執行時間不同。

在我看來,從堆的中間移除可能意味着必須重建整個堆:沒有 repair_heap 的原因是因為它必須執行與make_heap相同(大哦)的工作。

您是否能夠執行諸如將std::pair<bool, Item>放入堆中並僅使項目無效而不是刪除它們的操作? 然后,當他們最終到達頂部時,只需忽略該項目並繼續前進。

您可以嘗試“std::multiset”,它作為堆結構實現並支持“std::erase”操作,因此您可以“std::find”該元素然后將其擦除。

這是我用來從堆中刪除項目的一些 delphi 代碼。 我不知道你說的這個 C++ 沒有修復功能,但是嘿..

首先是流行音樂,所以你可以了解它是如何工作的:

function THeap.Pop: HeapItem;
begin
  if fNextIndex > 1 then begin
    Dec(fNextIndex);
    Result:= fBuckets[1];   //no zero element
    fBuckets[1] := fBuckets[fNextIndex];
    fBuckets[fNextIndex] := nil;
    FixHeapDown;            //this has a param defaulting to 
    end
  else
    Result:= nil;
end;

現在對比,刪除:

procedure THeap.Delete(Item: HeapItem);
var
  i:integer;
begin
  for i:=1 to pred(fNextIndex) do
    if Item=fBuckets[i] then begin
      dec(fNextIndex);
      fBuckets[i] := fBuckets[fNextIndex];
      fBuckets[fNextIndex] := nil;
      FixHeapDown(i);
      break;
      end;
end;

甚至考慮做我們在這里做的事情當然是不可以的,但是嘿,有時成本確實會發生變化並且工作確實會被取消。

請享用。 我希望這會有所幫助。

暫無
暫無

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

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