二叉樹的實現與二叉樹的遍歷
0.二叉樹的實現(C++)
未完,待補充
#include <iostream> #include<iostream> #include<queue> #include<stack> using namespace std; //二叉樹結點的定義 template <typename T> struct BinTreeNode { T value; BinTreeNode* left; //左孩子 BinTreeNode* right; //右孩子 BinTreeNode(const T &value): //為什麼此處宣告為const? //因為宣告的是模板,不確定傳入引數的大小,宣告為資料的常引用,保證頭不會太大, //並且該函式不改變,傳入引數的值,在進行列表初始化時,將形參value進行拷貝賦值給屬性value value(value),right(NULL),left(NULL) //此處列表初始化的賦值順序,不是按照在此處的書寫順序, //而是按照前面成員變數的定義順序,即value,left,right {} }; //二叉樹類的實現 template <typename T> class BinTree { //私有屬性 private: BinTreeNode* root; //為使用者提供的介面 public: //預設建構函式 BinTree() :root(nullptr) { } //有參構造 BinTree(BinTreeNode* root) { } //解構函式 ~BinTree() { destroyBinTree(root); } //賦值運算子過載 BinTree& operator=(const BinTree& binTree) { if(this!=binTree) { destroyBinTree(root); copyBinTree(binTree->root); } } //拷貝建構函式 BinTree(const BinTree &binTree) { root=copyBinTree(binTree->root); } //四種遍歷(具體實現見後文) //中序遍歷 //前序遍歷 //後序遍歷 //層序遍歷 //節點個數 //葉節點個數 //樹的高度 //第k層結點的個數 //查詢函式:在當前樹中查詢資料值為data的節點 //查詢某個結點的父親結點 //查詢某個節點的左孩子節點 //查詢某個節點的右孩子節點 }
1.中序遍歷
(1)遞迴實現
//中序遍歷遞迴實現
void inOrderTraverse(BinTreeNode *root)
{
if(root)
{
inOrderTraverse(root->left);
visit(root);
inOrderTraverse(root->right);
}
}
(2)非遞迴實現(棧實現)
思路:由於二叉樹的結點只能通過根結點向下訪問,從根結點開始入棧,然後不斷迭代讓左孩子結點入棧,直到左孩子結點為null時,開始訪問棧頂元素(即在出棧前夕開始訪問該節點),然後出棧,然後再訪問出棧結點的右結點,讓右結點入棧
對於每一組父親結點和兩個孩子
第一波入棧順序:根結點->左孩子,
則出棧順序:左孩子->根結點
第二波入棧:右孩子
出棧:右孩子
總是在出棧前夕的時候訪問就保證了訪問與出棧相同的順序:左孩子->根結點->右孩子
時間複雜度:O(n)
空間複雜度:O(n)
程式碼實現:
//中序遍歷非遞迴實現 void inOderTraverse(BinTreeNode *root) { stack<BinTreeNode *> s; BinTreeNode *p=root; //定義遍歷變數指標p while(p || !s.empty()) //中序遍歷的最終遍歷變數指標p指向為空,並且彈出了所有的入棧的結點 { if(p) //如果指標p指向的結點存在,就將它入棧 { s.push(p); //選擇的每一層都先讓根結點先入棧,然後讓它的左結點入棧,這樣利用棧的 //先進後出的特點,出棧的時候左孩子結點會先出棧,然後根結點再出棧, //然後根結點出棧之後訪問其右結點,所以整個過程正好符合左,根,右 p=p->left; }else { p=s.top(); visit(p); //這樣就訪問了當前層的根結點 s.pop(p); //從棧中彈出訪問過的結點 p=p->right; } } }
(3)Morris方法
//待補充
2.前序遍歷
(1)遞迴實現
//前序遍歷遞迴實現
void preOrderTraverse(BinTreeNode* root)
{
if(root)
{
visit(root);
preOrderTraverse(root->left);
preOrderTraverse(root->right);
}
}
(2)非遞迴實現
思路:
思路與上述中序遍歷相似,入棧和出棧順序相同,更改的是訪問的時機,即在入棧前夕訪問結點
對於每一組父親結點和兩個孩子
第一波入棧順序:根結點->左孩子,
則出棧順序:左孩子->根結點
第二波入棧:右孩子
出棧:右孩子
總是在入棧前夕的時候訪問就保證了訪問與入棧相同的順序:左孩子->根結點->右孩子
時間複雜度:O(n)
空間複雜度:O(n)
程式碼實現:
//前序遍歷非遞迴實現(棧實現)
void preOrderTraverse(BinTreeNode *root)
{
stack<BinTreeNode *> s;
BinTreeNode *p=root; //定義遍歷變數指標p
while(p || !s.empty()) //中序遍歷的最終遍歷變數指標p指向為空,並且彈出了所有的入棧的結點
{
if(p) //如果指標p指向的結點存在,就將它入棧
{
visit(p); //先訪問根結點,再將該結點入棧,直到左孩子為空開始返回
//每次先訪問根結點,然後指向本次的左孩子時,對於下一層又是根結點
s.push(p);
p=p->left;
}else
{
p=s.top();
s.pop(p); //在彈出本層根結點時,再去訪問右結點
p=p->right;
}
}
}
(3)Morris方法
//待補充
3.後序遍歷
(1)遞迴實現
//後序遍歷遞迴實現
void postOrderTraverse(BinTreeNode* root)
{
if(root)
{
postOrderTraverse(root->left);
postOrderTraverse(root->right);
visit(root);
}
}
(2)非遞迴實現
思路:
中序遍歷和前序遍歷的入棧順序為
第一波入棧順序:根結點->左孩子,
則出棧順序:左孩子->根結點
我們沒辦法在入棧的時候先入棧左孩子,右孩子
但是在出棧的時候可以在左孩子出棧後,插入右孩子入棧,先判斷右孩子是否已經被訪問,沒有被訪問的話,就先將右孩子入棧,然後入棧的順序就儲存了同一組的根結點->右孩子
然後出棧順序就為右孩子->根結點
所以整體思路:
第一波入棧順序:根結點->左孩子,
則出棧順序:左孩子
判斷根結點是否有右孩子或者有孩子是否已經被訪問,
沒有被訪問的話,第二波入棧:右孩子
則出棧順序:右孩子->根結點
所以增加了判斷就保證了出棧順序為左孩子->右孩子->根結點
所以我們需要增加一個變數儲存剛才已經訪問過的結點,如果該結點儲存的是左孩子,那麼就會進行入棧,如果儲存的時右孩子,表明右孩子已經被訪問,可以訪問根結點了
經驗:當我們無法改變入棧的順序時,我們可以通過判斷,先讓部分出棧,然後又讓其他元素入棧,這樣就改變了出棧的順序
時間複雜度:O(n)
空間複雜度:O(n)
程式碼實現:
//後序遍歷非遞迴實現(棧實現)
void postOrderTraverse(BinTreeNode *root)
{
BinTreeNode* visited=nullptr;//儲存被訪問的右結點,用於判斷當前結點的右結點是否已被訪問
BinTreeNode* p=root;
stack<BinTreeNode*> s;
while(p || !s.empty())
{
//從頭結點開始,不斷迭代讓左孩子入棧
while(p)
{
s.push(p);
p=p->left;
}
//當左孩子全部入棧時,讓p棧頂,開始準備出棧
p=s.top();
//當前結點要出棧,要進行訪問,先必須保證它的右結點不存在或已經被訪問,
//否則,先讓它的右結點先入棧
if(p->right && p->right != visited)
{
p=p->right;
}
//當前結點的右結點不存在或已經被訪問,就可以訪問當前的結點,並且讓它出棧
else
{
visit(p);
s.pop();
visited=p;//訪問過後就將判斷結點設定為剛才訪問過的結點,
p=nullptr;//讓p為空,是因為對於當前層來說訪問完當前結點就結束了
}
}
}
(3)Morris方法
//待補充
4.層次遍歷
//待補充