簡體   English   中英

二叉搜索與二叉搜索樹

[英]Binary search vs binary search tree

二分搜索樹與使用二分搜索的排序數組相比有什么好處? 僅通過數學分析,我看不出有什么區別,因此我認為低級實現開銷肯定存在差異。 平均案例運行時間的分析如下所示。

帶二分搜索的排序數組
搜索:O(log(n))
插入:O(log(n))(我們運行二進制搜索來找到插入元素的位置)
刪除:O(log(n))(我們運行二分查找來查找要刪除的元素)

二叉搜索樹
搜索:O(log(n))
插入:O(log(n))
刪除:O(log(n))

對於上面列出的操作(如果樹不平衡),二叉搜索樹的最壞情況是 O(n),所以這看起來實際上比使用二分搜索的排序數組更糟糕。

另外,我不是假設我們必須事先對數組進行排序(這將花費 O(nlog(n)),我們會將元素一個一個地插入到數組中,就像我們對二叉樹所做的一樣。唯一的好處我可以看到的 BST 是它支持其他類型的遍歷,如中序、前序、后序。

您的分析是錯誤的,對於排序數組,插入和刪除都是 O(n),因為您必須物理移動數據為插入騰出空間或壓縮它以覆蓋已刪除的項目。

哦,對於完全不平衡的二叉搜索樹,最壞的情況是 O(n),而不是 O(logn)。

查詢任何一個都沒有什么好處。

但是,當您一次添加一個元素時,構建排序樹比構建排序數組要快得多。 因此,完成后將其轉換為數組是沒有意義的。

另請注意,存在用於維護平衡二叉搜索樹的標准算法。 他們擺脫了二叉樹的不足並保持了所有其他的優勢。 但是,它們很復雜,因此您應該先了解二叉樹。

除此之外,大 O 可能是相同的,但常數並不總是如此。 使用二叉樹,如果您正確存儲數據,您可以很好地利用多個級別的緩存。 結果是,如果您進行大量查詢,您的大部分工作都保留在 CPU 緩存內,這大大加快了速度。 如果您謹慎地構建樹,則尤其如此。 請參閱http://blogs.msdn.com/b/devdev/archive/2007/06/12/cache-oblivious-data-structures.aspx以了解樹的巧妙布局如何大大提高性能的示例。 您對其進行二分搜索的數組不允許使用任何此類技巧。

添加到 @Blindy ,我會說在排序數組中的插入比 CPU 指令 O(logn) 需要更多的內存操作 O(n) std::rotate() ) ,請參閱插入排序。

    std::vector<MYINTTYPE> sorted_array;

    // ... ...

    // insert x at the end
    sorted_array.push_back(x);

    auto& begin = sorted_array.begin();

    // O(log n) CPU operation
    auto& insertion_point = std::lower_bound(begin()
             , begin()+sorted_array().size()-1, x); 
    
    // O(n) memory operation
    std::rotate(begin, insertion_point, sorted_array.end());

我猜左孩子右兄弟樹結合了二​​叉樹和排序數組的本質。

數據結構 手術 CPU成本 內存操作成本
排序數組 插入 O(logn)(流水線的好處) O(n) 內存操作,參考使用std::rotate()插入排序
搜索 O(登錄) 內聯實施的好處
刪除 O(logn)(使用內存操作進行流水線操作時) O(n) 內存操作,參考std::vector::erase()
平衡二叉樹 插入 O(logn)(分支預測影響流水線的缺點,還增加了樹旋轉的成本) 耗盡緩存的指針的額外成本。
搜索 O(登錄)
刪除 O(logn)(與插入相同)
左孩子右兄弟樹(組合排序數組和二叉樹) 插入 O(logn) 平均 如果保持不平衡,則在插入左孩子時不需要std::rotate()
搜索 O(logn)(在不平衡的最壞情況下為 O(n)) 在右兄弟搜索中利用緩存局部性,參考 std::vector::lower_bound()
刪除 O(logn)(當超線程/流水線時) O(n) 內存操作參考std::vector::erase()

暫無
暫無

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

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