正文node
0. 數據結構圖文解析系列
數據結構系列文章 |
---|
數據結構圖文解析之:數組、單鏈表、雙鏈表介紹及C++模板實現 |
數據結構圖文解析之:棧的簡介及C++模板實現 |
數據結構圖文解析之:隊列詳解與C++模板實現 |
數據結構圖文解析之:樹的簡介及二叉排序樹C++模板實現. |
數據結構圖文解析之:AVL樹詳解及C++模板實現 |
數據結構圖文解析之:二叉堆詳解及C++模板實現 |
數據結構圖文解析之:哈夫曼樹與哈夫曼編碼詳解及C++模板實現 |
1. 樹的簡介
1.1 樹的特徵
樹是一種數據結構,它是n(n>=0)個節點的有限集。n=0時稱爲空樹。n>0時,有限集的元素構成一個具備層次感的數據結構。
git
區別於線性表一對一的元素關係,樹中的節點是一對多的關係。樹具備如下特色:github
- n>0時,根節點是惟一的,不可能存在多個根節點。
- 每一個節點有零個至多個子節點;除了根節點外,每一個節點有且僅有一個父節點。根節點沒有父節點。
1.2 樹的相關概念
樹有許多相關的術語與概念,在學習樹的結構以前,咱們要熟悉這些概念。算法
- 子樹:除了根節點外,每一個子節點均可以分爲多個不相交的子樹。(圖二)
- 孩子與雙親:若一個結點有子樹,那麼該結點稱爲子樹根的"雙親",子樹的根是該結點的"孩子"。在圖一中,B、H是A的孩子,A是B、H的雙親。
- 兄弟:具備相同雙親的節點互爲兄弟,例如B與H互爲兄弟。
- 節點的度:一個節點擁有子樹的數目。例如A的度爲2,B的度爲1,C的度爲3.
- 葉子:沒有子樹,也便是度爲0的節點。
- 分支節點:除了葉子節點以外的節點,也便是度不爲0的節點。
- 內部節點:除了根節點以外的分支節點。
- 層次:根節點爲第一層,其他節點的層次等於其雙親節點的層次加1.
- 樹的高度:也稱爲樹的深度,樹中節點的最大層次。
- 有序樹:樹中節點各子樹之間的次序是重要的,不能夠隨意交換位置。
- 無序樹:樹種節點各子樹之間的次序是不重要的。能夠隨意交換位置。
- 森林:0或多棵互不相交的樹的集合。例如圖二中的兩棵樹爲森林。
2. 二叉樹簡介
2.1 二叉樹的定義
二叉樹或者爲空集,或者由一個根節點和兩棵互不相交的、分別稱爲左子樹和右子樹的二叉樹組成。從定義能夠看出一棵二叉樹:數組
- 二叉樹是有序樹,區分左子樹與右子樹,不能夠隨意交換子樹位置。
- 一個節點的子樹數量取值範圍爲0,1,2。0表明該節點是葉子節點,1表明該節點只有左子樹或只有右子樹,2表明該節點有左右子樹。
根據定義,一棵二叉樹有5中基本形態:markdown
2.2 斜樹、滿二叉樹、徹底二叉樹、二叉查找樹
斜樹
全部節點都只有左子樹的二叉樹叫作左斜樹,全部節點都只有右子樹的二叉樹叫作右斜樹。左斜樹和右子樹統稱爲斜樹。
斜樹已經退化成線性結構,二叉樹在查找上表現出來優異性能在斜樹得不到體現。
數據結構
注意:爲了只關注重點,咱們全部的節點都採用統一淺綠色着色,如有特殊節點將在圖中備註函數
滿二叉樹
滿二叉樹要知足兩個條件:post
- 全部的節點都同時具備左子樹和右子樹。
- 全部的葉子節點都在同一層上。
在一樣深度的二叉樹中,滿二叉樹的節點數目是最多的,葉子數也是最多的。
徹底二叉樹
在一棵二叉樹中,只有最下兩層的度能夠小於2,而且最下一層的葉子節點集中出如今靠左的若干位置上。
或者這樣定義:對一棵具備n個節點的二叉樹按層序從左到右編序,二叉樹樹某個節點的編序與一樣位置的滿二叉樹節點的編序相同若是全部節點都知足這個條件,則二叉樹爲徹底二叉樹。
從定義能夠看出: 滿二叉樹必定是徹底二叉樹;徹底二叉樹不必定是滿二叉樹。
二叉查找樹
二叉排序樹也稱爲二叉搜索樹或二叉排序樹。二叉排序樹的節點包含鍵值key。二叉排序樹或者是一棵空樹,不然要求:
- 若它的左子樹不爲空,那麼左子樹上全部節點的key都小於根節點的key
- 若它的右子樹不爲空,那麼右子樹上全部節點的key都大於根節點的key
- 它的左右子樹也分別爲二叉排序樹
根據定義,二叉查找樹中沒有重複key的節點。
在實際的應用中,二叉排序樹的應用比較多,咱們後面要講的AVL樹自己也是一種二叉排序樹。
2.3 二叉樹的性質
性質一:在二叉樹的第i層上至多有2^(i-1)個節點(i>=1)
證實:利用數學概括法進行證實
- 當i==1時,第1層節點數目爲2^(i-1) = 2^(1-1) = 2^0 = 1。顯然成立,此時二叉樹只有根節點。
- 假設i>1時,第i層的節點數目爲2^(i-1)。
根據假設,只需證實第i+1層節點數爲2^i 便可。
因爲二叉樹每一個節點最多有兩個孩子,故第(i+1)層上的節點數最可能是第i層的兩倍。
即:第i+1層上節點數最多爲: 2* 2^(i-1) = 2 ^ i
故假設成立,命題得證。
性質二:深度爲k的二叉樹至多有2^k-1個節點
證實:二叉樹節點數最多時,每層的節點樹都必須最多。
根據性質一,深度爲k的二叉樹的節點數最多爲: 2^0 + 2^1 +....+2^(k-1) = 2 ^ k -1
性質三:對任何一棵二叉樹T,若是終端節點數爲n0,度爲2的節點數爲n2 ,那麼 n0 = n2 +1
證實:二叉樹節點度數最大爲2,則 : n = n0 + n1 + n2 (等式一)
從孩子個數角度出發: 度爲0的節點沒有孩子, 度爲1的節點沒有1個孩子,度爲2的節點有2個孩子,孩子總數爲 n00 + n11 +n2 2 = n1+2n2;樹的全部節點中,只有根不是任何節點的孩 子,所以有 n -1 = n1 + 2* n2 ,即 n = n1 + 2* n2 + 1. (等式二)
由等式一等式而能夠推出 n0 = n2 +1
性質四: 具備n個節點的徹底二叉樹的高度爲至少爲log2(n+1)
證實:高度爲h的二叉樹最多有2{h}–1個結點。反之,對於包含n個節點的二叉樹的高度至少爲log2(n+1)。
性質五:若是對一棵有n個節點的徹底二叉樹的節點按層序編號(從第一層開始到最下一層,每一層從左到右編號),對任一節點i有:
- 若是i=1 ,則節點爲根節點,沒有雙親。
- 若是2 * i > n ,則節點i沒有左孩子 ;不然其左孩子節點爲2*i . (n爲節點總數)
- 若是2 * i+1>n ,則節點i沒有右孩子;不然其右孩子節點爲2*1+1
3. 二叉查找樹
二叉查找樹的定義咱們已經知道。要維護二叉查找樹的特性,比較複雜的是刪除節點操做,咱們將進行重點的解析。不過咱們先來看看二叉查找樹的節點結構定義與類定義。
3.1 節點結構
//二叉查找樹的節點結構 template <typename T> struct BSNode { BSNode(T t) : value(t), lchild(nullptr), rchild(nullptr){} BSNode() = default; T value; BSNode<T>* lchild; BSNode<T>* rchild; BSNode<T>* parent; };
- value:節點的值,也便是上文的key,類型由模板參數決定
- lchild :指向節點的左孩子
- rchild:指向節點的右孩子
- parent: 指向節點的雙親
3.2 二叉查找樹的抽象數據結構
//二叉查找樹類 template <typename T> class BSTree { public: BSTree(); ~BSTree(); void preOrder(); //前序遍歷二叉樹 void inOrder(); //中序遍歷二叉樹 void postOrder(); //後序遍歷二叉樹 void layerOrder(); //層次遍歷二叉樹 BSNode<T>* search_recursion(T key); //遞歸地進行查找 BSNode<T>* search_Iterator(T key); //迭代地進行查找 T search_minimun(); //查找最小元素 T search_maximum(); //查找最大元素 BSNode<T>* successor (BSNode<T>* x); //查找指定節點的後繼節點 BSNode<T>* predecessor(BSNode<T>* x); //查找指定節點的前驅節點 void insert(T key); //插入指定值節點 void remove(T key); //刪除指定值節點 void destory(); //銷燬二叉樹 void print(); //打印二叉樹 private: BSNode<T>* root; //根節點 private: BSNode<T>* search(BSNode<T>* & p, T key); void remove(BSNode<T>* p, T key); void preOrder(BSNode<T>* p); void inOrder(BSNode<T>* p); void postOrder(BSNode<T>* p); T search_minimun(BSNode<T>* p); T search_maximum(BSNode<T>* p); void destory(BSNode<T>* &p); };
這裏咱們定義了二叉排序樹的類型BSTree。它包含了:
- BSTree的根節點root,這是惟一的數據成員
- 操做的外部接口與內部實現接口。例如 preOrder()爲提供給用戶使用的接口,接口聲明爲public;而preOrder(AVLTreeNode* pnode)是類內部爲了遞歸操做所使用的接口,接口聲明爲private。
提供的其餘接口都有相應的備註說明。
3.3 插入新節點
假設咱們要爲數組 a[] = {10 , 5 , 15 , 6 , 4 , 16 }構建一個二叉排序樹,咱們按順序逐個插入元素。
插入過程是這樣的:
- 若是是空樹,則建立一個新節點,新節點做爲根,所以以元素10構建的節點爲該二叉查找樹的根。
- 插入5,5比10小,與10的左孩子節點進行比較,10的左孩子節點爲空,進行插入。
- 插入15,15比10大,與10的右孩子節點進行比較,10的右孩子節點爲空,進行插入。
- 插入6,6比10小,與10的左孩子節點5比較;6比5大,與5的右孩子節點進行比較,5的右孩子爲空,進行插入。
- 插入4,4比10小,與10的左孩子節點5比較;4比5小,與5的左孩子節點進行比較,5的左孩子爲空,進行插入。
- 插入16,16比10大,與10的右孩子節點15比較;16比15大,與15的右孩子節點進行比較,15的右孩子爲空,進行插入。
從這個過程咱們能夠總結出插入新元素的步驟:
- 尋找元素合適的插入位置:新元素與當前結點進行比較,若值大於當前結點,則從右子樹進行尋找;不然從左子樹進行尋找.
- 找到插入位置以後,以元素的值構建新節點,插入二叉排序樹中
該過程的實現代碼:
/*插入函數*/ template <typename T> void BSTree<T>::insert(T key) { BSNode<T>* pparent = nullptr; BSNode<T>* pnode = root; while (pnode != nullptr) //尋找合適的插入位置 { pparent = pnode; if (key > pnode->value) pnode = pnode->rchild; else if (key < pnode->value) pnode = pnode->lchild; else break; } pnode = new BSNode<T>(key); //以元素的值構建新節點 if (pparent == nullptr) //若是是空樹 { root = pnode; //則新節點爲根 } else { if (key > pparent->value) { pparent->rchild = pnode;//不然新節點爲其父節點的左孩 } else pparent->lchild = pnode; //或右孩 } pnode->parent = pparent; //指明新節點的父節點 };
將構建出來的新節點插入二叉排序樹時,須要修改連接指針的指向。
3.2 遍歷平衡二叉樹
遍歷平衡二叉樹,就是以某種方式逐個「訪問」二叉樹的每個節點。「訪問」是指對節點的進行某種操做,例如輸出節點的值。
平衡二叉樹是有序樹,嚴格區分左子樹與右子樹,若是規定左子樹先於右子樹的次序,咱們有三種方式遍歷二叉樹:
- 前序遍歷
- 中序遍歷
- 後序遍歷
咱們以如圖的兩棵二叉排序樹進行遍歷的算法演示。
前序遍歷
若二叉樹爲空,則空操做返回,不然先訪問根節點,而後前序遍歷左子樹,再前序遍歷右子樹。(簡記爲:VLR)
/*前序遍歷算法*/ template <typename T> void BSTree<T>::preOrder() { preOrder(root); }; template <typename T> void BSTree<T>::preOrder(BSNode<T> *p) { if (p != nullptr) { cout << p->value << endl; preOrder(p->lchild); preOrder(p->rchild); } };
前序遍歷樹a:10 5 4 3 6 15 16
前序遍歷樹b:5 3 2 4 8 7 9
中序遍歷
若二叉樹爲空,則空操做返回,不然從根節點開始,中序遍歷根節點的左子樹,而後訪問根節點,最後中序遍歷右子樹。(簡記爲:LVR)
/*中序遍歷算法*/ template <typename T> void BSTree<T>::inOrder() { inOrder(root); }; template<typename T> void BSTree<T>::inOrder(BSNode<T>* p) { if (p != nullptr) { inOrder(p->lchild); cout << p->value << endl; inOrder(p->rchild); } };
前序遍歷樹a:3 4 5 6 10 15 16
前序遍歷樹b:2 3 4 5 7 8 9
二叉排序樹的中序遍歷恰好輸出一個非遞減的有序序列。
後序遍歷
若樹爲空,則返回空操做,不然從左到右先葉子後節點的方式遍歷訪問左右子樹,左右子樹都訪問結束,才訪問根節點。(簡稱LRV)
/*後序遍歷算法*/ template <typename T> void BSTree<T>::postOrder() { postOrder(root); }; template <typename T> void BSTree<T>::postOrder(BSNode<T>* p) { if (p != nullptr) { postOrder(p->lchild); postOrder(p->rchild); cout << p->value<<endl; } };
後序遍歷樹a:3 4 6 5 16 15 10
後序遍歷樹b:2 4 3 7 9 8 5
3.2 前驅與後繼
對於一棵二叉排序樹,中序遍歷時恰好能夠輸出一個非遞減的序列。例如前序遍歷圖九樹a:3 4 5 6 10 15 16,則可稱:
- 4是5 前驅節點,6是5的後繼節點
- 6是10的前驅節點,15是10的後繼節點
一個節點的前驅節點有3種狀況:
- 它有左子樹,則左子樹根節點爲其前驅節點
- 它沒有左子樹,且它自己爲右子樹,則其父節點爲其前驅節點
- 它沒有左子樹,且它自己爲左子樹,則它的前驅節點爲「第一個擁有右子樹的父節點」
/*尋找其前驅節點*/ template <typename T> BSNode<T>* BSTree<T>::predecessor(BSNode<T>* pnode) { if (pnode->lchild != nullptr) { pnode = pnode->lchild; while (pnode->rchild != nullptr) { pnode = pnode->rchild; } return pnode; } BSNode<T>* pparent = pnode->parent; while (pparent != nullptr && pparent->lchild == pnode)//若是進入循環,則是第三種狀況;不然爲第二種狀況 { pnode = pparent; pparent = pparent->parent; } return pparent; };
一樣的,一個節點的後繼節點也有三種狀況:
- 它有右子樹;則其後繼節點爲其右子樹的最左節點
- 它沒有右子樹,但它自己是一個左孩子,則後繼節點爲它的雙親
- 它沒有右子樹,但它自己是一個右孩子,則其後繼節點爲「具備左孩子的最近父節點」
/*尋找其後繼節點*/ template <typename T> BSNode<T>* BSTree<T>::successor(BSNode<T>* pnode) { if (pnode->rchild != nullptr) { pnode = pnode->rchild; while (pnode->lchild != nullptr) { pnode = pnode->lchild; } return pnode; } BSNode<T>* pparent = pnode->parent; while (pparent!=nullptr&& pparent->rchild == pnode) { pnode = pparent; pparent = pparent->parent; } return pparent; };
3.3 刪除節點
刪除二叉排序樹的某個節點有三種狀況:
- 被刪除節點同時有左子樹與右子樹。
- 被刪除節點只有左子樹或只有右子樹。
- 被刪除節點沒有子樹。
對於第一種狀況,咱們的處理方式是將前驅節點的值保存在當前結點,繼而刪除前驅節點。
對於第二種狀況,咱們直接用子樹替換被刪節點。
對於第三種狀況,咱們能夠直接刪除節點。
刪除節點的代碼:
/*刪除指定節點*/ template <typename T> void BSTree<T>::remove(T key) { remove(root, key); }; /*刪除指定節點*/ /*內部使用函數*/ template <typename T> void BSTree<T>::remove(BSNode<T>* pnode, T key) { if (pnode != nullptr) { if (pnode->value == key) { BSNode<T>* pdel=nullptr; if (pnode->lchild == nullptr || pnode->rchild == nullptr) pdel = pnode; //狀況2、三:被刪節點只有左子樹或右子樹,或沒有孩子 else pdel = predecessor(pnode); //狀況一:被刪節點同時有左右子樹,則刪除該節點的前驅 //此時,被刪節點只有一個孩子(或沒有孩子).保存該孩子指針 BSNode<T>* pchild=nullptr; if (pdel->lchild != nullptr) pchild = pdel->lchild; else pchild = pdel->rchild; //讓孩子指向被刪除節點的父節點 if (pchild != nullptr) pchild->parent = pdel->parent; //若是要刪除的節點是頭節點,注意更改root的值 if (pdel->parent == nullptr) root = pchild; //若是要刪除的節點不是頭節點,要注意更改它的雙親節點指向新的孩子節點 else if (pdel->parent->lchild==pdel) { pdel->parent->lchild = pchild; } else { pdel->parent->rchild = pchild; } if (pnode->value != pdel->value) pnode->value = pdel->value; delete pdel; } //進行遞歸刪除 else if (key > pnode->value) { remove(pnode->rchild, key); } else remove(pnode->lchild, key); } };
3.4 查找元素
咱們能夠遞歸或非遞歸地進行元素的查找。元素的查找過程與元素的插入過程一致,也是在不斷地與當前結點進行比較,若值比當前節點的值大,則在右子樹進行查找,若值比當前節點的值小,則在左子樹進行查找,能夠看到這是一個很適合遞歸操做的過程。而因爲二叉排序樹這種左小右大的節點特徵,也很容易進行非遞歸查找。
/*查找指定元素的節點(非遞歸)*/ template <typename T> BSNode<T>* BSTree<T>::search_Iterator(T key) { BSNode<T> * pnode = root; while (pnode != nullptr) { if (key == pnode->value) //找到 return pnode; if (key > pnode->value) //關鍵字比節點值大,在節點右子樹查找 pnode = pnode->rchild; else pnode = pnode->lchild; //關鍵字比節點值小,在節點左子樹查找 } return nullptr; }; /*查找指定元素的節點(遞歸)*/ template <typename T> BSNode<T>* BSTree<T>::search_recursion(T key) { return search(root, key); }; /*private:search()*/ /*遞歸查找的類內部實現*/ template <typename T> BSNode<T>* BSTree<T>::search(BSNode<T>* & pnode, T key) { if (pnode == nullptr) return nullptr; if (pnode->value == key) return pnode; //cout << "-->" << pnode->value << endl; //能夠輸出查找路徑 if (key > pnode->value) return search(pnode->rchild, key); return search(pnode->lchild, key); };
3.5 查找最值元素
二叉排序樹的最小值位於其最左節點上;最大值位於其最右節點上:
/*尋找最小元素*/ template <typename T> T BSTree<T>::search_minimun() { return search_minimun(root); }; template <typename T> T BSTree<T>::search_minimun(BSNode<T>* p) { if (p->lchild != nullptr) return search_minimun(p->lchild); return p->value; }; /*尋找最大元素*/ template <typename T> T BSTree<T>::search_maximum() { return search_maximum(root); }; template <typename T> T BSTree<T>::search_maximum(BSNode<T>*p) { if (p->rchild != nullptr) return search_maximum(p->rchild); return p->value; };
3.6 銷燬二叉樹
使用後序遍歷遞歸銷燬二叉樹
/*銷燬二叉樹*/ template<typename T> void BSTree<T>::destory() { destory(root); }; template <typename T> void BSTree<T>::destory(BSNode<T>* &p) { if (p != nullptr) { if (p->lchild != nullptr) destory(p->lchild); if (p->rchild != nullptr) destory(p->rchild); delete p; p = nullptr; } };
3.7測試代碼
int main() { BSTree<int> t; t.insert(62); t.insert(58); t.insert(47); t.insert(51); t.insert(35); t.insert(37); t.insert(88); t.insert(73); t.insert(99); t.insert(93); t.insert(95); cout << endl << "中序遍歷:" << endl; t.inOrder(); cout << "最大元素:" << t.search_maximum() << endl; cout << "最小元素:" << t.search_minimun() << endl; cout << "刪除元素99" << endl; t.remove(99); cout << "最大元素:" << t.search_maximum() << endl; t.destory(); getchar(); return 0; }
運行結果:
中序遍歷:
35 37 47 51 58 62 73 88 93 95 99 最大元素:99 最小元素:35 刪除元素99 最大元素:95
4. 二叉查找樹完整代碼
在github上存放了二叉排序樹的vs項目工程。這是二叉排序樹的源代碼:
https://github.com/huanzheWu/Data-Structure/blob/master/BSTree/BSTree/BSTree.h