1. 程式人生 > >二叉樹(0)——二叉樹的實現與二叉樹的遍歷

二叉樹(0)——二叉樹的實現與二叉樹的遍歷

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.層次遍歷

//待補充