最近公司項目比較忙,又加上要給十幾個新人作培訓,晚上和週末又要個姐姐裝修店鋪(太痛苦的事情,並且裝修的效果也不是很好),前些每天涯上有一篇寒門難出貴子帖子很火,那個做者語文水平和我一個級別的(錯別字太多了),你們能夠看淘寶鬼腳七整理版本(關注他的微信公衆號),雖然裏邊有些觀點不是很認同,但對我影響很大,出生寒門的兄弟必定去靜下心來好讀讀,這個遠比我下面寫的這邊技術文章帶給你更多思考,很是歡迎你們留言討論文章的觀點。html
前面的文章一直在分析數據結構中線性結構部分,從本篇文章之後將進入非線性結構部分,前面未弄懂部分能夠在網上找些視頻結合我寫的文章在補補,只有這樣後面學習起來才相對容易些,下面進入正題,首先仍是先分享一個軟件帶來誤會故事。
node
之前學校旁邊有一個火車票代售點,每次到過年學生放假的時候,偶爾會看有人由於先買沒有買到票,然後麪人確買到票和售票員吵架,估計每次發生這樣事情,售票員本身也解釋不清楚,連當時學計算機的我也猜想後面人給售票員好處,後來偶然看到一篇帖子才明白,原來火車票售票系統,會把必定數量的票鎖定給一個售票廳,前一我的買票時候,餘票被其餘售票廳鎖住了,後面去人再買票的時候正好遇上其餘售票廳釋放餘票(太幸運),如今技術和硬件愈來愈好,從09年後我就沒有見過爲這事吵架的。ios
一:概念面試
樹狀圖是一種數據結構,它是由n(n>=1)個有限結點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具備如下的特色:算法
1、每一個結點有零個或多個子結點;數據庫
2、沒有前驅的結點稱爲根結點;編程
3、每個非根結點有且只有一個父結點;微信
4、除了根結點外,每一個子結點能夠分爲m個不相交的子樹;數據結構
二:名稱解釋app
子孫:以某節點爲根的子樹中任一節點都稱爲該節點的子孫;
森林:由m(m>=0)棵互不相交的樹的集合稱爲森林;
樹的度:一棵樹中,最大的節點的度稱爲樹的度;
節點的度:一個節點含有的子樹的個數稱爲該節點的度;
兄弟節點:具備相同父節點的節點互稱爲兄弟節點;
堂兄弟節點:雙親在同一層的節點互爲堂兄弟;
節點的祖先:從根到該節點所經分支上的全部節點;
節點的層次:從根開始定義起,根爲第1層,根的子節點爲第2層,以此類推;
樹的高度或深度:樹中節點的最大層次;
雙親節點或父節點:若一個結點含有子節點,則這個節點稱爲其子節點的父節點;
孩子節點或子節點:一個節點含有的子樹的根節點稱爲該節點的子節點;
葉節點或終端節點:度爲零的節點;
非終端節點或分支節點:度不爲零的節點;
三:示例圖
四:樹的應用
一、查找算法中應用(數據庫索引-B-tree)
二、哈夫曼編碼
三、最短路徑問題
四、幾乎全部的編譯器都須要實現一個表達式樹
上面這些樹的應用,面試中也會常常問到,爭取後面能寫比較好分析它們的文章
五:樹的分類
上面的截圖是維基百科裏邊的解釋,畫紅框的在後面的文章會分析到;
五:代碼分析
一、建立一顆樹
1 /** 2 *根據文件內容遞歸建立樹 3 */ 4 void CreateTreeFromFile(ifstream &f) { 5 //每次讀取一個字符 6 T e; 7 InputFromFile(f, e); 8 if (e == '#') { 9 return; 10 } 11 12 //建立一個新的節點 13 root = new TNode<T> (e); 14 15 //建立左右兩個樹對象 16 LinkTree<T> left, right; 17 //遞歸建立左子樹 18 left.CreateTreeFromFile(f); 19 //遞歸建立右子樹 20 right.CreateTreeFromFile(f); 21 22 //設置根節點的左孩子接左子樹的根節點 23 root->lchild = left.root; 24 left.root = 0; 25 //設置根節點的右孩子接右子樹的根節點 26 root->rchild = right.root; 27 right.root = 0; 28 } 29 /** 30 *讀取文件並賦值給遍歷c 31 */ 32 void InputFromFile(ifstream &f, T &c) { 33 f >> c; 34 }
二、清空一顆樹
1 /** 2 *清空樹的全部節點 3 */ 4 void Clear() { 5 Clear(root); 6 } 7 /** 8 *根據節點遞歸清空樹 9 */ 10 void Clear(TNode<T>* t) { 11 //判斷指針是否爲空 12 if (t) { 13 //獲取資源當即放入管理對象(參考Effective C++裏邊條款13) 14 //tr1::shared_ptr(引用計數智慧指針)管理對象比auto_ptr更強大 15 std::auto_ptr<TNode<T> > new_ptr(t); 16 17 //遞歸清空右子樹 18 Clear(new_ptr->rchild); 19 20 //遞歸清空左子樹 21 Clear(new_ptr->lchild); 22 } 23 24 //清空樹的根節點 25 t = 0; 26 }
三、判斷樹是否爲空
1 /** 2 * 若樹爲空,則返回true;不然返回false 3 */ 4 bool IsEmpty() { 5 return root == 0; 6 }
四、計算樹的深度
1 /** 2 * 以傳入節點爲基礎計算樹的深度 3 */ 4 int GetTreeDept(const TNode<T> *t) { 5 int i, j; 6 if (t == 0) { 7 return 0; 8 } else { 9 //遞歸計算左子樹的深度 10 i = this->GetTreeDept(t->lchild); 11 //遞歸計算右子樹的深度 12 j = this->GetTreeDept(t->rchild); 13 } 14 15 //t的深度爲其左右子樹中深度中的大者加1 16 return i > j ? i + 1 : j + 1; 17 }
五、前序非遞歸遍歷樹
以下動畫flash不知道是網上那位大牛作,在這裏借用下,閱讀文章的朋友若是有作flash作的比較好,請幫忙指點下我,謝謝
以下中圖詳細分析下面代碼運行過程
以下代碼
1 /** 2 *前序非遞歸(利用棧)遍歷二叉樹 3 *前序遍歷的規則:根左右 4 *非遞歸遍歷樹會常常當作面試題,考察面試者的編程能力 5 *防止下次被鄙視,應該深刻理解而且動手在紙寫出來 6 */ 7 void PreOrderTraverse() { 8 //申明一個棧對象 9 stack<TNode<T>*> s; 10 //t首先指向根節點 11 TNode<T> *t = root; 12 //壓入一個空指針,做爲判斷條件 13 s.push(0); 14 15 //若是t所值節點非空 16 while (t != 0) { 17 //直接訪問根節點 18 std::cout << (&t->data) << " "; 19 20 //右孩子指針爲非空 21 if (t->rchild != 0) { 22 //入棧右孩子指針 23 s.push(t->rchild); 24 } 25 26 //左孩子指針爲非空 27 if (t->lchild != 0) { 28 //直接指向其左孩子 29 t = t->lchild; 30 } else {//左孩子指針爲空 31 //獲取棧頂元素(右孩子指針) 32 t = s.top(); 33 //清楚棧頂元素 34 s.pop(); 35 } 36 37 } 38 }
六、中序非遞歸遍歷樹
以下動畫flash不知道是網上那位大牛作,在這裏借用下,閱讀文章的朋友若是有作flash作的比較好,請幫忙指點下我,謝謝
以下圖是代碼遍歷樹的節點順序,看的不是很明白的朋友,能夠像前面先序中那樣方式,用圖畫出每個步驟,便於跟好的理解
以下是代碼
1 /** 2 *中序非遞歸(利用棧)遍歷二叉樹 3 *前序遍歷的規則:左根右 4 */ 5 void InOrderTraverse() { 6 //申明一個棧對象 7 stack<TNode<T>*> s; 8 //t首先指向根節點 9 TNode<T>* t = root; 10 11 //節點不爲空或者棧對象不爲空,都進入循環 12 while (t != 0 || !s.empty()) { 13 //若是t節點非空 14 if (t) { 15 //入棧t節點 16 s.push(t); 17 //t節點指向其左孩子 18 t = t->lchild; 19 } else { 20 //獲取棧頂元素(左孩子指針) 21 t = s.top(); 22 //清楚棧頂元素 23 s.pop(); 24 //直接訪問t節點 25 std::cout << (&t->data) << " "; 26 //t節點指向其右孩子 27 t = t->rchild; 28 } 29 } 30 }
七、後序非遞歸遍歷樹
以下動畫flash不知道是網上那位大牛作,在這裏借用下,閱讀文章的朋友若是有作flash作的比較好,請幫忙指點下我,謝謝
以下圖是代碼遍歷樹的節點順序,看的不是很明白的朋友,能夠像前面先序中那樣方式,用圖畫出每個步驟,便於跟好的理解
以下是代碼
1 /** 2 *後序非遞歸(利用棧)遍歷二叉樹 3 *前序遍歷的規則:左右根 4 */ 5 void PostOrderTraverse() { 6 //申明一個棧對象 7 stack<TNode<T>*> s; 8 //t首先指向根節點 9 TNode<T>* t = root; 10 //申請中間變量,用作判斷標識 11 TNode<T>* r; 12 //節點不爲空或者棧對象不爲空,都進入循環 13 while (t != 0 || !s.empty()) { 14 //若是t節點非空 15 if (t) { 16 //入棧t節點 17 s.push(t); 18 //t節點指向其左孩子 19 t = t->lchild; 20 } else { 21 //獲取棧頂元素(左孩子指針) 22 t = s.top(); 23 //判斷t的右子樹是否存在而且沒有訪問過 24 if (t->rchild && t->rchild != r) { 25 //t節點指向其右孩子 26 t = t->rchild; 27 //入棧t節點 28 s.push(t); 29 //t節點指向其左孩子 30 t = t->lchild; 31 } else { 32 //獲取棧頂元素(左孩子指針) 33 t = s.top(); 34 //清楚棧頂元素 35 s.pop(); 36 //直接訪問t節點 37 std::cout << (&t->data) << " "; 38 //設置已經訪問過的節點,防止屢次訪問(右孩子指針) 39 r = t; 40 t = 0; 41 } 42 } 43 } 44 }
八、根據模式遞歸遍歷二叉樹
1 /** 2 * 根據模式遞歸遍歷二叉樹 3 * 下面代碼相對比較簡單,只要記住遍歷樹的規則而且弄一點遞歸,均可以寫出來 4 * 若是在面試中實在寫不出來非遞歸方式,能夠寫一個遞歸版本,也許能夠爭取一個好的工做 5 */ 6 void OrderTraverse(const TNode<T>* t, Style mode) { 7 if (t) { 8 //先序遍歷二叉樹:根左右 9 if (mode == Pre) { 10 //直接訪問t節點 11 std::cout << (&t->data) << " "; 12 //遞歸遍歷左子樹 13 this->OrderTraverse(t->lchild, mode); 14 //遞歸遍歷右子樹 15 this->OrderTraverse(t->rchild, mode); 16 } 17 //中序遍歷二叉樹:左根右 18 if (mode == In) { 19 //遞歸遍歷左子樹 20 this->OrderTraverse(t->lchild, mode); 21 //直接訪問t節點 22 std::cout << (&t->data) << " "; 23 //遞歸遍歷右子樹 24 this->OrderTraverse(t->rchild, mode); 25 } 26 //後序遍歷二叉樹:左右根 27 if (mode == Post) { 28 //遞歸遍歷左子樹 29 this->OrderTraverse(t->lchild, mode); 30 //遞歸遍歷右子樹 31 this->OrderTraverse(t->rchild, mode); 32 //直接訪問t節點 33 std::cout << (&t->data) << " "; 34 } 35 } 36 }
九、運行結果,因爲在虛擬機中打中文,實在太痛苦,弄一點英文裝下B
測試代碼以下:
1 void test() { 2 3 ifstream fin("data.txt"); 4 this->CreateTreeFromFile(fin); 5 std::cout << "create tree success" << std::endl; 6 7 std::cout << "create tree after is null ? "; 8 std::cout << boolalpha << this->IsEmpty(); 9 10 std::cout << std::endl; 11 std::cout << "calculated depth of the tree begins" << std::endl; 12 std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot()); 13 std::cout << std::endl; 14 std::cout << "calculated depth of the tree end" << std::endl; 15 16 std::cout << std::endl; 17 std::cout << "not recursion--------------------begin" << std::endl; 18 std::cout << "pre order traverse: "; 19 this->PreOrderTraverse(); 20 std::cout << std::endl; 21 22 std::cout << "in order traverse: "; 23 this->InOrderTraverse(); 24 std::cout << std::endl; 25 26 std::cout << "post order traverse: "; 27 this->PostOrderTraverse(); 28 std::cout << std::endl; 29 std::cout << "not recursion--------------------end" << std::endl; 30 31 std::cout << std::endl; 32 std::cout << "recursion--------------------begin" << std::endl; 33 std::cout << "pre order traverse:"; 34 this->OrderTraverse(this->GetRoot(), Pre); 35 std::cout << std::endl; 36 37 std::cout << "in order traverse:"; 38 this->OrderTraverse(this->GetRoot(), In); 39 std::cout << std::endl; 40 41 std::cout << "post order traverse:"; 42 this->OrderTraverse(this->GetRoot(), Post); 43 std::cout << std::endl; 44 std::cout << "recursion--------------------end" << std::endl; 45 }
十、完整代碼
TNode.h
1 /* 2 * TLNode.h 3 * 4 * Created on: 2013-7-6 5 * Author: sunysen 6 */ 7 8 #ifndef TLNODE_H_ 9 #define TLNODE_H_ 10 template <class T> 11 class TNode{ 12 public: 13 T data; 14 TNode *rchild,*lchild; 15 TNode(T value):data(value),rchild(0),lchild(0){} 16 }; 17 18 #endif /* TLNODE_H_ */
LinkTree.h
1 /* 2 * LinkTree.h 3 * Created on: 2013-7-6 4 * Author: sunysen 5 */ 6 7 #ifndef LINKTREE_H_ 8 #define LINKTREE_H_ 9 #include "core/common/Common.h" 10 #include "core/node/TNode.h" 11 #include <queue> 12 #include <stack> 13 14 //樹的三種遍歷方式 15 enum Style { 16 Pre, In, Post 17 }; 18 19 /** 20 *鏈式樹 21 */ 22 template<class T> 23 class LinkTree { 24 private: 25 TNode<T> *root;//樹的根節點 26 27 /** 28 *讀取文件並賦值給遍歷c 29 */ 30 void InputFromFile(ifstream &f, T &c) { 31 f >> c; 32 } 33 34 /** 35 *根據節點遞歸清空樹 36 */ 37 void Clear(TNode<T>* t) { 38 //判斷指針是否爲空 39 if (t) { 40 //獲取資源當即放入管理對象(參考Effective C++裏邊條款13) 41 //tr1::shared_ptr(引用計數智慧指針)管理對象比auto_ptr更強大 42 std::auto_ptr<TNode<T> > new_ptr(t); 43 44 //遞歸清空右子樹 45 Clear(new_ptr->rchild); 46 47 //遞歸清空左子樹 48 Clear(new_ptr->lchild); 49 } 50 51 //清空樹的根節點 52 t = 0; 53 } 54 public: 55 /** 56 * 構造函數初始化樹根節點 57 */ 58 LinkTree() : 59 root(0) { 60 } 61 /** 62 *析構行數釋放全部構造的資源 63 */ 64 ~LinkTree() { 65 Clear(); 66 } 67 68 /** 69 *根據文件內容遞歸建立樹 70 */ 71 void CreateTreeFromFile(ifstream &f) { 72 //每次讀取一個字符 73 T e; 74 InputFromFile(f, e); 75 if (e == '#') { 76 return; 77 } 78 79 //建立一個新的節點 80 root = new TNode<T> (e); 81 82 //建立左右兩個樹對象 83 LinkTree<T> left, right; 84 //遞歸建立左子樹 85 left.CreateTreeFromFile(f); 86 //遞歸建立右子樹 87 right.CreateTreeFromFile(f); 88 89 //設置根節點的左孩子接左子樹的根節點 90 root->lchild = left.root; 91 left.root = 0; 92 //設置根節點的右孩子接右子樹的根節點 93 root->rchild = right.root; 94 right.root = 0; 95 } 96 97 /** 98 * 若樹爲空,則返回true;不然返回false 99 */ 100 bool IsEmpty() { 101 return root == 0; 102 } 103 /** 104 *清空樹的全部節點 105 */ 106 void Clear() { 107 Clear(root); 108 } 109 110 /** 111 *獲取const修飾的根節點,至關於作一次類型轉換 112 */ 113 const TNode<T>* GetRoot() { 114 return root; 115 } 116 117 /** 118 * 以傳入節點爲基礎計算樹的深度 119 */ 120 int GetTreeDept(const TNode<T> *t) { 121 int i, j; 122 if (t == 0) { 123 return 0; 124 } else { 125 //遞歸計算左子樹的深度 126 i = this->GetTreeDept(t->lchild); 127 //遞歸計算右子樹的深度 128 j = this->GetTreeDept(t->rchild); 129 } 130 131 //t的深度爲其左右子樹中深度中的大者加1 132 return i > j ? i + 1 : j + 1; 133 } 134 135 136 /** 137 *前序非遞歸(利用棧)遍歷二叉樹 138 *前序遍歷的規則:根左右 139 *非遞歸遍歷樹會常常當作面試題,考察面試者的編程能力 140 *防止下次被鄙視,應該深刻理解而且動手在紙寫出來 141 */ 142 void PreOrderTraverse() { 143 //申明一個棧對象 144 stack<TNode<T>*> s; 145 //t首先指向根節點 146 TNode<T> *t = root; 147 //壓入一個空指針,做爲判斷條件 148 s.push(0); 149 150 //若是t所值節點非空 151 while (t != 0) { 152 //直接訪問根節點 153 std::cout << (&t->data) << " "; 154 155 //右孩子指針爲非空 156 if (t->rchild != 0) { 157 //入棧右孩子指針 158 s.push(t->rchild); 159 } 160 161 //左孩子指針爲非空 162 if (t->lchild != 0) { 163 //直接指向其左孩子 164 t = t->lchild; 165 } else {//左孩子指針爲空 166 //獲取棧頂元素(右孩子指針) 167 t = s.top(); 168 //清楚棧頂元素 169 s.pop(); 170 } 171 172 } 173 } 174 175 /** 176 *中序非遞歸(利用棧)遍歷二叉樹 177 *前序遍歷的規則:左根右 178 */ 179 void InOrderTraverse() { 180 //申明一個棧對象 181 stack<TNode<T>*> s; 182 //t首先指向根節點 183 TNode<T>* t = root; 184 185 //節點不爲空或者棧對象不爲空,都進入循環 186 while (t != 0 || !s.empty()) { 187 //若是t節點非空 188 if (t) { 189 //入棧t節點 190 s.push(t); 191 //t節點指向其左孩子 192 t = t->lchild; 193 } else { 194 //獲取棧頂元素(左孩子指針) 195 t = s.top(); 196 //清楚棧頂元素 197 s.pop(); 198 //直接訪問t節點 199 std::cout << (&t->data) << " "; 200 //t節點指向其右孩子 201 t = t->rchild; 202 } 203 } 204 } 205 /** 206 *後序非遞歸(利用棧)遍歷二叉樹 207 *前序遍歷的規則:左右根 208 */ 209 void PostOrderTraverse() { 210 //申明一個棧對象 211 stack<TNode<T>*> s; 212 //t首先指向根節點 213 TNode<T>* t = root; 214 //申請中間變量,用作判斷標識 215 TNode<T>* r; 216 //節點不爲空或者棧對象不爲空,都進入循環 217 while (t != 0 || !s.empty()) { 218 //若是t節點非空 219 if (t) { 220 //入棧t節點 221 s.push(t); 222 //t節點指向其左孩子 223 t = t->lchild; 224 } else { 225 //獲取棧頂元素(左孩子指針) 226 t = s.top(); 227 //判斷t的右子樹是否存在而且沒有訪問過 228 if (t->rchild && t->rchild != r) { 229 //t節點指向其右孩子 230 t = t->rchild; 231 //入棧t節點 232 s.push(t); 233 //t節點指向其左孩子 234 t = t->lchild; 235 } else { 236 //獲取棧頂元素(左孩子指針) 237 t = s.top(); 238 //清楚棧頂元素 239 s.pop(); 240 //直接訪問t節點 241 std::cout << (&t->data) << " "; 242 //設置已經訪問過的節點,防止屢次訪問(右孩子指針) 243 r = t; 244 t = 0; 245 } 246 } 247 } 248 } 249 /** 250 * 根據模式遞歸遍歷二叉樹 251 * 下面代碼相對比較簡單,只要記住遍歷樹的規則而且弄一點遞歸,均可以寫出來 252 * 若是在面試中實在寫不出來非遞歸方式,能夠寫一個遞歸版本,也許能夠爭取一個好的工做 253 */ 254 void OrderTraverse(const TNode<T>* t, Style mode) { 255 if (t) { 256 //先序遍歷二叉樹:根左右 257 if (mode == Pre) { 258 //直接訪問t節點 259 std::cout << (&t->data) << " "; 260 //遞歸遍歷左子樹 261 this->OrderTraverse(t->lchild, mode); 262 //遞歸遍歷右子樹 263 this->OrderTraverse(t->rchild, mode); 264 } 265 //中序遍歷二叉樹:左根右 266 if (mode == In) { 267 //遞歸遍歷左子樹 268 this->OrderTraverse(t->lchild, mode); 269 //直接訪問t節點 270 std::cout << (&t->data) << " "; 271 //遞歸遍歷右子樹 272 this->OrderTraverse(t->rchild, mode); 273 } 274 //後序遍歷二叉樹:左右根 275 if (mode == Post) { 276 //遞歸遍歷左子樹 277 this->OrderTraverse(t->lchild, mode); 278 //遞歸遍歷右子樹 279 this->OrderTraverse(t->rchild, mode); 280 //直接訪問t節點 281 std::cout << (&t->data) << " "; 282 } 283 } 284 } 285 286 void test() { 287 288 ifstream fin("data.txt"); 289 this->CreateTreeFromFile(fin); 290 std::cout << "create tree success" << std::endl; 291 292 std::cout << "create tree after is null ? "; 293 std::cout << boolalpha << this->IsEmpty(); 294 295 std::cout << std::endl; 296 std::cout << "calculated depth of the tree begins" << std::endl; 297 std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot()); 298 std::cout << std::endl; 299 std::cout << "calculated depth of the tree end" << std::endl; 300 301 std::cout << std::endl; 302 std::cout << "not recursion--------------------begin" << std::endl; 303 std::cout << "pre order traverse: "; 304 this->PreOrderTraverse(); 305 std::cout << std::endl; 306 307 std::cout << "in order traverse: "; 308 this->InOrderTraverse(); 309 std::cout << std::endl; 310 311 std::cout << "post order traverse: "; 312 this->PostOrderTraverse(); 313 std::cout << std::endl; 314 std::cout << "not recursion--------------------end" << std::endl; 315 316 std::cout << std::endl; 317 std::cout << "recursion--------------------begin" << std::endl; 318 std::cout << "pre order traverse:"; 319 this->OrderTraverse(this->GetRoot(), Pre); 320 std::cout << std::endl; 321 322 std::cout << "in order traverse:"; 323 this->OrderTraverse(this->GetRoot(), In); 324 std::cout << std::endl; 325 326 std::cout << "post order traverse:"; 327 this->OrderTraverse(this->GetRoot(), Post); 328 std::cout << std::endl; 329 std::cout << "recursion--------------------end" << std::endl; 330 } 331 332 }; 333 334 #endif /* LINKTREE_H_ */
Common.h
1 /* 2 * Common.h 3 * 4 * Created on: May 17, 2012 5 * Author: sunysen 6 */ 7 8 #ifndef COMMON_H_ 9 #define COMMON_H_ 10 11 #include <iostream> 12 #include <fstream> 13 #include "memory" 14 #include "string" 15 #include "string.h" 16 #include <stdio.h> 17 #include <stdlib.h> 18 #include <math.h> 19 20 21 using namespace std; 22 #endif /* COMMON_H_ */
六:環境
1、運行環境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;
2、開發工具:Eclipse+make
七:題記
1、上面的代碼不免有bug,若是你發現代碼寫的有問題,請你幫忙指出,讓咱們一塊兒進步,讓代碼變的更漂亮和更健壯;
2、我本身能手動寫上面代碼,離不開郝斌、高一凡、侯捷、嚴蔚敏等老師的書籍和視頻指導,在這裏感謝他們;
3、鼓勵本身能堅持把更多數據結構方面的知識寫出來,讓本身掌握更深入,也順便冒充下"小牛";
歡迎繼續閱讀「啓迪思惟:數據結構和算法」系列