資料結構學習(C++)——二叉樹【3】 (轉)
遞迴遍歷與非遞迴遍歷
前面寫過一些關於遞迴的文章,因為那時還沒有寫到樹,因此也舉不出更有說服力的例子,只是闡述了“遞迴是一種思想”,正像網友評價的,“一篇的文章”。但只要能能讓你建立“遞迴是一種思想”這個觀念,我的努力就沒有白費。現在,講完了二叉搜尋樹,終於有了能說明問題的例子了。按照前面提供的程式碼,應該能透過下面的。
#include
using namespace std;
#include
#include
#include "BSTree.h"
#include "Timer.h"
#define ran(num) (rand() % (num))
#define randomize() srand((unsigned)time(NULL))
#define NODENUM 200000//node number
int data[NODENUM];
void zero(int &t) { t = 0; }
int main()
{
BSTree
for (i = 0; i < NODENUM; i++) data[i] = i;
for (i = 0; i < NODENUM; i++) s(data[i], data[random(NODENUM)]);//random swap
t.start(); for (i = 0; i < NODENUM; i++) a.insert(data[i]);
cout << "Insert time: " << t.GetTime() << "tNode number: " << NODENUM << endl;
t.start(); for (a.first(); a.get() != NULL; a.next()) a.get()->data = 0;
cout << "Non-Stack time: " << t.GetTime() << endl;
t.start(); a.LevelOrder(zero); cout << "LevlOrder time: " << t.GetTime() << endl;
t.start(); a.PreOrder(zero); cout << " PreOrder time: " << t.GetTime() << endl;
t.start(); a.InOrder(zero); cout << " InOrder time: " << t.GetTime() << endl;
t.start(); a.PostOrder(zero); cout << "PostOrder time: " << t.GetTime() << endl;
return 0;
}
以下是timer.h的內容
#ifndef Timer_H
#define Timer_H
#include <.h>
class Timer
{
public:
Timer() { QueryPerformanceFrequency(&Frequency); }
inline void start() { QueryPerformanceCounter(&timerB); }
inline double GetTime()
{
QueryPerformanceCounter(&timerE);
return (double)(timerE.QuadPart - timerB.QuadPart) / (double)Frequency.QuadPart * 1000.0;
}
private:
LARGE_INTEGER timerB, timerE, Frequency;
};
#endif
上面的程式中,層次遍歷用到的是佇列,這應該可以代表用棧消解遞迴的情況,在我的機器C500上執行的結果是:
Insert time: 868.818 Node number: 200000
Non-Stack time: 130.811
LevlOrder time: 148.438
PreOrder time: 125.47
InOrder time: 129.125
PostOrder time: 130.914
以上是VC6的release版的結果,時間單位是ms,不說明會有人認為是當機了,^_^。可以看出,遞迴遍歷實際上並不慢,相反,更快一些,而de版的結果是這樣的:
Insert time: 1355.69 Node number: 200000
Non-Stack time: 207.086
LevlOrder time: 766.023
PreOrder time: 183.287
InOrder time: 179.835
PostOrder time: 190.674
遞迴遍歷的速度是最快的
這恐怕是上面結果得出的最直接的結論。不知從哪聽來的觀點“遞迴的速度慢,為了提高速度,應該用棧消解遞迴”,證據就是斐波那契數列的計算,遺憾的是斐波那契數列的非遞迴演算法是迴圈迭代,不是棧消解;如果他真的拿棧來模擬,他就會發現,其實用棧的更慢。
我們來看看為什麼。遞迴的實現是將引數壓棧,然後call自身,最後按層返回,一系列的動作是在堆疊上操作的,用的是push、pop、call、ret之類的指令。而用ADT棧來模擬遞迴,實現的還是上述指令的功能,不同的是那些指令對照的ADT實現可就不只是一條指令了。誰都明白模擬的肯定比真實的差,怎麼會在這個問題上就犯糊塗了呢?
當然,你非要在visit中加入類似這樣的istream file1(“input.txt”),然後在用棧模擬的把這個放在迴圈的外面,最後你說,棧模擬的比遞迴的快,我也無話可說——曾經就見過一個人,/Develop/Read_Article.?Id=18342">將連線放在visit函式里面,然後說遞迴的速度慢。
如果一個遞迴過程用非遞迴的方法實現後,速度提高了,那只是因為遞迴做了一些無用功。比如用迴圈消解的尾遞迴,是多了無用的壓棧和出棧才使速度受損的;斐波那契數列計算的遞迴改迴圈迭代所帶來的速度大幅提升,是因為改掉了重複計算的毛病。假使一個遞迴過程必須要用棧才能消解,那麼,完全模擬後的結果根本就不會對速度有任何提升,只會減慢;如果你改完後速度提升了,那隻證明你的遞迴函式寫的有問題,例如多了許多重複操作——開啟關閉、連線斷開資料庫,而這些完全可以放到遞迴外面。遞迴方法本身是簡潔高效的,只是使用的人不注意使用方法。
這麼看來,研究遞迴的棧消解好像是無用的,其實不然,用棧模擬遞迴還是有點意義的,只是並不大,下面將給出示例來說明。
棧模擬遞迴的好處是節省了堆疊
將上面的程式//node number那行的數值改為15000——不改沒反應了別找我,將//random swap那行註釋掉,執行debug版,耐心的等30秒,就會拋異常了,最後的輸出結果是這樣的:
Insert time: 27555.5 Node number: 15000
Non-Stack time: 16.858
LevlOrder time: 251.036
這隻能說明堆疊了。你可以看到層次遍歷還能工作(由此類推,棧模擬的也能工作),但是遞迴的不能工作了。這是因為和總的空間比起來,堆疊空間是很少的,如果遞迴的層次過深,堆疊就溢位了。所以,如果你預先感到遞迴的層次可能過深,你就要考慮用棧來消解了。
然而,如果你必須用遞迴,而遞迴的層次深到連堆疊都溢位了,那肯定是你的演算法有問題,或者是那個程式根本不適合在PC上執行——執行起來就象當機了,這樣的程式誰敢用?所以說用棧模擬遞迴是有意義的,但是不大,因為很少用到。
對於樹結構來說,如果沒有雙親指標,那麼遍歷時的遞迴是必須的,與其搞什麼棧消解不如新增一個雙親指標,這實際上也是用堆空間換取堆疊空間的一個方法,只是比ADT棧來得直接、高效罷了。
綜上,遞迴的棧消解,實際上只能節省堆疊空間,不僅不會提高速度,相反卻會降低——天下哪有白吃的午餐,既省了堆疊空間還能提高速度。那些“棧消解遞迴能提高速度”的謠傳只是對“消除尾遞迴能提高速度”的不負責任的引申,然而一群人以訛傳訛,真就像是那麼回事了,這就叫三人成虎。等我這時候再回過頭看教科書,竟然沒看見一本書上寫著“棧消解遞迴能提高速度”。真的,以前一直被誤導了,還不知道是被誰誤導的——書上根本就沒有寫。
另外的結論
對比上面兩組結果,可以看到插入15000個節點比200000個節點消耗的時間還多,其原因就是插入資料的順序不同,從而導致了find的效率不同。隨機的順序大致能保證樹的左右子樹分佈均勻,而有序的序列將使樹退化成單支的連結串列,從而使得O(logN)的時間複雜度變成了O(N)。同時,這也是為什麼200000個節點的樹遞迴遍歷還能工作,而遞迴遍歷15000個節點的樹堆疊就溢位了——遞迴的每一層對應的節點太少了。
為了提高find的效率,同時也是使樹的遞迴遍歷方法的使用更為寬泛,有必要研究如何能使樹的高度降低,這就是下面將要講到的平衡樹的來由。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-981897/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 資料結構學習(c++)——二叉樹 (轉)資料結構C++二叉樹
- 資料結構學習(C++)——二叉樹【2】 (轉)資料結構C++二叉樹
- 資料結構學習(C++)——二叉樹【1】 (轉)資料結構C++二叉樹
- 資料結構學習(C++)——樹(總結) (轉)資料結構C++
- 資料結構二叉樹學習資料結構二叉樹
- 【資料結構】二叉樹(c++)資料結構二叉樹C++
- 資料結構 其五 樹與二叉樹學習總結資料結構二叉樹
- 資料結構學習(C++)——遞迴【2】(3) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【3】(1) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【3】(2) (轉)資料結構C++遞迴
- 資料結構學習(C++)續——排序【3】交換排序 (轉)資料結構C++排序
- 資料結構學習(C++)——序言 (轉)資料結構C++
- 資料結構(樹):二叉樹資料結構二叉樹
- [資料結構] 樹、二叉樹、森林的轉換資料結構二叉樹
- 重學資料結構之樹和二叉樹資料結構二叉樹
- 重學資料結構(六、樹和二叉樹)資料結構二叉樹
- 資料結構學習(C++)——圖(總結) (轉)資料結構C++
- 資料結構學習之樹結構資料結構
- 資料結構學習(C++)——圖【3】(無向圖)(上) (轉)資料結構C++
- 資料結構學習(C++)——圖【3】(無向圖)(下) (轉)資料結構C++
- 資料結構-二叉樹資料結構二叉樹
- 資料結構 - 二叉樹資料結構二叉樹
- 【資料結構】二叉樹!!!資料結構二叉樹
- 資料結構學習系列之二叉搜尋樹詳解!資料結構
- 資料結構學習(C++)——遞迴【1】 (轉)資料結構C++遞迴
- 資料結構-平衡二叉樹資料結構二叉樹
- 資料結構之「二叉樹」資料結構二叉樹
- 資料結構——平衡二叉樹資料結構二叉樹
- PTA練習7 二叉樹(1)——資料結構二叉樹資料結構
- 資料結構學習(C++)——迴圈連結串列 (轉)資料結構C++
- 資料結構學習(C++)——雙向連結串列 (轉)資料結構C++
- 資料結構學習(C++)——遞迴【2】(1) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【2】(2) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【2】(4) (轉)資料結構C++遞迴
- 資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)資料結構二叉樹
- 資料結構——二叉樹進階資料結構二叉樹
- 資料結構-二叉搜尋樹資料結構
- 資料結構 二叉樹遍歷資料結構二叉樹