多叉树的表示方法是左孩子右兄弟。二叉树每个节点的度至多为2,所以用左孩子,右孩子。
二叉树
有二叉链和三叉链。
分类
二叉链的数据结构
struct BTNode
{
int data;
struct BTNode* leftchild;
struct BTNode* rightchild;
};
三叉链的数据结构
struct BTNode
{
int data;
struct BTNode* leftchild;
struct BTNode* parent;
struct BTNode* rightchild;
};
任何一颗二叉树由三部分组成:根节点,左子树,右子树。
要用到分治算法:将大问题分成类似的子问题,子问题再分,直到子问题不可再分割。对于二叉树,就是分到空树不可再分了。
四种遍历方法
深度优先遍历:前中后序
为了不遗漏(也不能重复)处理二叉树的每一个节点,总得按照某种顺序,前辈们发明了处理二叉树节点的顺序:按层遍历、前序遍历、中序遍历、后续遍历。二叉树的前序、中序、后序遍历的作用都是不遗漏且不重复地处理二叉树的每一个结点。
A
B C
D E NULL NULL
F G NULL NULL
NULL NULL H I
NULL NULL NULL NULL
前序(先根遍历):根左右。先访问根,再访问左子树,再访问右子树。
顺序是A B D F NULL NULL G H NULL NULL I NULL NULL E NULL NULL C NULL NULL
中序:左根右。NULL F NULL D NULL H NULL G NULL I NULL B NULL E NULL A NULL C NULL
后序:左右根。NULL NULL F NULL NULL H NULL NULL I G D NULL NULL E B NULL NULL C A
为什么要用前中后序原因:前序(先根后子)用于搜索目标状态(先比较再扩展,你发现当前结点就是要搜索的目标就不需要继续扩展了)。或者子依赖父的对象创建(树是一组元数据,你基于这些元数据创建对象,子对象依赖于父对象所以父对象需要先创建),比如说根据元数据创建网页元素插入到浏览器DOM树。
中序(左根右)用于遍历排序二叉树。
后序(先子后根)用于父依赖子(子是父的组成要素)的对象创建。或者自低向上收集信息,比如不剪枝的博弈树收集极大极小值的过程。
深度优先遍历是前中后序,前序是先访问再往下走,中序是左路退回来的时候再访问,后序是左右两路都退回来再访问。
//前序 根左右
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);
//左子树
PrevOrder(root->leftchild);
//右子树
PrevOrder(root->rightchild);
}
//中序 左根右
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
//左子树
InOrder(root->leftchild);
printf("%d ", root->data);
//右子树
InOrder(root->rightchild);
}
//后序 左右根
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
//左子树
PostOrder(root->leftchild);
//右子树
PostOrder(root->rightchild);
printf("%d ", root->data);
}
广度优先遍历:层序遍历
用的是队列,先进先出的原则。核心思路是上一层带下一层
计算
节点个数
//方法1 外部传节点个数的地址,函数中直接改
void TreeSize1(BTNode* root, int* size)
{
if (root == NULL)
{
return 0;
}
//该节点不为空就+1
(*size)++;
//继续遍历左子树
TreeSize1(root->leftchild, size);
//右子树
TreeSize1(root->rightchild, size);
}
//方法2
int TreeSize2(BTNode* root)
{
if (root == NULL)
{
return 0;
}
//根不为空就可以继续往下遍历
return 1 + TreeSize2(root->leftchild) + TreeSize2(root->rightchild);
}
叶子节点个数
int TreeLeafSize(BTNode* root)
{
//当前节点为空,就无法访问它的左右子树
if (root == NULL)
{
return 0;
}
if (root->leftchild == NULL && root->rightchild == 0)
{
return 1;
}
return TreeLeafSize(root->leftchild) + TreeLeafSize(root->rightchild);
}
树的高度
//要用后序思想来做
int TreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftHeight = TreeHeight(root->leftchild);
int rightHeight = TreeHeight(root->rightchild);
//有了左子树的高度和右子树的高度,等于1 + max(左子树高度,右子树高度)
return leftHeight > rightHeight ?
1 + leftHeight : 1 + rightHeight;
}
第k层的节点个数
root的第k层,转换成求root左右子树的第k-1层
int TreeKLevel(BTNode* root, int K)
{
assert(K > 0);
if (root == NULL)
{
return 0;
}
//到第K层,遇到1个非空节点,就+1
if (K == 1)
{
return 1;
}
return TreeKLevel(root->leftchild, K - 1) + TreeKLevel(root->rightchild, K - 1);
}
返回值为x的节点
这种方法还可以去修改指定值
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
//如果该节点为空,那就没必要执行后面的语句,直接返回
if (root == NULL)
{
return NULL;
}
//如果找到了,返回该节点
if (root->data == x)
{
return root;
}
//没找到就接着找
//先在左子树找
BTNode* left = BinaryTreeFind(root->leftchild, x);
if (left != NULL)
{
return left;
}
//左子树没找到,就去右子树找
BTNode* right = BinaryTreeFind(root->rightchild, x);
if (right != NULL)
{
return right;
}
//左子树和右子树都找不到,要返回NULL
return NULL;
}