如何寫一顆二叉樹(遞迴)【菜鳥學習日記】
阿新 • • 發佈:2019-02-01
老規矩,開篇點題,今天寫了二叉樹,整理總結一下
要寫二叉樹,先畫出一顆來
二叉樹我是用連結串列來實現的
1、每一個節點包含資料,左指標和右指標,分別指向左孩子和右孩子
//建立節點型別
//節點中有資料,有指標
template<class T>
struct BinaryTreeNode
{
T _data;//資料
BinaryTreeNode<T> *_left;//左孩子
BinaryTreeNode<T> *_right;//右孩子
//初始化
BinaryTreeNode(const T&x)
:_data(x)
, _left(NULL )
, _right(NULL)
{}
};
2、然後開始建立一個二叉樹類
並建立樹的各個節點
思路:
‘#’是我們這裡用到的識別符號(invalid),用來標記的,當然也可以用其它的,當遇到#就代表為空,沒有節點了
從根節點開始按前序的順序遍歷建立(這裡的遍歷用了遞迴的方法)
class BinaryTree
{
typedef BinaryTreeNode<T> Node;
public:
BinaryTree(T* a,size_t n,const T&invalid)
{
size_t index = 0;
_root = GreateTree(a, n, invalid, index);
}
Node* GreateTree(T* a, size_t n, const T&invalid,size_t &index)
{
//根->左子樹->右字樹
Node* root = NULL;
if (a[index] != invalid)
{
root = new Node(a[index]);//建立根節點
root->_left = GreateTree(a, n, invalid, ++index );//左樹
root->_right = GreateTree(a, n, invalid, ++index);//右樹
}
return root;
}
protected:
Node* _root;//根節點
};
3、建立好一棵樹,就可以實現它的其它介面了
前序、種序、後續遍歷,遍歷其實就是
列印的順序不同
有一點要注意的是,因為我們遍歷時要有根節點引數,但類外拿不到樹的根節點,所以再寫一些要用根節點的介面時,要有一個無參型別的函式,在類的內部再將根節點傳參訪問
//前序遍歷
void PrevOrder()
{
PrevOrder(_root);
}
//根->左->右
void PrevOrder(Node* root)
{
//為空樹,返回
if (root == NULL)
{
return;
}
//不為空樹
cout << root->_data <<" ";
PrevOrder(root->_left);
PrevOrder(root->_right);
}
//中序
void MidOrder()
{
MidOrder(_root);
}
//左->根->右
void MidOrder(Node* root)
{
if (root == NULL)
{
return;
}
MidOrder(root->_left);
cout << root->_data <<" ";
MidOrder(root->_right);
}
//後序
void PostOrder()
{
PostOrder(_root);
}
//左->右->根
void PostOrder(Node* root)
{
if (root == NULL)
{
return;
}
PostOrder(root->_left);
PostOrder(root->_right);
cout << root->_data <<" ";
}
//測試一下
void TestBinaryTree()
{
int arr[] = { 1, 2, 3, '#', '#', 4, '#', '#', 5, '#', '#'};
BinaryTree<int> t(arr, sizeof(arr) / sizeof(int), '#');
cout << "前序:";
t.PrevOrder();
cout << endl;
cout << "中序:";
t.MidOrder();
cout << endl;
cout << "後序:";
t.PostOrder();
cout << endl;
}
還有一種層序遍歷
層序遍歷與前面三種實現的方法不太一樣,層序的遍歷要藉助一個佇列來實現
先將根節點入佇列,然後將其左右孩子入佇列,將隊頭Pop;
每次將隊頭的左右孩子入隊,Pop隊頭,就可以實現層序遍歷了
//層序
void LevelOrder()
{
LevelOrder(_root);
}
void LevelOrder(Node* root)
{
if (root == NULL)
{
return;
}
//藉助棧來實現
//棧裡放指向節點的指標
queue<Node*> q;
q.push(root);
while (!q.empty())
{
//列印隊頭,入棧對頭的左右孩子再隊尾,出隊頭
Node* front = q.front();//取隊頭
cout << front->_data << " ";
if (front->_left != NULL)
{
q.push(front->_left);
}
if (front->_right != NULL)
{
q.push(front->_right);
}
q.pop();
}
}
實現了四種遍歷的介面,接下來該實現計算節點的幾種介面了,還有計算深度的介面
- 計算節點總數
- 計算葉子節點數
- 計算第K層的節點數
- 樹的深度
計算節點的總數
可以有兩種思路
- 遍歷,計數
- 將其轉化為子問題,節點總數=左子樹+右子樹+根
實現一下:
//節點總數
//方法一:遍歷
//size_t Size()
//{
// size_t size = 0;//計數
// Size(_root,size);
// return size;
//}
//size_t Size(Node* root,size_t &size)
//{
// if (root == NULL)
// {
// return 0;
// }
//遍歷計數
// size++;
// Size(root->_left, size);
// Size(root->_right, size);
// return size;
//}
//方法二:轉化為子問題
size_t Size()
{
return Size(_root);
}
size_t Size(Node* root)
{
if (root == NULL)
{
return 0;
}
//左子樹+右子樹+根
return Size(root->_left) + Size(root->_right)+1;
}
計算葉子節點數
這個問題也是跟上一個問題一樣兩種思路,遍歷或者轉化為子問題
//方法一:遍歷
//size_t LeafSize()//葉子節點數
//{
// size_t size = 0;
// LeafSize(_root,size);
// return size;
//}
//size_t LeafSize(Node* root,size_t &size)
//{
// if (root == NULL)
// {
// return 0;
// }
// if (root->_left == NULL&&root->_right == NULL)
// {
// size++;
// }
// LeafSize(root->_left,size);
// LeafSize(root->_right,size);
// return size;
//}
//方法二:子問題
size_t LeafSize()//葉子節點數
{
return LeafSize(_root);
}
size_t LeafSize(Node* root)
{
if (root == NULL)
{
return 0;
}
//是葉子節點,返回1
if (root->_left == NULL&&root->_right == NULL)
{
return 1;
}
//不是葉子節點,再向下遞迴
return LeafSize(root->_left)+LeafSize(root->_right);
}
計算第K層的節點數
這裡我們雖然要求的是第k層的節點數,但是我們無法直接到達第k層,我們依然要從根節點開始逐層進入,直到k==1,我們就到達了我們要求的那層了,也就是我們遞迴結束的標誌
然後,再來將問題轉化為子問題,第k層的節點數=第k-1層的左子樹+右子樹節點
這裡要注意的是,引數不能用k的引用,因為如果用了引用,當
我們訪問完左子樹時,k已經被修改,再訪問右子樹時,會出錯
size_t GetKLevel(size_t k)//第k層節點數
{
return _GetKLevel(_root,k);
}
size_t _GetKLevel(Node* root,size_t k)//這裡注意不能用k的引用
{
assert(k > 0);
if (root == NULL)
{
return 0;
}
//k==1
if (k == 1)
{
return 1;
}
//k>1,第k層的節點數=第k-1層的左子樹+右子樹節點
if (k > 1)
{
return _GetKLevel(root->_left, k - 1) + _GetKLevel(root->_right, k - 1);
}
}
樹的深度
這個問題也同樣轉化為子問題解決
思路:樹的深度=左右子樹深度大的+1,每一個子樹都是如此,遞迴就行了
當遞迴到葉子時,返回1
//深度
size_t Depth()
{
return Depth(_root);
}
size_t Depth(Node* root)
{
if (root == NULL)
{
return 0;
}
//為最底層葉子時,返回1
if (root->_left == NULL&&root->_right == NULL)
{
return 1;
}
//否則,返回左右子樹大的+1
return Depth(root->_left) > Depth(root->_right) ?
Depth(root->_left) + 1 : Depth(root->_right) + 1;
}
測試一下這顆新樹: