關於二叉樹的遍歷,使用棧遞歸或者仿棧循環都是須要O(N)
的空間,Morris Traversal
保證了空間爲O(1)
,時間仍是O(N)
(比原來多了一遍)。code
這裏只介紹inOrder
順序。遞歸
思路:io
對每個cur
節點,優先找到一個pre
節點,這個pre
節點的做用是,當後續cur
節點遍歷 到這個位置時,能夠直接經過這個pre
節點返回它須要返回的位置。console
例如:function
6 / \ 4 8 / \ 2 5
cur
節點在6
的時候,pre
節點會在5
,由於後面當cur
節點遍歷到5
的時候,能夠經過pre
節點直接返回6
cur
節點再4
的時候,pre
節點會在2
,當後面cur
到2
的時候,能夠直接返回4
pre
找到了,是經過什麼返回呢,由於不能修改二叉樹結構,也不能使用堆棧記錄。class
經過mirror
(鏡像),也就是說,當找到pre
的時候(每一個pre
的右節點確保爲null),在它的右節點建立一個鏡像節點,
這個鏡像節點直接指向當前的cur
節點。二叉樹
這個操做是不佔用空間的,由於只是互相引用。循環
例如:當上面的cur
爲6
,pre
爲5
,那麼設置pre.right=cur
,感受上是這樣:rsa
6 / \ 4 8 / \ 2 5 \ 6 / \ 4 8 ...
其實並無多出來那一塊,只是5
引用到6
罷了遍歷
6 / ↑ \ 4 ↑ 8 / \↑ 2 5
理解了這些,那麼後續就簡單了,當cur
遍歷到pre
的時候而且打印後,將pre
新增的引用刪除恢復原來的樹即可。
代碼:
function morrisTraversal(root){ let cur=root,pre while(cur!=null){ // 當左爲空,直接打印 if(cur.left==null){ console.log(cur.val) cur=cur.right }else{ // 當左不爲空,先去找 pre pre=cur.left while(pre.right!=null && pre.right!==cur){ pre=pre.right } // 創建引用,用於返回 if(pre.right==null){ pre.right=cur cur=cur.left }else{ // 刪除引用 console.log(cur.val) pre.right=null cur=cur.right } } } }