1. 程式人生 > >【資料結構】二叉樹的實現

【資料結構】二叉樹的實現

上篇部落格中,我們詳細說明了樹和二叉樹的資料結構及其特徵,本次,我們用C++來實現一下二叉樹

定義二叉樹節點結構

二叉樹需要定義指向左孩子和右孩子節點的指標,還有儲存的資料;我們在這把它的建構函式也寫出來

//定義一個二叉樹節點
template<typename T>
struct BinaryTreeNode
{
	T _data;//資料
	BinaryTreeNode<T> *_left;//左孩子
	BinaryTreeNode<T> *_right;//右孩子

	BinaryTreeNode(const T& x)//節點的建構函式
		:_data(x)
		, _left(NULL)
		, _right(NULL)
	{}
};

定義二叉樹結構

在此,我們定義二叉樹的結構,先實現他的構造、解構函式,拷貝建構函式和賦值運算子過載。

通過呼叫protected中實現Create、Copy、Destory三個遞迴函式來實現。

//定義二叉樹
template<typename T>
class BinaryTree
{
	typedef BinaryTreeNode<T> Node;//重新命名為Node
public:
	BinaryTree()
		:_root(NULL)
	{}
	BinaryTree(T* arr, const size_t size,const T& invalid = T())
	{
		assert(arr);
		size_t index = 0;
		_root = CreateTree(arr, size, index, invalid);//用遞迴的形式建立樹,通過呼叫保護成員函式CreateTree() 
	}

	//拷貝建構函式
	BinaryTree(const BinaryTree& b)
	{
		_root = Copy(b._root);
	}

	//賦值運算子過載
	BinaryTree&operator=(BinaryTree t)
	{
		if (this != &t)
		{
			std::swap(t._root, _root);
		}
		return *this;
	}

	//解構函式
	~BinaryTree()
	{
		if (_root != NULL)
		{
			Destory(_root);
			_root = NULL;
		}
	}

	//先序遍歷,中序遍歷,後序遍歷
	void PrevOrder();
	
	void InOrder();
	
	void PostOrder();

	//三種遍歷方式的非遞迴形式
	void PrevOrderNonR();

	//中序遍歷的非遞迴,只用把壓棧訪問元素的位置改到出棧的時候訪問即可
	void InOrderNonR();

	//後序非遞迴遍歷
	void PostOrderNonR();

	//層序遍歷
	void LevelOrder();

	//求二叉樹的節點個數
	size_t Size(); 

	//求二叉樹的深度
	size_t Depth();

	//求二叉樹的葉子節點
	size_t GetLeafSize();

	//求第K層節點的個數
	size_t GetKLevelSize(size_t k);
protected:
	Node* _root;
	
	//根據字元陣列構建二叉樹 
	Node* CreateTree(T* arr,const size_t size, size_t& index,const T& invalid = T())
	{ 
		//陣列未完 並且 不為非法值 
		if (index < size && arr[index]!=invalid)
		{
			//生成節點遞迴構建 
			Node* root = new Node(arr[index]);
			root->_left = CreateTree(arr,size,++index,invalid);
			root->_right = CreateTree(arr, size, ++index, invalid);
			
			//返回生成的節點 
			return root;
		}
		
		//返回空 
		return NULL;
	}
	
	//遞迴銷燬二叉樹 
	void Destory(Node* root)
	{
		assert(root);
		
		//不為空,遞迴銷燬左子樹 
		if (root->_left != NULL)
			Destory(root->_left);
		
		//銷燬完成賦值為NULL 
		root->_left = NULL;
		
		//不為空,遞迴銷燬右子樹 
		if (root->_right != NULL)
			Destory(root->_right);
		
		//銷燬完成賦值為NULL 
		root->_right = NULL;
			
		//銷燬當前節點 
		delete[] root;
		root = NULL;
		return;
	}
	
	//遞迴拷貝二叉樹 
	Node* Copy(Node* root)
	{	
		//根為空 
		if (root == NULL)
			return NULL;
			
		//呼叫Node的建構函式 生成一個節點 
		Node* newnode = new Node(root->_data);
		
		//遞迴拷貝 
		newnode->_left = Copy(root->_left);
		newnode->_right = Copy(root->_right);
		return newnode;
	}
};

二叉樹遞迴的遍歷方法

(1)先序

這裡都要採用遞迴的方法,通過呼叫protected成員函式 _PrevOrder來遍歷

void PrevOrder()
{
	//採用遞迴方法,呼叫protected內部的_PrevOrder函式,中序和後序也是一樣
	_PrevOrder(_root);
	cout << endl;
}

protected:

void _PrevOrder(Node* root)
{
	//節點為空,返回
        if (root == NULL)
		return;

        //先訪問該節點
       cout << root->_data << " ";
        
        //遞迴訪問左子樹
       _PrevOrder(root->_left);
        //左子樹訪問完成後訪問右子樹
       _PrevOrder(root->_right);
}

(2)中序

同理,呼叫protected的_InOrder()

        void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
protected:
       void _InOrder(Node* root)
	{
                //節點為空返回
		if (root == NULL)
			return;
            
                //先訪問左子樹
		_InOrder(root->_left);

                //訪問完成後訪問該根節點
		cout << root->_data << " ";

                //訪問右子樹
		_InOrder(root->_right);
	}

(3)後序

       void PostOrder()
    {
        _PostOrder(_root);
        cout << endl;
    }
protected:
        void _PostOrder(Node* root)
	{
		if (root == NULL)
			return;

                //先訪問左子樹
		_PostOrder(root->_left);

                //左子樹訪問完了,訪問右子樹
		_PostOrder(root->_right);

                //左子樹右子樹訪問完了,訪問該節點
		cout << root->_data << " ";
	}

二叉樹非遞迴的遍歷方法

(1)先序

所有的遞迴程式都可以用非遞迴來實現;

簡單的遞迴程式可以改成迴圈實現;

所有的遞迴程式都可以棧來實現;

所以我們現在要實用STL的棧

public:        
        void PrevOrderNonR()
	{
		//定義一個棧和一個指標變數
		stack<Node*> s;
		Node* cur = _root;

		//在cur,或者棧為空時
		while (cur || !s.empty())
		{
			//遞迴遍歷左字數
			while (cur)
			{
				//訪問元素
				cout << cur->_data << " ";
				
				//進行壓棧
				s.push(cur);
				
				//指向左孩子
				cur = cur->_left;
			}
			//一路向左,此時cur為空

			//取棧頂元素
			Node* top = s.top();
			//出棧
			s.pop();
			//訪問右孩子
			cur = top->_right;
		}
		cout << endl;
	}

(2)中序

一樣是用棧,只用改變訪問元素的位置即可

public:
       //中序遍歷的非遞迴,只用把壓棧訪問元素的位置改到出棧的時候訪問即可
	void InOrderNonR()
	{
		//定義一個棧和指標變數cur
		Node* cur = _root;
		stack<Node*> s;

		//判斷是否結束
		while (cur || !s.empty())
		{
			//迴圈壓入左字數
			while (cur)
			{
				s.push(cur);

				//中序,先不要訪問元素
				cur = cur->_left;
			}
			Node* top = s.top();
			s.pop();

			//此時再訪問元素
			cout << top->_data << " ";
			cur = top->_right;
		}
	}

(3)後序

後序的話,出來需要判斷右子樹的訪問情況,否則會出錯

public:
        //後序非遞迴遍歷
	void PostOrderNonR()
	{
		//與中序,先序相比,多定義了一個prev指標,儲存上一個訪問的元素
		Node* prev = NULL;

		//定義一個棧s和指向節點的臨時變數cur
		Node* cur = _root;
		stack<Node*> s;

		//判斷是否結束
		while (cur || !s.empty())
		{
			//遞迴壓入左子樹
			while (cur)
			{
				//依舊不訪問元素
				s.push(cur);
				cur = cur->_left;
			}

			//取棧頂元素進行判斷
			Node* top = s.top();

			//如果站定元素的右子樹為空  或者  右子樹已經被訪問
			if (top->_right == NULL || top->_right == prev)
			{
				//列印根
				cout << top->_data << " ";
				
				//將剛剛訪問過的元素讓prev儲存起來
				prev = top;

				//出棧
				s.pop();
			}
			//右子樹不為空 並且 還沒有被訪問
			else
			{
				//訪問右字數
				cur = top->_right;
			}
		}
	}

(4)層序

層序遍歷,我們需要藉助另一個數據結構---佇列

每次將根節點入棧,出棧後將它的孩子入棧;

以此論推

public:        
        //層序遍歷
	void LevelOrder()
	{
		if (_root == NULL)
			return;

		//利用佇列來儲存每一層的節點
		queue<Node*> q;
		//壓入根節點
		q.push(_root);

		//佇列為空,訪問結束
		while (q.empty() == false)
		{
			//取隊頭元素,進行訪問
			Node* tmp = q.front();
			cout << tmp->_data << " ";

			//彈出佇列的首元素
			q.pop();

			//哪個孩子不為空,就壓入該孩子
			if (tmp->_left != NULL)
				q.push(tmp->_left);
			if (tmp->_right != NULL)
				q.push(tmp->_right);
		}
		cout << endl;
	}

 遞迴求深度、節點個數、葉子節點個數和第K層節點個數

public:        
        //求二叉樹的節點個數
	size_t Size()
	{
		return _Size(_root);
	}

	//求二叉樹的深度
	size_t Depth()
	{
		return _Depth(_root);
	}

	//求二叉樹的葉子節點
	size_t GetLeafSize()
	{
		size_t count = 0;
		_GetLeafSize(_root,count);
		return count;
	}

	//求第K層節點的個數
	size_t GetKLevelSize(size_t k)
	{
		assert(k > 0);
		return _GetKLevelSize(_root,k);
	}

protected:
       size_t _Size(Node* root)
	{
		if (root == NULL)
			return 0;
                //遞迴求解子問題。返回左子樹的節點個數+右子樹節點個數+自己 
               return _Size(root->_left) + _Size(root->_right) + 1;
	}
	size_t _Depth(Node* root)
	{
		if (root == NULL)
			return 0;
                //求左子樹的深度和右子樹的深度,用返回值接收  
               size_t leftDepth = _Depth(root->_left);
		size_t rightDepth = _Depth(root->_right);

                //返回子樹中最大的+當層的深度1
		return leftDepth > rightDepth ? leftDepth + 1: rightDepth + 1;
	}

        //通過傳入引用的count,來計數
       void _GetLeafSize(Node* root,size_t &count)
	{
		if (root->_left == NULL && root->_right == NULL)
		{
			count++;
			return;
		}
		if (root->_left != NULL)
			_GetLeafSize(root->_left,count);
		if (root->_right!=NULL)
			_GetLeafSize(root->_right,count);
	}

        //求第K層的節點
       size_t _GetKLevelSize(Node* root ,size_t k)
	{
		if (root == NULL)
			return 0;
		if (k == 1)
			return 1;

		return _GetKLevelSize(root->_left,k-1) + _GetKLevelSize(root->_right,k-1);
	}