==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍
二叉树递归实现比较符合树的特点,也较容易理解,代码也较为简单。接下来进行图文详解。
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);
}
}