資料結構學習(C++)——二叉樹【3】 (轉)

amyz發表於2007-11-12
資料結構學習(C++)——二叉樹【3】 (轉)[@more@]

遞迴遍歷與非遞迴遍歷

前面寫過一些關於遞迴的文章,因為那時還沒有寫到樹,因此也舉不出更有說服力的例子,只是闡述了“遞迴是一種思想”,正像網友評價的,“一篇的文章”。但只要能能讓你建立“遞迴是一種思想”這個觀念,我的努力就沒有白費。現在,講完了二叉搜尋樹,終於有了能說明問題的例子了。按照前面提供的程式碼,應該能透過下面的。

#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 a; Timer t; randomize(); int i;

  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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章