[英]Performance of Morris Traversal vs recursive In-Order in a binary tree
在去面試之前我正在做一些准備工作,我剛剛了解了 Morris Traversal。
這是我用 Java 編寫的 Morris Traversal 代碼(它的工作):
protected void morrisTraversal(){
BinaryNode pre = null;//BinaryNode is a class which represent a node in the tree
BinaryNode current = this.root;//root is the root of the tree
while(current != null){
if(current.getLeftChild() == null){
System.out.println(current.getNodeData().getId());//id is unique
current = current.getRightChild();
}//if
else{
pre = current.getLeftChild();
while(pre.getRightChild() != null && pre.getRightChild().getNodeData().getId() != current.getNodeData().getId())
pre = pre.getRightChild();
if(pre.getRightChild() == null){
pre.setRightChild(current);
current = current.getLeftChild();
}//if
else{
System.out.println(current.getNodeData().getId());
current = current.getRightChild();
pre.setRightChild(null);
}//else
}//else
}//while
}//MorrisTraversal
這是我的 In-Order 方法的代碼:
protected void recInOrder() {
if(this.root != null)
recInOrderHelper(this.root);
else
System.out.println("The Tree Is Empty");;
}//recInOrder
private void recInOrderHelper(BinaryNode node) {
if(node != null){
recInOrderHelper(node.getLeftChild());
System.out.println(node.getNodeData().getId());
recInOrderHelper(node.getRightChild());
}
}//recInOrderHelper
我主要做的是:我在我的二叉樹中插入了 70,000,000 個節點,然后我做了下一個小檢查:
long start = System.currentTimeMillis();
tree4.morrisTraversalInOrder();
System.out.println("morris traversal TOOK: " + (System.currentTimeMillis() - start) + " ms");
start = System.currentTimeMillis();
tree4.recInOrder();
System.out.println("recursive in-order TOOK: " + (System.currentTimeMillis() - start) + " ms");
結果讓我吃驚!
這些是結果:
莫里斯遍歷 TOOK:637 毫秒
遞歸有序 TOOK:367 毫秒
注意 - 當我做這個測試時,我評論了來自 morrisTraversal() 和 recInOrderHelper() 方法的所有 System.out.println(...) 。
這些結果讓我感到驚訝,因為我認為 Morris Traversal 會更快,因為它的迭代(不打開堆棧幀等)和 In-Order 是遞歸的(每次遞歸調用打開大約 70,000,000 個堆棧幀)
我知道它們都是 O(n),所以顯然我對某些事情是錯誤的,但是哪個? 我錯過了什么? 為什么 Morris Traversal 比遞歸 In-Order 慢得多?
編輯-我還進行了下一個測試:我沒有向樹中插入 70,000,000 個節點,而是插入了 100,000 個節點並且我確實運行了下一個代碼:
//running the two algorithms one time before doing the actual test(but in the same execution)
tree4.recInOrder();
tree4.morrisTraversalInOrder();
//starting the actual test
long start = System.currentTimeMillis();
for(i = 0; i < 100000; i++){
tree4.recInOrder();
}
System.out.println("Recursive In-Order TOOK: " + (System.currentTimeMillis() - start) + " ms");
start = System.currentTimeMillis();
for(i = 0; i < 100000; i++){
tree4.morrisTraversalInOrder();
}
System.out.println("Morris Traversal TOOK: " + (System.currentTimeMillis() - start) + " ms");
結果是:
遞歸有序 TOOK:214434 毫秒
莫里斯遍歷 TOOK:502786 毫秒
正如您所看到的,與遞歸 In-Order 相比,Morris Traversal 仍然非常慢。 所以我又做了一次測試,只插入了 1000 個節點,每個代碼只運行了 10000 次,結果是:
遞歸有序 TOOK:44 毫秒
莫里斯遍歷 TOOK:97 毫秒
我還是不明白? 為什么莫里斯遍歷速度較慢?
看到這里你必須明白其中的邏輯。 只需對莫里斯進行空運行,順序遍歷就可以說需要時間 2k。 現在為遞歸中序遍歷做空運行,我很確定你會或多或少地找到時間 k。 這里的邏輯是,Morris 中序遍歷所花費的時間是中序遍歷所花費的時間的兩倍或多或少,因為我們最多到達每個節點 2 次。 一次用於創建虛擬右鏈接,另一個用於刪除該正確鏈接。
現在,如果您對空間復雜度感興趣,那么莫里斯中序遍歷就像是一個國王。 在這里你不需要額外的空間。 它的空間復雜度是 O(1),但是遞歸中序遍歷的空間復雜度是 O(n)。 希望現在你可以理解這兩種遍歷策略的用途了。 :)
如果不將性能估計分解為它們的組件,很難判斷,但是當用假的正確部分回溯節點時,Morris Traversal 基本上遍歷了樹的一部分。 您還在莫里斯循環內做更多的工作,因此常數因子會更大。 奇怪的是它幾乎是 2 倍,但如果沒有更多細節,我不會依賴於實際成本。
你沒有熱身通行證。 您應該先執行這兩種方法,然后再定時執行它們。 您還忽略了大多數 Java 代碼在長時間運行的進程中運行的事實,這些進程最終會編譯為機器代碼。 我會嘗試使用較小的樹並多次執行遍歷。
Morris Traversal -- O(2n)-> O(n) 時間 有序方法 -- O(2n)-> O(n) 時間
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.