• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            【1】

            這些天參與了CSDN論壇的討論,改變了我以前的一些看法。回頭看我以前的東西,我雖對這本書很不滿,但我還是按照它的安排在一點點的寫;這樣就導致了,我過多的在意書中的偏漏,我寫的更多是說“這本書怎樣”,而偏離了我寫這些的初衷——給正在學習數據結構的人一些幫助。正像我在前面所說的,雖然現有的教科書都不是很合理,但如果僅僅是抱怨這點,那無異于潑婦罵街。雖然本人的水平連初級都夠不上,但至少先從我做一點嘗試,以后這門課的教授方法必將一點點趨于合理。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

            因此,后面不在按照書上的次序,將本著“實際應用(算法)決定數據結構”的思想來講解,常見教科書上有的,基本不再重點敘述(除了重點,例如AVL樹的平衡旋轉),——因此,在看本文的同時,一定要有一本教科書。這只是一個嘗試,希望大家多提寶貴意見。

            因為現實世界中存在這“樹”這種結構——族譜、等級制度、目錄分類等等,而為了研究這類問題,必須能夠將樹儲存,而如何儲存將取決于所需要的操作。這里有個問題,是否允許存在空樹。有些書認為樹都是非空的,因為樹表示的是一種現實結構,而0不是自然數;我用過的教科書都是說可以有空樹,當然是為了和二叉樹統一。這個沒有什么原則上的差別,反正就是一種習慣。

            二叉樹

            二叉樹可以說是人們假想的一個模型,因此,允許有空的二叉樹是無爭議的。二叉樹是有序的,左邊有一個孩子和右邊有一個的二叉樹是不同的兩棵樹。做這個規定,是因為人們賦予了左孩子和右孩子不同的意義,在二叉樹的各種應用中,你將會清楚的看到。下面只講解鏈式結構。看各種講數據結構的書,你會發現一個有趣的現象:在二叉樹這里,基本操作有計算樹高、各種遍歷,就是沒有插入、刪除——那樹是怎么建立起來的?其實這很好理解,對于非線性的樹結構,插入刪除操作不在一定的法則規定下,是毫無意義的。因此,只有在具體的應用中,才會有插入刪除操作。

            節點結構

            數據域、左指針、右指針肯定是必須的。除非很少用到節點的雙親,或者是資源緊張,建議附加一個雙親指針,這將會給很多算法帶來方便,尤其是在這個“空間換時間”的時代。

            template <class T>

            struct BTNode

            {

                BTNode(T data = T(), BTNode<T>* left = NULL, BTNode<T>* right = NULL, BTNode<T>* parent = NULL)

                    : data(data), left(left), right(right), parent(parent) {}

                BTNode<T> *left, *right, *parent;

                T data;

            };

            基本的二叉樹類

            template <class T>

            class BTree

            {

            public:

                BTree(BTNode<T> *root = NULL) : root(root) {}

                ~BTree() { MakeEmpty(); }

                void MakeEmpty() { destroy(root); root = NULL; }

            protected:

                BTNode<T> *root;

            private:

                void destroy(BTNode<T>* p)

                {

                    if (p)

                    {

                        destroy(p->left);

                        destroy(p->right);

                        delete p;

                    }

                }

            }

            二叉樹的遍歷

            基本上有4種遍歷方法,先、中、后根,逐層。當初我對這個很迷惑,搞這么多干什么?到了后面才明白,這是不同的應用需要的。例如,判斷兩個二叉樹是否相等,只要子樹根節點不同,那么就不等,顯然這時要用先序遍歷;而刪除二叉樹,必須先刪除左右子樹,然后才能刪除根節點,這時就要用后序遍歷。

            實際上,搞這么多遍歷方法,根本原因是在內存中儲存的樹是非線性結構。對于用數組儲存的二叉樹,這些名目繁多的方法都是沒有必要的。利用C++的封裝和重載特性,這些遍歷方法能很清晰的表達。

            1.         前序遍歷

            public:

            void PreOrder(void (*visit)(T &data) = print) { PreOrder(root, visit); }

            private:

            void PreOrder(BTNode<T>* p, void (*visit)(T &data))

            {

            if (p){ visit(p->data); PreOrder(p->left, visit); PreOrder(p->right, visit); }

            }

            2.         中序遍歷

            public:

                   void InOrder(void (*visit)(T &data) = print) { InOrder(root, visit); }

            private:

            void InOrder(BTNode<T>* p, void (*visit)(T &data))

            {

                   if (p){ InOrder(p->left, visit);       visit(p->data);       InOrder(p->right, visit); }

                   }

            3.         后序遍歷

            public:

                   void PostOrder(void (*visit)(T &data) = print) { PostOrder(root, visit); }

            private:

            void PostOrder(BTNode<T>* p, void (*visit)(T &data))

            {

                   if (p){ PostOrder(p->left, visit); PostOrder(p->right, visit); visit(p->data); }

                   }

            4.         層次遍歷

            void LevelOrder(void (*visit)(T &data) = print)

            {

                   queue< BTNode<T>* > a; BTNode<T>* p = root;//記得#include<queue>

                   while (p)

                   {

                          visit(p->data);

                          if (p->left) a.push(p->left); if (p->right) a.push(p->right);

                          if (a.empty()) break; p = a.front(); a.pop();

                   }

                   }

            附注:缺省的visit函數print如下

            private:

                   static void print(T &data) { cout << data << ' '; }

            5.         不用棧的非遞歸中序遍歷

            當有parent指針時,可以不用棧實現非遞歸的中序遍歷,書上提到了有這種方法,但沒給出例程。

            public:

            BTNode<T>* next()

            {

                   if(!current) return NULL;

                   if (current->right) { current = current->right; while (current->left) current = current->left; }

                   else

                   {

                          BTNode<T>* y = current->parent;

                          while (y && current == y->right) {current = y; y = y->parent; }

                          current = y;

                   }

                   return current;

            }

            private:

            BTNode<T>* current;

            上面的函數能使current指針向前移動一個位置,如果要遍歷整棵二叉樹,需要使current指向中序序列的第一個節點,例如下面的成員函數:

            public:

            void first() { current = root; while (current->left) current = current->left; }

            【2】 

            線索化二叉樹

            這是數據結構課程里第一個碰到的難點,不知道你是不是這樣看,反正我當初是費了不少腦細胞——當然,惱人的矩陣壓縮和相關的加法乘法運算不在考慮之列。我費了不少腦細胞是因為思考:他們干什么呢?很欣喜的看到在這本黃皮書上,這章被打了*號,雖然我不確定作者是不是跟我一個想法——線索化二叉樹在現在的PC上是毫無用處的!——不知我做了這個結論是不是會被人罵死,^_^

            為了證明這個結論,我們來看看線索化二叉樹提出的緣由:第一,我們想用比較少的時間,尋找二叉樹某一個遍歷線性序列的前驅或者后繼。當然,這樣的操作很頻繁的時候,做這方面的改善才是有意義的。第二,二叉樹的葉子節點還有兩個指針域沒有用,可以節省內存。說真的,提出線索化二叉樹這樣的構思真的很精巧,完全做到了“廢物利用”——這個人真應該投身環保事業。但在計算機這個死板的東西身上,人們的精巧構思往往都是不能實現的——為了速度,計算機的各個部件都是整齊劃一的,而構思的精巧往往都是建立在組成的復雜上的。

            我們來看看線索化二叉樹究竟能不能達到上面的兩個目標。

            求遍歷后的線性序列的前驅和后繼。前序線索化能依次找到后繼,但是前驅需要求雙親;中序線索化前驅和后繼都不需要求雙親,但是都不很直接;后序線索化能依次找到前驅,但是后繼需要求雙親。可以看出,線索化成中序是最佳的選擇,基本上算是達到了要求。

            節省內存。添加了兩個標志位,問題是這兩個位怎么儲存?即使是在支持位存儲的CPU上,也是不能拿位存儲器來存的,第一是因為結構體成員的地址是在一起的,第二是位存儲器的數目是有限的。因此,最少需要1個字節來儲存這兩個標志位。而為了速度和移植,一般來說,內存是要對齊的,實際上根本就沒節省內存!然而,當這個空間用來儲存雙親指針時,帶來的方便絕對不是線索化所能比擬的,前面已經給出了無棧的非遞歸遍歷。并且,在線索化二叉樹上插入刪除操作附加的代價太大。

            綜上,線索化最好是中序線索化(前序后序線索化后還得用棧,何必要線索化呢),附加的標志域空間至少1個字節,在32位的CPU會要求對齊到4字節,還不如存儲一個雙親指針,同樣能達到中序線索化的目的,并且能帶來其他的好處。所以,線索化二叉樹在現在的PC上是毫無用處的!

            由于對其他體系不太了解,以下觀點姑妄聽之。在內存空間非常充裕的現在,一個節點省23個字節實在是沒什么意思(實際上由于對齊還省不出來);而在內存非常寶貴的地方(比如單片機),會盡量避免使用樹結構——利用其他的方法。所以,現在看來,線索化二叉樹真的是毫無用處了。

            二叉搜索樹

            這恐怕是二叉樹最重要的一個應用了。它的構想實際是個很自然的事情——查找值比當前節點小轉左,大轉右,等則查到,到頭了就是沒找著。越自然的東西越好理解,也就越不需要我廢話。在給出BST的實現之前,我們要在二叉樹的類中添加一個打印樹狀結構的成員函數,這樣,就能清楚的看出插入和刪除過程。

            public:

            void print()

            {

                   queue< BTNode<T>* > a; queue<bool> flag; ofstream outfile("out.txt");

                   BTNode<T>* p = root; BTNode<T> zero; bool v = true;

                   int i = 1, level = 0, h = height();

                   while (i < 2<<h)

                   {

                          if (i == 1<<level)

                          {

                                 cout << endl << setw(2 <<(h - level)); level++;

                                 if (v) cout << p->data;

                                 else cout << ' ';

                          }

                          else

                          {

                                 cout << setw(4 <<(h - level + 1));

                                 if (v) cout << p->data;

                                 else cout << "  ";

                          }

                          if (p->left) { a.push(p->left); flag.push(true); }

                          else { a.push(&zero); flag.push(false); }

                          if (p->right) { a.push(p->right); flag.push(true); }

                          else { a.push(&zero); flag.push(false); }

                          p = a.front(); a.pop(); v = flag.front(); flag.pop(); i++;

                   }

                   cout << endl;

            }

            打印樹狀結構的核心是按層次遍歷二叉樹,但是,二叉樹有許多節點缺左或右子樹,連帶的越到下面空隙越大。為了按照樹的結構打印,必須把二叉樹補成完全二叉樹,這樣下面的節點就知道放在什么位置了——a.push(&zero);但是這樣的節點不能讓它打印出來,所以對應每個節點,有一個是否打印的標志,按理說pair結構很合適,為了簡單我用了并列的兩個隊列,一個放節點指針——a,一個放打印標志——flag。這樣一來,循環結束的標志就不能是隊列空——永遠都不可能空,碰到NULL就補一個節點——而是變成了到了滿二叉樹的最后一個節點2^(height+1)-1。——黃皮書對于樹高的定義是,空樹為的高度為-1

            對于輸出格式,注意的是到了第1248號節點要換行,并且在同一行中,第一個節點的域寬是后序節點的一半。上面的函數在樹的層次少于等于5height<=4)的時候能正常顯示,再多的話就必須輸出到文件中去ofstream outfile("out.txt");——如果層次再多的話,打印出來也沒什么意義了。

            二叉搜索樹的實現

            實際上就是在二叉樹的基礎上增加了插入、刪除、查找。

            #include "BaseTree.h"

            template <class T>

            class BSTree : public BTree<T>

            {

            public:

                   BTNode<T>* &find(const T &data)

                   {

                          BTNode<T>** p = &root; current = NULL;

                          while(*p)

                          {

                                 if ((*p)->data == data) break;

                                 if ((*p)->data < data) { current = *p; p = &((*p)->right); }

                                 else { current = *p; p = &((*p)->left); }

                          }

                          return *p;

                   }

                   bool insert(const T &data)

                   {

                          BTNode<T>* &p = find(data); if (p) return false;

                          p = new BTNode<T>(data, NULL, NULL, current); return true;

                   }

                   bool remove(const T &data)

                   {

                          return remove(find(data));

                   }

            private:

            bool remove(BTNode<T>* &p)

            {

                   if (!p) return false; BTNode<T>* t = p;

                   if (!p->left || !p->right)

                   {

                          if (!p->left) p = p->right; else p = p->left;

                          if (p) p->parent = current;

                          delete t; return true;

                   }

                   t=p->right;while(t->left) t=t->left;p->data=t->data;current=t->parent;

                   return remove(current->left==t?current->left:current->right);

                   }

            };

            以上代碼有點費解,有必要說明一下——非線性鏈式結構操作的實現都是很讓人費神。insertremove都是以find為基礎的,因此必須讓find能最大限度的被這兩個操作利用。

            l         對于insert,需要修改查找失敗時的指針內容,顯然這是個內部指針(在雙親節點的內部,而不是象rootcurrent那樣在節點外面指向節點),這就要求find返回一個內部指針的引用。但是C++的引用綁定到一個對象之后,就不能再改變了,因此在find內部的實現是一個二重指針。insert操作還需要修改插入的新節點的parent指針域,因此在find中要產生一個能被insert訪問的指向find返回值所在節點的指針,這里用的是current。實際上find返回的指針引用不是current->left就是current->right。這樣一來,insert的實現就非常簡單了。

            l         對于remove,需要修改查找成功時的指針內容,同樣是個內部指針。在find的基礎上,很容易就能得到這個內部指針的引用(BTNode<T>* &p = find(data)

            ?         p->leftp->right中至少有一個為NULL

            數據結構學習(C++)二叉樹

            Posted on 2005-12-15 12:57 艾凡赫 閱讀(509) 評論(0)  編輯 收藏 引用 所屬分類: C++
            伊人色综合久久天天人手人婷| 久久久91人妻无码精品蜜桃HD| 四虎影视久久久免费观看| 久久强奷乱码老熟女网站| 亚洲乱码精品久久久久..| 久久精品国产一区| 欧美亚洲国产精品久久高清| 97精品国产97久久久久久免费 | 久久久久国产亚洲AV麻豆| 久久天天躁狠狠躁夜夜躁2014| 99国产欧美精品久久久蜜芽| 久久精品国产99久久香蕉| 狠狠88综合久久久久综合网| 久久久WWW免费人成精品| 久久精品国产精品亚洲毛片| 亚洲伊人久久成综合人影院| 青草影院天堂男人久久| 青青草原精品99久久精品66| 亚洲精品tv久久久久久久久久| 国产精品久久一区二区三区| 精品综合久久久久久97| 色婷婷久久久SWAG精品| 久久亚洲国产午夜精品理论片| 亚洲欧美日韩中文久久| 怡红院日本一道日本久久| 国产综合久久久久| 国内精品久久久久久99| 久久久久久久精品成人热色戒 | 久久精品国产亚洲AV久| 日韩欧美亚洲综合久久影院Ds | 久久久国产打桩机| 日韩久久无码免费毛片软件| 久久九九久精品国产免费直播| 91超碰碰碰碰久久久久久综合| 国产精品久久永久免费| 久久国产精品久久| 嫩草影院久久国产精品| 国产激情久久久久影院小草| 国产精品综合久久第一页| 久久嫩草影院免费看夜色| 色综合久久天天综线观看|