算法導論-二叉查找樹

目錄                                         

內容                            

一、引言                                  html

前面的文章介紹過二分查找散列表查找;二分查找效率爲Θ(lgn)、二分查找要求數組是靜態的,即元素不能動態的插入和刪除,不然會耗費較多的時間;散列表查找效率能夠到達Θ(1),可是通常用於「等於性查找「,不能進行「範圍查找」;本文介紹的二叉查找樹,(1)能實現動態查找,查找效率仍能達到Θ(lgn);(2)也能方便的進行範圍查找;node

        除了上面的查找,二叉查找樹還能實現動態的插入和刪除操做。而且指望效率能達到Θ(lgn)ios

Question 一、那麼,什麼是二叉查找樹呢?git

        二叉查找樹,也叫二叉搜索樹、二叉排序樹。是一種特殊的二叉樹,數據結構能夠用二叉鏈表結構表示;二叉查找樹關鍵字知足以下特性:設x爲二叉查找樹中的一個結點。若是y是x的左子樹中的一個結點,則key[y]≤key[x]。若是y是x的右子樹中的一個結點,則key[x]≤key[y]github

      二叉查找樹有一個很好的特性:中序遍歷二叉查找樹能輸出一組有序的節點序列;其中,中序遍歷運行時間爲Θ(n)。算法

Question 二、二叉排序樹各類操做的效率如何?數組

       二叉樹能夠實現的操做包括:基本查詢,查詢最小、最大關鍵值、前驅、後繼,單個節點的插入和刪除操做。這些操做的效率與二叉樹的高度成正比,一顆隨機構造的二叉查找樹的指望高度爲O(lgn),從而基本動態集合的操做平均時間爲Θ(lgn),注意這裏只是指望效率,最壞狀況下爲Θ(n),在二叉查找樹存在的問題小節中會進行講解緣由及如何改進使其最壞狀況下達到Θ(lgn)數據結構

二、二叉查找樹                           ide

2.一、節點定義函數

節點中包括關鍵字元素key,衛星數據,每一個節點還包含屬性left,right和parent,他們指向節點的左孩子、右孩子、和雙親。

 1 //二叉查找樹節點定義,帶模板
 2 template <class T>
 3 class  BinarySearchTreeNode
 4 {
 5 public:
 6     T key;//節點值,這裏只有一個關鍵元素值,沒有附帶數據(衛星數據)
 7     BinarySearchTreeNode<T>* parent;//指向雙親節點指針
 8     BinarySearchTreeNode<T>* left;//指向左孩子節點指針
 9     BinarySearchTreeNode<T>* right;//指向右孩子節點指針
10 };

2.二、查找操做

二叉查找樹中最多見的操做是查找樹中的某個關鍵字,除了基本的查詢,還支持最大值、最小值、前驅和後繼查詢操做

    2.2.一、基本查詢

    這個過程從樹根開始查找,並沿着這棵樹中一條簡單路徑向下進行,對遇到的每一個節點x,比較關鍵字k(待查找的關鍵字)與x.key。若是兩個關鍵字相等,查找就終止。若是k小於x.key,查找在x的左子樹中繼續。對稱地,若是k大於x.key,查找在右子樹中繼續。重複此過程,知道找到或者遇到空節點爲止。下圖是查找元素4的流程

查找過程僞碼:

遞歸:

1 TREE_SEARCH(x,k)
2   if x=NULL or k=key[x]
3       then return x
4   if(k<key[x])
5       then return TREE_SEARCH(left[x],k)
6    else
7       then return TREE_SEARCH(right[x],k)

非遞歸:

1 ITERATIVE_TREE_SEARCH(x,k)
2   while x!=NULL and k!=key[x]
3       do if k<key[x]
4               then x=left[x]
5            else
6               then x=right[x]
7    return x

   2.2.一、查詢最小、最大關鍵字

根據二叉查找樹的特徵,很容易查找出最大和最小關鍵字。查找二叉樹中的最小關鍵字:從根結點開始,沿着各個節點的left指針查找下去,直到遇到NULL時結束。若是一個結點x無左子樹,則以x爲根的子樹中,最小關鍵字就是key[x]。查找二叉樹中的最大關鍵字:從根結點開始,沿着各個結點的right指針查找下去,直到遇到NULL時結束。查找最大最小關鍵字的僞代碼:

最小關鍵字

1 TREE-MINIMUM(x)
2     while x.left != NIL
3         x = x.left
4     return x

 最大關鍵字

1 TREE_MAXIMUM(x)
2     while x.right != NIL
3         do x= x.right
4     return x     

      2.2.一、查詢前驅和後繼

給定一個二叉查找樹中的結點,找出在中序遍歷順序下某個節點的前驅和後繼。若是樹中全部關鍵字都不相同,則某一結點x的前驅就是小於key[x]的全部關鍵字中最大的那個結點,後繼便是大於key[x]中的全部關鍵字中最小的那個結點。根據二叉查找樹的結構和性質,不用對關鍵字作任何比較,就能夠找到某個結點的前驅和後繼。

  查找前驅步驟:先判斷x是否有左子樹,若是有則在left[x]中查找關鍵字最大的結點,便是x的前驅。若是沒有左子樹,則從x繼續向上執行此操做,直到遇到某個結點是其父節點的右孩子結點。則該父親節點即爲前驅節點。例以下圖查找結點7的前驅結點6過程:

      查找後繼步驟:先判斷x是否有右子樹,若是有則在right[x]中查找關鍵字最小的結點,即便x的後繼。若是沒有右子樹,則從x的父節點開始向上查找,直到遇到某個結點是其父結點的左兒子的結點時爲止,則該父親節點即爲後繼節點。例以下圖查找結點13的後繼結點15的過程:

求x前驅節點的僞碼:

1 TREE_PREDECESSOR(x)
2     if left[x] != NULL
3         then return TREE_MAXIMUM(left(x))
4     y=parent[x]
5     while y!= NULL and x ==left[y]
6            do x = y
7                y=parent[y]
8     return y

 求x後繼節點的僞碼:

1 TREE_SUCCESSOR(x)
2     if right[x] != NULL
3         then return TREE_MINMUM(right(x))
4     y=parent[x]
5     while y!= NULL and x ==right[y]
6            do x = y
7                y=parent[y]
8     return y

2.三、插入操做

插入結點的位置對應着查找過程當中查找不成功時候的結點位置,所以須要從根結點開始查找帶插入結點位置,找到位置後插入便可。下圖所示插入結點過程:

插入過程僞代碼:

 1 TREE_INSERT(T,z)
 2     y = NULL;
 3     x =root[T]
 4     while x != NULL
 5         do y =x
 6            if key[z] < key[x]
 7                then x=left[x]
 8            else  x=right[x]
 9     parent[z] =y
10     if y=NULL
11         then root[T] =z
12     else if key[z]>key[y]
13         then  keft[y]  = z
14     else   right[y] =z 

2.四、刪除操做

 刪除操做比較複雜,從二叉查找樹中刪除給定的節點z,分三種狀況:

<1>結點z沒有左右子樹,則修改其父節點p[z],使其爲NULL。

<2>若是結點z只有一個子樹(左子樹或者右子樹),那麼將這個孩子提高到樹中z的位置上,並修改z的父節點,用z的孩子來替換z.

<3>若是z有兩個子女,則先刪除z的後繼y(y沒有左孩子),在用y的內容來替代z的內容。

下面的分析把上述狀況分紅4種狀況進行詳細講解:

  • 若是z沒有左孩子(圖a),那麼用其右孩子來替換z,這個右孩子能夠是NIL,也能夠不是。當z的右孩子是NIL時,此時這種狀況歸爲z沒有孩子節點的情形。當z的右孩子非NIL時,這種狀況就是z僅有一個孩子節點的情形,該孩子是其右孩子。
  • 若是z僅有一個孩子且爲左孩子(圖b),那麼用氣左孩子來替換z。
  • 不然,z既有一個左孩子又有一個右孩子。咱們要查找z的後繼y,這個後繼位於z的右孩子中而且沒有左孩子。如今須要將y移出原來的位置進行拼接,並替換樹中的z。
  • 若是y是z的右孩子(圖c),那麼用y替換z,並僅留下y的右孩子。
  • 不然,y位於z的右子樹中但並非z的右孩子(圖d),在這種狀況下,先用y的右孩子替換y,而後再用y替換z。

在寫刪除僞代碼以前,爲了在二叉查找樹內移動子樹,先定義一個子過程TRANSPLANT,它是用另外一顆子樹替換一顆子樹併成爲其雙親的孩子節點。當TRANSPLANT用一顆以v爲根的子樹來替換一顆以u爲根的子樹是,節點u的雙親就變爲節點v的雙親,而且最後v成爲u的雙親的相應孩子。

TRANSPLANT子過程僞代碼:

1 TRANSPLANT(T,u,v)
2     if u.p == NIL
3         T.root  = v
4     elseif u == u.p.left//若是u是一個左孩子
5         u.p.left = v
6     else u.p.right = v//若是u是一個右孩子
7     if v != NIL 
8         v.p = u.p

 

利用現成的TRANSPLANT過程,下面是二叉搜索樹T中刪除節點z的刪除過程:

 1 TREE-DELETE(T,z)
 2     if z.left == NIL //圖a,左孩子爲空,用z的右孩子替換掉z
 3         TRANSPLANT(T,z,z.right)
 4     elseif z.right == NIL////圖b,右孩子爲空,用z的左孩子替換掉z
 5         TRANSPLANT(T,z,z.left)
 6     else //左右孩子都存在
 7         y = TREE-SUCCESSOR(z)//z的後繼
 8         if y.p != z//y不是z的右孩子
 9             TRANSPLANT(T,y,y.right)//y的右孩子替換掉y
10             y.right = z.right//z的右孩子轉換爲y的右孩子
11             y.right.p = y
12         TRANSPLANT(T,z,y)//用y替換z併成爲z的雙親的一個孩子
13         y.left = z.left//再用z的左孩子替換爲y的左孩子
14         y.left.p = y

 

三、二叉查找樹存在問題             

二叉查找上各類基本操做的運行時間都是O(h),h爲樹的高度。可是在元素插入和刪除過程當中,樹的高度會發生改變。若是n個元素按照嚴格增加的順序插入,那個構造出的二叉查找樹的高度爲n-1。例如按照前後順序插入七、1五、1八、20、3四、4六、59元素構造二叉查找樹,二叉查找樹結構以下所示:

這樣就致使二叉查找樹的基本操做運行時間是Θ(n),解決方法就是"旋轉"二叉樹,使二叉樹"平衡",好比平衡二叉樹、紅黑樹等。這些在其餘文章裏面講解。

四、完整源碼                             

Github源碼

BinarySearchTree.h(二叉查找樹實現)

  1 #include <iostream>
  2 #include <stack>
  3 using namespace std;
  4 
  5 //二叉查找樹節點定義,帶模板
  6 template <class T>
  7 class  BinarySearchTreeNode
  8 {
  9 public:
 10     T key;//節點值,這裏只有一個值,沒有附帶數據
 11     BinarySearchTreeNode<T>* parent;//指向雙親節點指針
 12     BinarySearchTreeNode<T>* left;//指向左孩子節點指針
 13     BinarySearchTreeNode<T>* right;//指向右孩子節點指針
 14 };
 15 
 16 /* 
 17 二查查找樹功能實現類,包括節點查找,最大、最小節點查找,
 18    前驅、後繼節點查找,插入節點,刪除節點功能實現,帶模板
 19 */
 20 template <class T>
 21 class BinarySearchTree
 22 {
 23 public:
 24     BinarySearchTree();
 25     void tree_insert(const T& elem);
 26     int  tree_remove(const T& elem);
 27     BinarySearchTreeNode<T>* tree_search(const T& elem)const;
 28     BinarySearchTreeNode<T>* tree_minmum(BinarySearchTreeNode<T>* root)const;
 29     BinarySearchTreeNode<T>* tree_maxmum(BinarySearchTreeNode<T>* root)const;
 30     BinarySearchTreeNode<T>* tree_successor(const T& elem) const;
 31     BinarySearchTreeNode<T>* tree_predecessor(const T& elem)const;
 32     bool empty() const;
 33     void inorder_tree_traverse()const;
 34     BinarySearchTreeNode<T>* get_root()const { return root; }
 35     void transplant(BinarySearchTreeNode<T>* u, BinarySearchTreeNode<T>* v);
 36 private:
 37     BinarySearchTreeNode<T>* root;
 38 };
 39 /*構造函數*/
 40 template <class T>
 41 BinarySearchTree<T>::BinarySearchTree()
 42 {
 43     root = NULL;
 44 }
 45 /*二叉查找樹插入操做實現
 46   實現思路:插入結點的位置對應着查找過程當中查找不成功時候的結點位置,
 47   所以須要從根結點開始查找帶插入結點位置,找到位置後插入便可
 48 */
 49 template <class T>
 50 void BinarySearchTree<T>::tree_insert(const T& elem)
 51 {
 52     if (!empty())//判斷二叉樹是否爲空,非空則找到合適節點插入
 53     {
 54         BinarySearchTreeNode<T> *pnode = root;
 55         BinarySearchTreeNode<T> *qnode = NULL;
 56         BinarySearchTreeNode<T> *newnode = new BinarySearchTreeNode<T>;//建立新節點
 57         newnode->key = elem;//插入節點值
 58         newnode->parent = NULL;//初始化雙親節點、左右孩子節點
 59         newnode->left = NULL;
 60         newnode->right = NULL;
 61         while (pnode)//非空,查找插入節點位置
 62         {
 63             qnode = pnode;
 64             if (pnode->key > elem)//當前節點值大於待插入節點值,向左子樹查找
 65                 pnode = pnode->left;
 66             else//當前節點值不大於待插入節點值,向右子樹查找
 67                 pnode = pnode->right;
 68         }
 69         if (qnode->key > elem)//當前節點值大於待插入節點值,則插入到左孩子節點
 70             qnode->left = newnode;
 71         else//不然插入到右孩子節點
 72             qnode->right = newnode;
 73         newnode->parent = qnode;//設置新插入節點的雙親節點
 74     }
 75     else//二叉樹爲空,則插入節點爲根節點
 76     {
 77         root = new BinarySearchTreeNode<T>;
 78         root->key = elem;
 79         root->parent = NULL;
 80         root->left = NULL;
 81         root->right = NULL;
 82     }
 83 }
 84 /*刪除節點調用的子過程transplant,用一顆子樹v替換另一顆子樹u併成爲其雙親的孩子節點
 85 輸入:u-被替換的子樹指針,v-新替換的子樹指針
 86 輸出:void
 87 */
 88 template <class T>
 89 void BinarySearchTree<T>::transplant(BinarySearchTreeNode<T>* u, BinarySearchTreeNode<T>* v)
 90 {
 91     if (u->parent == NULL) root = v;
 92     else if (u == u->parent->left) u->parent->left=v;
 93     else u->parent->right = v;
 94 
 95     if (v != NULL) v->parent = u->parent;
 96 }
 97 /*節點刪除實現
 98   輸入:要刪除的節點的關鍵字
 99   輸出:刪除成功返回0
100         不存在此關鍵字則返回-1
101 */
102 template <class T>
103 int BinarySearchTree<T>::tree_remove(const T&elem)
104 {
105     BinarySearchTreeNode<T> *pnode;//當前節點
106     BinarySearchTreeNode<T> *snode;//雙親節點、後繼節點
107     pnode = tree_search(elem);//查找待刪除節點
108     if (!pnode) return -1;//待刪除節點不存在,返回-1
109     if (pnode->left == NULL)//左孩子不存在,用右孩子直接替換待刪除節點(這個右孩子能夠是NULL,也能夠不是NULL)
110         transplant(pnode, pnode->right);
111     else if (pnode->right == NULL)//只存在左孩子,用左孩子直接替換待刪除節點
112         transplant(pnode,pnode->left);
113     else//左右孩子都存在
114     {
115         snode = tree_minmum(pnode->right);//求待刪除節點的後繼(因右孩子存在,因此必定是右子樹中最小的節點)
116         if (snode->parent != pnode)//後繼不是待刪除節點的右孩子,
117         {
118             transplant(snode, snode->right);//snode是後繼節點,因此無左子樹,右子樹替換掉snode
119             snode->right = pnode->right;//pnode的右孩子成爲snode的右孩子
120             snode->right->parent = snode;
121         }
122         transplant(pnode, snode);//snode替換掉pnode
123         snode->left = pnode->left;//處理snode的左子樹,及左子樹的雙親關係
124         snode->left->parent = snode;
125     }
126     return 0;
127     
128 }
129 /*查找search實現
130  輸入:待查找的數據elem
131  輸出:查找成功,返回指向該節點的指針;
132        待查找數值不存在,返回空指針
133 */
134 template <class T>
135 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_search(const T& elem)const
136 {
137     BinarySearchTreeNode<T> *pnode = root;//節點指針,指向根節點
138     //非遞歸查找過程
139     while (pnode)//節點非空
140     {
141         if (pnode->key == elem)//查找成功:指針指向節點關鍵值等於待查找數值,退出while
142             break;
143         else if (pnode->key > elem)//當前節點值大於查找的elem,則在當前節點左孩子二叉樹中進行查找
144             pnode = pnode->left;
145         else               //當前節點值小於查找的elem,則在當前節點右孩子二叉樹中進行查找
146             pnode = pnode->right;
147     }
148 
149     return pnode;
150 }
151 
152 /*查找最小關鍵字
153   輸入:指向要查找的二叉樹的根節點的指針
154   輸出:返回指向最小關鍵字節點的指針
155 */
156 template <class T>
157 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_minmum(BinarySearchTreeNode<T>* root)const
158 {
159     BinarySearchTreeNode<T> *pnode = root;//節點指針,指向根節點
160     
161     while (pnode->left)//從根結點開始,沿着各個節點的left指針查找下去,直到遇到NULL時結束
162             pnode = pnode->left;
163     
164     return pnode;
165 }
166 /*查找最d大關鍵字
167 輸入:指向要查找的二叉樹的根節點的指針
168 輸出:返回指向最大關鍵字節點的指針
169 */
170 template <class T>
171 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_maxmum(BinarySearchTreeNode<T>* root)const
172 {
173     BinarySearchTreeNode<T> *pnode = root;//節點指針,指向根節點
174 
175     while (pnode->right)//從根結點開始,沿着各個節點的left指針查找下去,直到遇到NULL時結束
176             pnode = pnode->right;
177 
178     return pnode;
179 }
180 /*求節點後繼
181   輸入:當前節點的值
182   輸出: 當前節點的後繼節點的指針
183 */
184 template <class T>
185 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_successor(const T& elem) const
186 {
187     BinarySearchTreeNode<T>* pnode = tree_search(elem);//查找值爲elem的節點,返回指針保存到pnode
188     BinarySearchTreeNode<T>* parentnode=NULL;//定義雙親節點
189     if (pnode != NULL)//當前節點存在
190     {
191         if (pnode->right)//當前節點存在右孩子,則後繼爲右子樹的最小關鍵字節點
192             return tree_minmum(pnode->right);
193         parentnode = pnode->parent;//不存在右孩子,取雙親節點
194         /*
195         //雙親節點不爲空,而且當前節點時雙親節點的右孩子時,一直尋找雙親節點,
196         //直到雙親節點爲空,或者當前節點是雙親節點的左孩子
197         */
198         while (parentnode && pnode == parentnode->right)
199         {
200             pnode = parentnode;
201             parentnode = parentnode->parent;
202         }
203         if (parentnode)//雙親節點不爲空,返回
204             return parentnode;
205         else//爲空,無後繼節點
206             return NULL;
207     }
208     return NULL;//當前節點不存在
209 }
210 /*求節點前驅
211 輸入:當前節點的值
212 輸出: 當前節點的前驅節點的指針
213 */
214 template <class T>
215 BinarySearchTreeNode<T>* BinarySearchTree<T>::tree_predecessor(const T& elem)const
216 {
217     BinarySearchTreeNode<T>* pnode = tree_search(elem);//查找值爲elem的節點,返回指針保存到pnode
218     BinarySearchTreeNode<T>* parentnode;//定義雙親節點
219     if (pnode != NULL)//當前節點存在
220     {
221         if (pnode->right)
222             return tree_maxmum(pnode->right);//當前節點存在左孩子,則後繼爲左子樹的最大關鍵字節點
223         parentnode = pnode->parent;//不存在左孩子,取雙親節點
224         /*
225         //雙親節點不爲空,而且當前節點是雙親節點的左孩子時,一直尋找雙親節點,
226         //直到雙親節點爲空,或者當前節點是雙親節點的右孩子
227         */
228         while (parentnode && pnode == parentnode->left)
229         {
230             pnode = parentnode;
231             parentnode = pnode->parent;
232         }
233         if (parentnode)//雙親節點不爲空,返回
234             return parentnode;
235         else//爲空,無後繼節點
236             return NULL;
237     }
238     return NULL;
239 }
240 /*判斷二叉樹查找樹是否爲空
241   輸出:若爲空,返回true
242         非空,返回false
243 */
244 template <class T>
245 bool BinarySearchTree<T>::empty() const
246 {
247     return (NULL == root);
248 }
249 /*二叉查找樹中序遍歷非遞歸實現
250  實現思路:藉助棧完成
251 */
252 template <class T>
253 void BinarySearchTree<T>::inorder_tree_traverse()const
254 {
255     if (NULL != root)
256     {
257         stack<BinarySearchTreeNode<T>*> s;
258         BinarySearchTreeNode<T> *ptmpnode;
259         ptmpnode = root;//指向根節點
260         while (NULL != ptmpnode || !s.empty())//當前節點不空,或者棧不空
261         {
262             if (NULL != ptmpnode)//當前節點不爲空,入棧,置左孩子節點爲當前節點
263             {
264                 s.push(ptmpnode);//入棧
265                 ptmpnode = ptmpnode->left;//置左孩子節點爲當前節點
266             }
267             else//當前節點爲空,彈出棧頂元素並訪問該節點,而後置右孩子節點爲當前節點
268             {
269                 ptmpnode = s.top();//彈出棧頂元素
270                 s.pop();
271                 cout << ptmpnode->key << " ";//訪問該節點
272                 ptmpnode = ptmpnode->right; //而後置右孩子節點爲當前節點
273             }
274         }
275     }
276 }
View Code

Main.cpp(主測試函數)

 1 #include"BinarySearchTree.h"
 2 int main()
 3 {
 4     BinarySearchTree<int> bstree;
 5     BinarySearchTreeNode<int>* ptnode, *proot;
 6     bstree.tree_insert(32);
 7     bstree.tree_insert(21);
 8     bstree.tree_insert(46);
 9     bstree.tree_insert(54);
10     bstree.tree_insert(16);
11     bstree.tree_insert(38);
12     bstree.tree_insert(70);
13     cout << "inorder tree walk is: ";
14     bstree.inorder_tree_traverse();
15     proot = bstree.get_root();
16     cout << "\nmax value is: " << bstree.tree_maxmum(proot)->key << endl;
17     cout << "min value is: " << bstree.tree_minmum(proot)->key<< endl;
18     ptnode = bstree.tree_search(38);
19     if (ptnode)
20         cout << "the element 38 is exist in the binary tree.\n";
21     else
22         cout << "the element 38 is not exist in the binary tree.\n";
23     cout << "the successor of 38 is: " << bstree.tree_successor(38)->key << endl;
24     cout << "the predecessor of 38 is:" << bstree.tree_predecessor(38)->key << endl;
25     if (bstree.tree_remove(46) == 0)
26         cout << "delete 46 successfully" << endl;
27     else
28         cout << "delete 46 failed" << endl;
29     cout << "inorder tree walk is: ";
30     bstree.inorder_tree_traverse();
31     exit(0);
32 }
View Code

運行結果:

五、討論區                                 

(我的想法):二叉排序樹是否能夠用來進行對數組進行排序呢?效率是多少?堆排序也是構造二叉樹,他們的特色各是什麼?(設平均高度爲O(lgn))

答(若有不妥請大神指點):(1)能夠排序,首先把每一個節點插入到而叉查找樹中構造完整二叉查找樹,而後進行中序遍歷便可獲得有序序列;其中插入單個節點時間爲Θ(lgn),n個節點用時Θ(nlgn),因此二叉排序樹排序效率爲Θ(nlgn)。

  (2)堆排序是一顆徹底二叉樹,排序效率爲Θ(nlgn),須要構造大頂堆或者小頂堆。邊調整堆邊交換元素排序。完成後進行層序遍歷便可獲得有序序列;

      (3)二叉排序樹能夠不是徹底二叉樹,堆排序是一顆徹底二叉樹。

六、參考資料                             

 【1】 http://www.cnblogs.com/Anker/archive/2013/01/28/2880581.html

 【2】http://www.cnblogs.com/huangxincheng/archive/2012/07/21/2602375.html

 【3】《算法導論》

相關文章
相關標籤/搜索