数据结构和算法(06)---二叉树(c++)
文章目錄
- 目錄
- 二叉樹
- 1.二叉樹的基本概念
- 2.二叉樹的應用和時間復雜度
- 3.二叉樹的插入
- 4.二叉樹的查找
- 5. 二叉樹的遍歷
- 6.二叉樹的刪除
- 二叉樹的基本操作
- 1.二叉樹的基礎操作
- 2.代碼實現
- 創(chuàng)建二叉樹和三種遍歷二叉樹的方法
目錄
- 數據結構:
- 邏輯結構:數組,棧,隊列,字符串,樹,圖
- 存儲結構:順序存儲,鏈式存儲
- C++常用的數據結構有:string , stack , queue , deque , vector , list , map , iterators.
二叉樹
1.二叉樹的基本概念
樹是一些節(jié)點的集合,節(jié)點之間用邊鏈接,節(jié)點之間不能有環(huán)路。上層的節(jié)點稱為父節(jié)點,下層節(jié)點稱為子節(jié)點。最上層的節(jié)點稱為根節(jié)點。
二叉樹是特殊的樹。對于每個節(jié)點而言,與之直接相連的子節(jié)點不能超過兩個(可以為0)。左邊的子節(jié)點稱為左子樹,右邊的子節(jié)點稱為右子樹。如下圖就是一顆二叉樹:
與樹相關的一些概念:
**葉子節(jié)點:**沒有任何子節(jié)點的節(jié)點稱為葉子節(jié)點
深度:對于任意節(jié)點N,其深度指的是從根節(jié)點到N的唯一路徑的長。根的深度為0。深度最深的葉子節(jié)點的深度為樹的深度。可以理解為:樹根是一個入口,離樹根越遠,就越深。如上圖:A、B、C的深度為1,D、E的深度為2。
高: 對于任意節(jié)點N,從N到一片樹葉的最遠路徑的長為N的高度。(只可以從從上到下不能經過從下到上的節(jié)點。)樹葉的高為0。樹的高為根的高。如上圖,根的高度為2。A的高度為1,其他節(jié)點高度為0。
2.二叉樹的應用和時間復雜度
二叉樹是一種常見的數據結構,常常用于查找,也運用于unix等常見操作系統(tǒng)的文件系統(tǒng)中。c++STL(標準模板庫)中的set和map也使用二叉樹中的紅黑樹實現。
- 二叉樹的查找思想基于:在二叉樹中,對于任意節(jié)點N,左子樹中的所有項的值不大于節(jié)點N中存儲的值,右子樹中的所有項的值不小于節(jié)點N中存儲的值。如下圖:
這樣,在查找時,只需要不斷比較需要查找的x與N的大小,若小于N中的值,只需要搜索左子樹,若大于N中的值,只需要搜索右子樹。這樣每次就能縮小搜索的范圍。經過證明,普通二叉樹的平均時間復雜度是O(LogN)。
看到這里,我們發(fā)現其實二叉樹的搜索思想和二分查找一致,每次不斷的減少搜索范圍。但是二者之間還是有區(qū)別的。
對于二分查找而言,每次的時間復雜度不會超過O(LogN)。但是對于二叉樹,搜索時間的復雜度取決于樹的形狀。在最壞情況下可能達到O(N)。如下圖,如果要找到10,則要查找5次。
那我們?yōu)槭裁催€要使用二叉樹而不直接使用二分查找來代替?
這是因為,二分查找一般基于數組,如果需要插入或刪除數據,則會帶來很大的開銷。因為每次插入或者刪除數據需要將改變節(jié)點之后的數據往后挪或者往前挪。但是對于二叉樹而言,只需要改變一下指向下一個節(jié)點的指針就可以很方便的實現插入或者刪除。而且一些特殊的二叉樹如紅黑樹可以保證查找的最壞復雜度不超過O(LogN)。
所以,如果是對于靜態(tài)數據,不需要改變的數據而言,采用數組存儲,使用二分查找比較好。而對于動態(tài)數據,需要頻繁插入或者刪除數據的,采取二叉樹存儲是較好的。
3.二叉樹的插入
思路:插入數據x,從根節(jié)點開始,不斷比較節(jié)點與x的大小。若x小于節(jié)點,下一次比較x與節(jié)點的左子樹,反之,比較x與節(jié)點的右子樹。直到遇到一個空的節(jié)點,插入數據。(我們不考慮插入重復數據) 。如下圖:
過程:比較4與7,4<7,再比較4與7的左子樹6,4<6,比較4與6的左子樹3,4>3,比較4與3的右子樹,為空,插入4。
代碼:
template< typename T> void BinaryTree<T>::insert(const T &theElement, BinaryNode * &t ) {if ( nullptr == t ){ //如果插入的節(jié)點沒有創(chuàng)建,則進行創(chuàng)建t = new BinaryNode (theElement);} else if ( theElement < t->element ) { //如果插入的節(jié)點小于當前的節(jié)點,則繼續(xù)搜索其左子樹insert( theElement, t->leftNode );} else if ( theElement > t->element ) { //如果插入的節(jié)點大于當前的節(jié)點,則繼續(xù)搜索其右子樹insert ( theElement, t->rightNode );} else {//重復的數據不添加到樹中 (找到節(jié)點后,直接插入到合適的位置)} };4.二叉樹的查找
思路:與插入類似,不斷比較插入值與節(jié)點的值。代碼如下
template< typename T> bool BinaryTree<T>::isFind(const T &theElement, BinaryNode * t ) const {if ( nullptr == t ){return false;} else if ( theElement < t->element ) {return isFind( theElement, t->leftNode );} else if ( theElement > t->element ) {return isFind ( theElement, t->rightNode );} else { //匹配return true;} };5. 二叉樹的遍歷
二叉樹的遍歷有三種方式:
- 前序遍歷(DLR):首先訪問根結點。然后如果有子樹,則對于左孩子也采用DLR的遍歷規(guī)則。沒有就忽略。然后如果有右子樹,則對右子樹也采用DRL的遍歷規(guī)則。沒有就忽略。
- 中序遍歷(LDR):首先訪問根節(jié)點的左子樹(對左子樹也采用LDR),沒有則忽略。再訪問根節(jié)點。最后則對右子樹也采用LDR的遍歷規(guī)則,沒有就忽略。
- 后序遍歷:首先訪問根節(jié)點的左子樹(對左子樹也采用LRD),沒有則忽略。再對右子樹也采用LRD的遍歷規(guī)則,沒有就忽略。最后則對右子樹也采用LRD的遍歷規(guī)則,沒有就忽略。
三種遍歷方式其實是根據根節(jié)點的訪問順序命名的。根最先方位為前序,次之訪問為中序遍歷。最后訪問為后序遍歷。
使用遞歸實現
前序遍歷:
中序遍歷:
template< typename T> void BinaryTree<T>::inOrder( BinaryNode *bNode ) const {if( nullptr != bNode ) {inOrder(bNode->leftNode);std::cout << bNode->element << " " ;inOrder(bNode->rightNode);} };后序遍歷:
template< typename T> void BinaryTree<T>::postOrder( BinaryNode *bNode ) const {postOrder(bNode->leftNode);postOrder(bNode->rightNode);std::cout << bNode->element << " " ; };6.二叉樹的刪除
代碼
二叉樹的完整代碼
二叉樹的基本操作
1.二叉樹的基礎操作
- 1.創(chuàng)建二叉樹
- 2.遞歸輸出二叉樹
- 2.1遞歸先序輸出
- 2.2遞歸中序輸出
- 2.3遞歸后序輸出
- 3.非遞歸輸出
- 3.1非遞歸先序輸出
- 3.2非遞歸中序輸出
- 3.3非遞歸后序輸出
- 4.層次遍歷二叉樹
- 5.求樹高
- 6.求樹葉子節(jié)點
- 7.按值查找對應節(jié)點,輸出左孩子結點值和右孩子結點值
- 8.計算所有節(jié)點數
2.代碼實現
#include<iostream> #include<string> #include<stack> #include<deque> #include<fstream> using namespace std;//const int MAX_N = 100; //數據節(jié)點 class Node { public:char data;//數據class Node *lchild;//左節(jié)點class Node *rchild;//右節(jié)點 };//二叉樹 class Tree { public:Tree(){}~Tree(){}//構建二叉樹void Create(string name){ifstream readfile;string str;readfile.open(name);if (readfile.is_open()){getline(readfile, str);//讀取一行}readfile.close();CreateNode(str);//構建二叉樹}//先序遍歷非遞歸算法void Disp(){if (t == NULL){return;}stack<Node *> m_stack;//定義棧m_stack.push(t);while (!m_stack.empty()){Node *p = m_stack.top();//賦值一份當前雙親節(jié)點cout << p->data << ends;m_stack.pop();if (p->rchild)//先存儲右子樹,確保先輸出左子樹{m_stack.push(p->rchild);}if (p->lchild)//后存儲左子樹{m_stack.push(p->lchild);}}}//非遞歸中序遍歷二叉樹void DispMid(){if (t == NULL){return;}Node *p = t;stack<Node *>m_stack;while (p != NULL || !m_stack.empty()){while (p != NULL)//一路直走至左下角{m_stack.push(p);p = p->lchild;}if (!m_stack.empty()){p = m_stack.top();//備份當前棧頂地址m_stack.pop();cout << p->data << ends;p = p->rchild;}}}//非遞歸后序遍歷二叉樹void DispBehid(){if (t == NULL){return;}Node *pre = NULL, *p = t;stack<Node *>m_stack;while (p != NULL || !m_stack.empty()){while (p != NULL)//一路直走至左下角{m_stack.push(p);p = p->lchild;}p = m_stack.top();//右子樹為空或者已訪問,輸出當前節(jié)點if (p->rchild == NULL || p->rchild == pre){cout << p->data << ends;pre = p;//將當前結點地址賦值pre作為下一次判斷標志,防止重復訪問m_stack.pop();p = NULL;//p賦值空以便訪問右子樹}else{p = p->rchild;//訪問子樹的右子樹}}}//層次遍歷void level_display(){if (t == NULL){return;}deque<Node *>m_qu;//定義隊列m_qu.push_back(t);//樹根入隊列while (!m_qu.empty()){Node *p = m_qu.front();//拷貝當前對頭cout <<p->data << ends;//輸出m_qu.pop_front();if (p->lchild)//左孩子入隊列{m_qu.push_back(p->lchild);}if (p->rchild)//右孩子入隊列{m_qu.push_back(p->rchild);}}}//遞歸先序遍歷輸出二叉樹void display(){cout << "遞歸先序:";output(t);cout << endl;}//遞歸中序遍歷輸出二叉樹void displayMid(){cout << "遞歸中序:";outputMid(t);cout << endl;}//遞歸后序遍歷輸出二叉樹void displayBhind(){cout << "遞歸后序";outputBhind(t);cout << endl;}//二叉樹高度void Height(){int height = get_height(t);cout << "Height: " << height << endl;}//輸出葉子節(jié)點值void display_leaf(){cout << "Leaves: ";output_leaf(t);cout << endl;}//查找二叉樹中值data域為elem的節(jié)點void find_node(char elem){Node *res = NULL;res = find_node(t, elem, res);if (res != NULL){cout << "nice." << endl;if (res->lchild){cout << "left child:";cout << leftchild(res)->data << endl;}if (res->rchild){cout << "right child:";cout << rightchild(res)->data << endl;}}else{cout << "NO." << endl;}}//計算節(jié)點數void nodes_count(){int sum;if (t == NULL)//若為空,則0個節(jié)點{sum = 0;}else{sum = node_count(t);cout << "Total Nodes:" << sum + 1 << endl;}} private:Node *t;//構建二叉樹void CreateNode(string str){stack<Node *> m_stack;Node *p;int k;while (str.length() != 0){//若當前為'(',將雙親節(jié)點推入棧,下一位存儲的p值作為左節(jié)點處理if (str[0] == '('){m_stack.push(p); k = 1;}//為右括號則棧頂退出一位else if (str[0] == ')'){m_stack.pop();}//為',',則下一個字符作右節(jié)點處理else if (str[0] == ','){k = 2;}//存儲值用作雙親結點else{p = (Node *)malloc(sizeof(Node));p->data = str[0];p->lchild = p->rchild = NULL;//樹根為空時,將第一個節(jié)點作為樹根并賦值給私有成員變量if (t == NULL){t = p;}//樹根不為空else{if (k == 1)//作為左節(jié)點處理,將棧中雙親節(jié)點的左指針指向當前節(jié)點{m_stack.top()->lchild = p;}else//作為右節(jié)點處理{m_stack.top()->rchild = p;}}}//重構串,除去首字符,并將串長度減小1str.assign(str.substr(1, str.length() - 1));}}//遞歸先序遍歷輸出二叉樹void output(Node *t){if (t != NULL)//當樹根不為空時{cout << t->data;//輸出if (t->lchild != NULL || t->rchild != NULL)//左/右結點不為空時遞歸到下一層{cout << "(";output(t->lchild);if (t->rchild != NULL)//當左節(jié)點遍歷結束后,左節(jié)點遞歸返回一層,遞歸右節(jié)點{cout << ",";}output(t->rchild);cout << ")";}}}//遞歸中序遍歷二叉樹void outputMid(Node *t){if (t == NULL)//空則返回{return;}else{cout << "(";outputMid(t->lchild);//遞歸左孩子節(jié)點if (t->rchild != NULL){cout << ",";}cout << t->data;//輸出outputMid(t->rchild);//遞歸右孩子結點cout << ")";}}//遞歸后序遍歷輸出二叉樹void outputBhind(Node *t){if (!t)//空則返回{return;}else{cout << "(";outputBhind(t->lchild);//遞歸左孩子節(jié)點if (t->rchild != NULL){cout << ",";}outputBhind(t->rchild);//遞歸右孩子結點cout << t->data;//輸出cout << ")";}}//求樹高int get_height(Node *t){int leftheight, rightheight;if (t == NULL)//遞歸至不存在子節(jié)點時返回0{return 0;}else{leftheight = get_height(t->lchild);//遞歸求左子樹高度rightheight = get_height(t->rchild);//遞歸其右子樹高度return leftheight > rightheight ? leftheight+1 : rightheight+1;//遞歸返回時返回最大值}}//查找左節(jié)點Node *leftchild(Node *p){return p->lchild;}//查找右節(jié)點Node *rightchild(Node *p){return p->rchild;}//輸出葉子節(jié)點void output_leaf(Node *t){if (t != NULL)//樹根不為空時{//當前節(jié)點沒有子節(jié)點時輸出節(jié)點數據if (t->lchild == NULL&&t->rchild == NULL){cout << t->data << ends;}output_leaf(t->lchild);//遞歸左子樹output_leaf(t->rchild);//遞歸右子樹}}//查找二叉樹中值data域為elem的節(jié)點Node * find_node(Node *t, char elem, Node *res = NULL){//Node *res = NULL;if (t == NULL)//若當前節(jié)點為空,則返回結束{return NULL;}else{if (t->data == elem)//若找到值,返回地址{//res = t;return t;}else{if (res == NULL)//若保存結果的指針不為空,則遞歸查找左節(jié)點{res = find_node(t->lchild, elem, res);}if (res == NULL)//若保存結果的指針不為空,且左節(jié)點為搜索到,則遞歸查找右節(jié)點{res = find_node(t->rchild, elem, res);}}return res;}}//計算節(jié)點數int node_count(Node *t){int lcount = 0, rcount = 0;if (t == NULL)//空則返回{return 0;}else{if (t->lchild != NULL)//遍歷左孩子節(jié)點{lcount = node_count(t->lchild);lcount += 1;}if (t->rchild != NULL)//遍歷右孩子節(jié)點{rcount = node_count(t->rchild);rcount += 1;}return lcount + rcount;//返回當前左右孩子節(jié)點數}} };int main() {Tree m_tree;m_tree.Create("data");m_tree.display();//遞歸先序輸出m_tree.displayMid();//遞歸中序輸出m_tree.displayBhind();//遞歸后序輸出m_tree.Height();//樹高m_tree.display_leaf();//葉子節(jié)點cout << "非遞歸先序:";//cout << "Fir:";m_tree.Disp();//非遞歸先序遍歷cout << endl;cout << "非遞歸中序:";//cout << "Mid:";m_tree.DispMid();//非遞歸中序遍歷cout << endl;cout << "非遞歸后序:";//cout << "Bac:";m_tree.DispBehid();//非遞歸后序遍歷cout << endl;cout << "層次遍歷:";m_tree.level_display();//層次遍歷cout << endl;cout << "Input element:";char elem;cin >> elem;m_tree.find_node(elem);//按節(jié)點值查找m_tree.nodes_count();//計算節(jié)點數return 0; }創(chuàng)建二叉樹和三種遍歷二叉樹的方法
#include<iostream> #include<fstream> #include<string.h>using namespace std; //定義二叉樹的結構體 typedef struct BTree{int val;struct BTree *left , *right; }BTree; //二叉樹類的實現 class Tree{ public:Tree();~Tree();//其他操作方法BTree *create_node(int level,string pos); void PreOrder(BTree *t); //先序遍歷 void InOrder(BTree *t); //中序遍歷 void PostOrder(BTree *t); //后序遍歷BTree* root; //定義根節(jié)點 };BTree* Tree::create_node(int level,string pos){int data;BTree *node = new BTree;cout<<"please enter data:level"<<level<<" "<<pos<<endl;cin>>data;//若輸入的數據為0,則把該結點的子結點置為空 if(data == 0) { return NULL; } node->val= data; /*create_node()的 參數用于在給二叉樹賦值時表明 現在賦值的是哪個結點*/ node->left = create_node(level+1,"left"); node->right= create_node(level+1,"right"); return node; }void Tree::PreOrder(BTree *t) { if(t) { cout<<t->val<<endl;; PreOrder(t->left); PreOrder(t->right); } } void Tree::InOrder(BTree *t) { if(t) { InOrder(t->left); cout<<t->val<<endl;; InOrder(t->right); } } void Tree::PostOrder(BTree *t) { if(t) { PostOrder(t->left); PostOrder(t->right); cout<<t->val<<endl; } } int main() { Tree tree; tree.root = tree.create_node(1,"root"); cout<<"Pre"<<endl; tree.PreOrder(tree.root); cout<<"In"<<endl; tree.InOrder(tree.root); cout<<"Post"<<endl; tree.PreOrder(tree.root); return 0; }輸入:
7
4 10
3 5 8 12
輸出:
總結
以上是生活随笔為你收集整理的数据结构和算法(06)---二叉树(c++)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 180°舵机的使用步骤
- 下一篇: C++(1)--概况、开发工具、hell