【資料結構】二叉樹的實現
阿新 • • 發佈:2019-01-06
上篇部落格中,我們詳細說明了樹和二叉樹的資料結構及其特徵,本次,我們用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);
}