二叉樹遞迴實現
二叉樹遞迴實現比較符合樹的特點,也較容易理解,程式碼也較為簡單。接下來進行圖文詳解。
C程式碼下載
C++程式碼下載
java程式碼下載
( 備用地址下載)
導航
1.建立二叉樹
2.前序遍歷二叉樹
3.中序遍歷二叉樹
4.後序遍歷二叉樹
5.層次遍歷二叉樹
6.計算二叉樹深度
7.計算二叉樹雙分支節點數
8.計算二叉樹單分支節點數
9.計算二叉樹葉子節點數
10.新增節點
11.查詢二叉樹中的節點
注:有一些重複的程式碼且多的就不重複貼出來了,需要的可以點上面的連結去下載。
一、建立二叉樹
按照前序遍歷來建立,給定一個串,其規則是空格代表空節點
例如給定串:ABC D EF G ;
建立步驟如下:
這個回溯就相當於出棧一樣,當條件不滿足了,就return,然後系統維護的棧就會執行出棧操作,這樣就相當於回溯了。由此可以推出非遞迴的實現方式。
1.C語言實現
/*
* function 建立二叉樹(前序建立)
* param PBTree* root(二級指標)
* param char* s 根據給定字串建立
* param int i 指示當前s的下標
* return 無
*/
void CreateBTree(PBTree* root, char * s, bool newTree)
{
static int i = -1; //s下標
if (newTree) i = -1; //因為i是靜態變數,所以只能手動修改它值,如果是新的二叉樹則重置它的值
++i;
//如果s為空,即二叉樹為空,若s=""或者到達末尾s[i]都等於'\0',這裡空節點用空格表示
if (!s || s[i] == '\0' || s[i] == ' ')
(*root) = NULL;
else
{
*root = (PBTree)malloc(sizeof(_binary_tree)); //建立節點
(*root)->data = s[i];
CreateBTree(&(*root)->left, s, false); //遞迴建立左子樹
CreateBTree(&(*root)->right, s, false); //遞迴建立右子樹
}
}
1).為什麼要用二級指標?
因為傳進去當引數的根節點指標會改變,所以需要一個指標的指標來一直的指向根節點
2).為什麼要用靜態變數i?
因為如果i不是靜態的,每次遞迴的時候都會重置i的值。
3).為什麼要加個bool標誌?
因為i是靜態的,只能手動去修改它的值,而只有當新建立一棵樹的時候才需要重置,遞迴的時候是不需要的。
2.C++實現
/*
* function 建立二叉樹
* param Node** 二級指標
* param string s
* return 無
*/
template<typename T>
void BTree<T>::CreateBtree(Node** root, string s)
{
++m_i;
//如果s為空,即二叉樹為空,若s=""或者到達末尾s[i]都等於'\0',這裡空節點用空格表示
if (s.empty() || s[m_i] == ' ')
(*root) = nullptr;
else
{
*root = new Node(s[m_i]); //建立節點
CreateBtree(&(*root)->left, s); //遞迴建立左子樹
CreateBtree(&(*root)->right, s); //遞迴建立右子樹
}
}
這裡不再需要靜態變數,因為已經將下標作為成員變量了,只當新建立樹的時候才重置它的值。
3.java實現
/*
* function 建立二叉樹
* param Node node
* param string s
* return 返回根節點
*/
private Node createBtree(Node node, String s) {
++iPos;
//如果s為空,即二叉樹為空,若s=""或者到達末尾s[i]都等於'\0',這裡空節點用空格表示
if (s.isEmpty() || s.charAt(iPos) == ' ')
return null;
else {
node = new Node(s.charAt(iPos));
node.setLeft(createBtree(node.getLeft(),s));
node.setRight(createBtree(node.getRight(),s));
return node;
}
}
一開始寫這個java遞迴建立二叉樹的時候,我就卡在了,如何記錄根節點的地址,因為java中沒有指標這種概念,所以二級指標這種方法就不可能了,網上有一篇部落格說是可以寫一個類模擬二級指標,但是這樣又太複雜,我不想這樣寫。然後轉啊轉的,就找到了一個寫得比較好的。這個思想和C/C++的差不多,就是最後會返回根節點回去,也許你會想,它為什麼會返回根節點?因為第一個進棧的就是根節點,所以最後一個出棧的就是根節點。
二、前序遍歷二叉樹
遍歷順序是:根節點 -> 左節點 -> 右節點
然後一個二叉樹又可以分為很多子樹,每一顆子樹都會有根、左、右節點,所以遞迴的思想就很好的體現在這裡了。
1.C程式碼
/*
* function 前序遍歷
* param PBTree root
* return 無
*/
void PreOrder(PBTree root)
{
if (root)
{
printf("%c", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
}
2.C++程式碼
/*
* function 前序遍歷
* param Node* root
* return 無
*/
template<typename T>
void BTree<T>::PreOrder(Node* root)
{
if (root)
{
cout << root->data << " ";
PreOrder(root->left);
PreOrder(root->right);
}
}
3.java程式碼
/*
* function 前序遍歷
* param Node root
* return 無
*/
private void preOrder(Node root) {
if (root != null) {
System.out.print(root.data + " ");
preOrder(root.left);
preOrder(root.right);
}
}
三、中序遍歷二叉樹
遍歷順序是:左節點 -> 根節點 -> 右節點
1.C程式碼
/*
* function 中序遍歷
* param PBTree root
* return 無
*/
void InOrder(PBTree root)
{
if (root)
{
InOrder(root->left);
printf("%c", root->data);
InOrder(root->right);
}
}
2.C++程式碼
/*
* function 中序遍歷
* param Node* root
* return 無
*/
template<typename T>
void BTree<T>::InOrder(Node* root)
{
if (root)
{
InOrder(root->left);
cout << root->data << " ";
InOrder(root->right);
}
}
3.java程式碼
/*
* function 中序遍歷
* param Node root
* return 無
*/
private void inOrder(Node root) {
if (root != null) {
inOrder(root.left);
System.out.print(root.data + " ");
inOrder(root.right);
}
}
四、後序遍歷二叉樹
遍歷順序:左節點 -> 右節點 -> 根節點
1.C程式碼
/*
* function 後序遍歷
* param PBTree root
* return 無
*/
void PostOrder(PBTree root)
{
if (root)
{
PostOrder(root->left);
PostOrder(root->right);
printf("%c", root->data);
}
}
2.C++程式碼
/*
* function 後序遍歷
* param Node* root
* return 無
*/
template<typename T>
void BTree<T>::PostOrder(Node* root)
{
if (root)
{
PostOrder(root->left);
PostOrder(root->right);
cout << root->data << " ";
}
}
3.java程式碼
/*
* function 後序遍歷
* param Node root
* return 無
*/
private void postOrder(Node root) {
if (root != null) {
postOrder(root.left);
postOrder(root.right);
System.out.print(root.data + " ");
}
}
五、層次遍歷二叉樹
從左到右遍歷,這個需要一個輔助函式,這個函式負責遍歷指定層數,然後主函式負責迴圈遍歷層數。這個缺點就是,每一次的遍歷都要從根節點開始,當層數很大的時候,遍歷就會非常消耗時間。
/*
* function 層次遍歷輔助函式
* param PBTree root
* param int level
* return 無
*/
void PrintNodeAtLevel(PBTree root, int level)
{
// 空樹或層級不合理
if (NULL == root || level < 1)
return;
if (1 == level) //相當於輸出根節點,因為每一個節點都可以左為子樹的根節點
{
printf("%c", root->data);
return;
}
// 左子樹的 level - 1 級
PrintNodeAtLevel(root->left, level - 1);
// 右子樹的 level - 1 級
PrintNodeAtLevel(root->right, level - 1);
}
六、計算二叉樹深度
先計算左子樹深度,再計算右子樹深度,然後返回較大的那個+1
/*
* function 計算二叉樹深度
* param PBTree root
* return 返回二叉樹深度
*/
int BTreeDepth(PBTree root)
{
if (!root)
return 0;
else
{
int lDepth = BTreeDepth(root->left); //遞迴計算左子樹的深度
int rDepth = BTreeDepth(root->right); //遞迴計算右子樹的深度
//返回較深的那個+1
if (lDepth >= rDepth)
return lDepth + 1;
else
return rDepth + 1;
}
}
七、計算二叉樹雙分支節點數
用前序遍歷的方法,一個個節點遍歷過去,如果該節點不為空,則判斷它是否有左孩子和有孩子,如果兩個都有,則count + 1
/*
* function 計算二叉樹雙分支節點數
* param PBTree root
* return 返回二叉樹雙分支節點數
*/
int GetN2(PBTree root, bool newTree)
{
static int count = 0;
if (newTree) count = 0;
if (root == NULL) //如果二叉樹為空,則返回0
return 0;
else
{
if (root->left && root->right) //當該節點有兩個分支的時候+1
++count;
GetN2(root->left, false); //遍歷左子樹
GetN2(root->right, false); //遍歷右子樹
}
return count;
}
八、計算二叉樹單分支節點數
和計算雙分支節點的方法一樣,只需要把判斷語句改一下即可
if ((root->left && !root->right) || (!root->left && root->right)) //當該節點僅且只有一個分支的時候+1
++count;
九、計算二叉樹葉子節點數
這個就簡單了,有一個公式: n0 = n2 + 1
/*
* function 計算二叉樹終端節點數
* param PBTree root
* return 二叉樹終端節點數
*/
int GetN0(PBTree root)
{
return GetN2(root, true) + 1; //計算公式n0 = n2 + 1;
}
十、新增節點
可以用前序、中序、後序、層次遍歷的方法來新增,前三個的缺點很明顯,最後新增後可能會退化成一個長長的單鏈表。所以這裡採用層次遍歷的方法新增,一層層掃描,遇到空節點就新增在它那裡。
/*
* function 新增節點的輔助函式
* param PBTree root
* param int level
* param char ch
* param bool* 標記是否新增成功
* return 無
*/
void LevelAdd(PBTree root, int level,char ch,bool* bAdd)
{
//用來標記新的節點是否已經新增,如果添加了就退出了,避免重複新增
if (*bAdd)return;
//如果該節點為空,則可以將ch賦值給該節點
if (!root->left || !root->right)
{
PBTree node = (PBTree)malloc(sizeof(_binary_tree)); //建立節點
node->data = ch;
node->left = NULL;
node->right = NULL;
if(!root->left)
root->left = node;
else
root->right = node;
*bAdd = true;
return;
}
//層級不合理
if (level < 1)
return;
//遞迴結束條件
if (1 == level)
return;
// 左子樹的 level - 1 級
LevelAdd(root->left, level - 1, ch, bAdd);
// 右子樹的 level - 1 級
LevelAdd(root->right, level - 1, ch, bAdd);
}
/*
* function 新增節點值(新增的位置是不確定的)
* param PBTree root
* param char ch
* return 無
*/
void AddValue(PBTree root,char ch)
{
//採用層次遍歷的辦法,一層層掃描,若有空的地方,則新增到該地方
if (NULL == root) //如果此時二叉樹為空,則建立根節點
{
root = (PBTree)malloc(sizeof(_binary_tree)); //建立節點
root->data = ch;
root->left = NULL;
root->right = NULL;
return;
}
int depth = BTreeDepth(root);
bool bAdd = false; //標記是否新增成功,避免重複新增
for (int i = 1; i <= depth; i++)
{
if (bAdd) //如果已經新增成功,則退出
break;
LevelAdd(root, i, ch, &bAdd);
}
}
十一、查詢二叉樹中的節點
用前序遍歷的方法查詢
/*
* function 查詢該值
* param PBTree root
* param char ch
* param bool 標誌是否是第一次查詢,如果是第一次要將標誌重置,因為靜態變數要手動重置它的值
* return 若存在則返回true,否則返回false
*/
bool Search(PBTree root, char ch,bool first)
{
static bool bFind = false;
if (first) bFind = false;
if (bFind)return true; //如果已經找到了就不需要繼續查找了
//利用前序遍歷來查詢
if (root)
{
if (root->data == ch)
bFind = true;
Search(root->left, ch, false);
Search(root->right, ch, false);
}
}