簡體   English   中英

在進行最多 3N 次比較時如何實現 std::make_heap ?

[英]How can std::make_heap be implemented while making at most 3N comparisons?

我查看了 C++0x 標准,發現 make_heap 的比較不能超過 3*N 的要求。

即 heapify 一個無序的集合可以在 O(N) 中完成

   /*  @brief  Construct a heap over a range using comparison functor.

為什么是這樣?

來源沒有給我任何線索(g ++ 4.4.3)

while (true) + __parent == 0 不是線索,而是對 O(N) 行為的猜測

template<typename _RandomAccessIterator, typename _Compare>
void
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
          _Compare __comp)
{

  const _DistanceType __len = __last - __first;
  _DistanceType __parent = (__len - 2) / 2;
  while (true)
    {
      _ValueType __value = _GLIBCXX_MOVE(*(__first + __parent));
      std::__adjust_heap(__first, __parent, __len, _GLIBCXX_MOVE(__value),
                 __comp);
      if (__parent == 0)
        return;
      __parent--;
    }
}

__adjust_heap 看起來像一個 log N 方法:

while ( __secondChild < (__len - 1) / 2)
{
    __secondChild = 2 * (__secondChild + 1);

對我來說是一個沼澤標准日志 N。

  template<typename _RandomAccessIterator, typename _Distance,
       typename _Tp, typename _Compare>
    void
    __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
          _Distance __len, _Tp __value, _Compare __comp)
    {
      const _Distance __topIndex = __holeIndex;
      _Distance __secondChild = __holeIndex;
      while (__secondChild < (__len - 1) / 2)
      {
        __secondChild = 2 * (__secondChild + 1);
          if (__comp(*(__first + __secondChild),
             *(__first + (__secondChild - 1))))
          __secondChild--;
          *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));
          __holeIndex = __secondChild;
      }
      if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2)
      {
        __secondChild = 2 * (__secondChild + 1);
        *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first
                             + (__secondChild - 1)));
        __holeIndex = __secondChild - 1;
      }
      std::__push_heap(__first, __holeIndex, __topIndex, 
               _GLIBCXX_MOVE(__value), __comp);      
      }

任何關於為什么這是 O <= 3N 的線索將不勝感激。
編輯:

實驗結果:

這個實際的實現使用

  • <2N 比較堆的堆
  • <1.5N 以相反的順序堆積堆。

使用巧妙的算法和巧妙的分析,可以在 O(n) 時間內創建一個包含 n 個元素的二進制堆。 在接下來的內容中,我將討論它是如何工作的,假設您有顯式節點和顯式左右子指針,但是一旦將其壓縮到數組中,這種分析仍然完全有效。

該算法的工作原理如下。 首先取大約一半的節點並將它們視為 singleton 最大堆 - 由於只有一個元素,因此僅包含該元素的樹必須自動成為最大堆。 現在,把這些樹和它們配對。 對於每對樹,取其中一個尚未使用的值並執行以下算法:

  1. 使新節點成為堆的根,使其左右子指針指向兩個最大堆。

  2. 雖然此節點有一個比它大的子節點,但將子節點與其更大的子節點交換。

我的主張是,這個過程最終會產生一個新的最大堆,其中包含兩個輸入最大堆的元素,並且它在 O(h) 時間內完成,其中 h 是兩個堆的高度。 證明是對堆高度的歸納。 作為基本情況,如果子堆的大小為零,則算法立即以 singleton 最大堆終止,並且在 O(1) 時間內完成。 對於歸納步驟,假設對於某些 h,此過程適用於任何大小為 h 的子堆,並考慮在兩個大小為 h + 1 的堆上執行它時會發生什么。當我們添加一個新根以將兩個大小為的子樹連接在一起時h + 1,有三種可能:

  1. 新根大於兩個子樹的根。 然后在這種情況下,我們有一個新的最大堆,因為根大於任一子樹中的任何節點(通過傳遞性)

  2. 新的根比一個孩子大,比另一個小。 然后我們將根與較大的子子交換,並再次遞歸執行此過程,使用舊根和子的兩個子樹,每個子樹的高度為 h。 根據歸納假設,這意味着我們交換的子樹現在是一個最大堆。 因此整個堆是一個最大堆,因為新的根比我們交換的子樹中的所有東西都大(因為它比我們添加的節點大並且已經比那個子樹中的所有東西都大),而且它也比所有東西都大在另一個子樹中(因為它大於根並且根大於另一個子樹中的所有內容)。

  3. 新的根比它的兩個孩子都小。 然后使用上面分析的稍微修改的版本,我們可以證明生成的樹確實是一個堆。

此外,由於在每一步子堆的高度都會減少 1,因此該算法的總運行時間必須為 O(h)。


至此,我們有了一個簡單的堆算法:

  1. 取大約一半的節點並創建 singleton 堆。 (您可以在這里明確計算需要多少節點,但大約是一半)。
  2. 將這些堆配對,然后使用未使用的節點之一和上述過程將它們合並在一起。
  3. 重復步驟 2,直到剩下一個堆。

因為在每一步我們都知道到目前為止我們擁有的堆是有效的最大堆,最終這會產生一個有效的整體最大堆。 如果我們能夠巧妙地選擇要制作多少個 singleton 堆,那么最終也將創建一個完整的二叉樹。

但是,這似乎應該在 O(n lg n) 時間內運行,因為我們進行 O(n) 合並,每個合並都在 O(h) 中運行,在最壞的情況下,我們正在合並的樹的高度是 O(lg n)。 但是這個界限並不緊密,我們可以通過更精確的分析來做得更好。

特別是,讓我們考慮一下我們合並的所有樹有多深。 大約一半的堆深度為零,剩下的一半深度為一,剩下的一半深度為二,依此類推。如果我們總結一下,我們得到總和

0 * n/2 + 1 * n/4 + 2 * n/8 +... + nk/(2 k ) = Σ k = 0 ⌈log n⌉ (nk / 2 k ) = n Σ k = 0 ⌈日志 n⌉ (k / 2 k+1 )

這是交換次數的上限。 每次交換最多需要兩次比較。 因此,如果我們將上述總和乘以 2,我們會得到以下總和,它是交換次數的上限:

n Σ k = 0 (k / 2 k )

這里的求和是求和 0 / 2 0 + 1 / 2 1 + 2 / 2 2 + 3 / 2 3 +... 。 這是一個著名的總結,可以用多種不同的方式進行評估。 這些演講幻燈片,幻燈片 45-47 中給出了評估這一點的一種方法。 它最終精確到 2n,這意味着最終進行的比較次數肯定以 3n 為界。

希望這可以幫助!

@templatetypedef 已經給出了一個很好的答案,為什么build_heap的漸近運行時間是O(n) CLRS第 2 版的第 6 章中也有一個證明。

至於為什么C++標准要求最多使用3n次比較:

從我的實驗(見下面的代碼)看來,實際上需要少於2n 次比較。 事實上,這些講義包含了build_heap僅使用2(n-⌈log n⌉)比較的證明。

標准的界限似乎比要求的更慷慨。


def parent(i):
    return i/2

def left(i):
    return 2*i

def right(i):
    return 2*i+1

def heapify_cost(n, i):
    most = 0
    if left(i) <= n:
        most = 1 + heapify_cost(n, left(i))
    if right(i) <= n:
        most = 1 + max(most, heapify_cost(n, right(i)))
    return most

def build_heap_cost(n):
    return sum(heapify_cost(n, i) for i in xrange(n/2, 1, -1))

一些結果:

n                     10  20  50  100  1000  10000
build_heap_cost(n)     9  26  83  180  1967  19960

暫無
暫無

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

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