數據結構系列文章 |
---|
數據結構圖文解析之:數組、單鏈表、雙鏈表介紹及C++模板實現 |
數據結構圖文解析之:棧的簡介及C++模板實現 |
數據結構圖文解析之:隊列詳解與C++模板實現 |
數據結構圖文解析之:樹的簡介及二叉排序樹C++模板實現. |
數據結構圖文解析之:AVL樹詳解及C++模板實現 |
數據結構圖文解析之:二叉堆詳解及C++模板實現 |
數據結構圖文解析之:哈夫曼樹與哈夫曼編碼詳解及C++模板實現 |
樹是一種數據結構,它是n(n>=0)個節點的有限集。n=0時稱爲空樹。n>0時,有限集的元素構成一個具備層次感的數據結構。
html
區別於線性表一對一的元素關係,樹中的節點是一對多的關係。樹具備如下特色:node
樹有許多相關的術語與概念,在學習樹的結構以前,咱們要熟悉這些概念。git
二叉樹或者爲空集,或者由一個根節點和兩棵互不相交的、分別稱爲左子樹和右子樹的二叉樹組成。從定義能夠看出一棵二叉樹:github
根據定義,一棵二叉樹有5中基本形態:算法
全部節點都只有左子樹的二叉樹叫作左斜樹,全部節點都只有右子樹的二叉樹叫作右斜樹。左斜樹和右子樹統稱爲斜樹。
斜樹已經退化成線性結構,二叉樹在查找上表現出來優異性能在斜樹得不到體現。
數組
注意:爲了只關注重點,咱們全部的節點都採用統一淺綠色着色,如有特殊節點將在圖中備註數據結構
滿二叉樹要知足兩個條件:函數
在一樣深度的二叉樹中,滿二叉樹的節點數目是最多的,葉子數也是最多的。
post
在一棵二叉樹中,只有最下兩層的度能夠小於2,而且最下一層的葉子節點集中出如今靠左的若干位置上。
或者這樣定義:對一棵具備n個節點的二叉樹按層序從左到右編序,二叉樹樹某個節點的編序與一樣位置的滿二叉樹節點的編序相同若是全部節點都知足這個條件,則二叉樹爲徹底二叉樹。
從定義能夠看出: 滿二叉樹必定是徹底二叉樹;徹底二叉樹不必定是滿二叉樹。性能
二叉排序樹也稱爲二叉搜索樹或二叉排序樹。二叉排序樹的節點包含鍵值key。二叉排序樹或者是一棵空樹,不然要求:
根據定義,二叉查找樹中沒有重複key的節點。
在實際的應用中,二叉排序樹的應用比較多,咱們後面要講的AVL樹自己也是一種二叉排序樹。
證實:利用數學概括法進行證實
證實:二叉樹節點數最多時,每層的節點樹都必須最多。
根據性質一,深度爲k的二叉樹的節點數最多爲: 2^0 + 2^1 +....+2^(k-1) = 2 ^ k -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
證實:高度爲h的二叉樹最多有2{h}–1個結點。反之,對於包含n個節點的二叉樹的高度至少爲log2(n+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; };
//二叉查找樹類 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。它包含了:
提供的其餘接口都有相應的備註說明。
假設咱們要爲數組 a[] = {10 , 5 , 15 , 6 , 4 , 16 }構建一個二叉排序樹,咱們按順序逐個插入元素。
插入過程是這樣的:
從這個過程咱們能夠總結出插入新元素的步驟:
該過程的實現代碼:
/*插入函數*/ 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; //指明新節點的父節點 };
將構建出來的新節點插入二叉排序樹時,須要修改連接指針的指向。
遍歷平衡二叉樹,就是以某種方式逐個「訪問」二叉樹的每個節點。「訪問」是指對節點的進行某種操做,例如輸出節點的值。
平衡二叉樹是有序樹,嚴格區分左子樹與右子樹,若是規定左子樹先於右子樹的次序,咱們有三種方式遍歷二叉樹:
咱們以如圖的兩棵二叉排序樹進行遍歷的算法演示。
若二叉樹爲空,則空操做返回,不然先訪問根節點,而後前序遍歷左子樹,再前序遍歷右子樹。(簡記爲: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
對於一棵二叉排序樹,中序遍歷時恰好能夠輸出一個非遞減的序列。例如前序遍歷圖九樹a:3 4 5 6 10 15 16,則可稱:
一個節點的前驅節點有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; };
刪除二叉排序樹的某個節點有三種狀況:
對於第一種狀況,咱們的處理方式是將前驅節點的值保存在當前結點,繼而刪除前驅節點。
對於第二種狀況,咱們直接用子樹替換被刪節點。
對於第三種狀況,咱們能夠直接刪除節點。
刪除節點的代碼:
/*刪除指定節點*/ 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); } };
咱們能夠遞歸或非遞歸地進行元素的查找。元素的查找過程與元素的插入過程一致,也是在不斷地與當前結點進行比較,若值比當前節點的值大,則在右子樹進行查找,若值比當前節點的值小,則在左子樹進行查找,能夠看到這是一個很適合遞歸操做的過程。而因爲二叉排序樹這種左小右大的節點特徵,也很容易進行非遞歸查找。
/*查找指定元素的節點(非遞歸)*/ 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); };
二叉排序樹的最小值位於其最左節點上;最大值位於其最右節點上:
/*尋找最小元素*/ 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; };
使用後序遍歷遞歸銷燬二叉樹
/*銷燬二叉樹*/ 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; } };
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
在github上存放了二叉排序樹的vs項目工程。這是二叉排序樹的源代碼:
https://github.com/huanzheWu/Data-Structure/blob/master/BSTree/BSTree/BSTree.h
原創文章,轉載請註明出處:http://www.cnblogs.com/QG-whz/p/5168620.html#_label0