二叉树相关概念
满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子节点都在同一层上!
完全二叉树
如果一棵具有N个结点的二叉树的结构与满二叉树的前N个结点的结构相同,称为完全二叉树!
叶子节点
度为0的结点称为叶结点,叶节点也称为终端节点,从图上看就是最下面的一层节点!
二叉树节点与接口
定义的二叉树的节点和本次需要实现的接口如下:<BinaryTree.h>
#ifndef __BINARY_TREE_H__
#define __BINARY_TREE_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "queue.h"
#include "Stack.h"
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
BTDataType _data;
}BTNode, *pBTNode;
// 创建二叉树
pBTNode BinaryTreeCreate(BTDataType* a, int n, int* pi);
void BinaryTreePrevOrder(pBTNode root);//前序遍历
void BinaryTreeInOrder(pBTNode root);//中序遍历
void BinaryTreePostOrder(pBTNode root);//后序遍历
//返回节点个数
size_t BTreeSize(pBTNode root);
//返回叶子节点的个数
size_t BTreeLeafSize(pBTNode root);
//返回第K层节点个数
size_t BTreeKLevelSize(pBTNode root, size_t k);
//返回树的深度
size_t BTreeDepth(pBTNode root);
//返回值为x的节点地址
pBTNode BTreeFind(pBTNode root, BTDataType x);
//层序遍历,需要使用队列
void BTreeLevelOrder(pBTNode root);
// 判断完全二叉树
int IsCompleteBTree(pBTNode root);
int IsCompleteBTree1(pBTNode root);
// 非递归遍历
void BTreePrevOrderNonR(pBTNode root);//前序遍历
void BTreeInOrderNonR(pBTNode root);//中序遍历
void BTreePostOrderNonR(pBTNode root);//后序遍历方式一
void BTreePostOrderNonR_02(pBTNode root);//后序遍历方式二
//销毁函数
void BinaryTreeDestory(pBTNode* proot);
//测试函数
void TestBinaryTree();
#endif // !__BINARY_TREE_H__
二叉树的创建
递归创建二叉树
//获取新节点
static pBTNode BuyBTNode(BTDataType x)
{
pBTNode newNode = (pBTNode)malloc(sizeof(BTNode));
assert(newNode != NULL);
newNode->_left = NULL;
newNode->_right = NULL;
newNode->_data = x;
return newNode;
}
// 创建二叉树
pBTNode BinaryTreeCreate(BTDataType* a, int n, int *pi)
{
assert(a != NULL);
if ('#' != a[*pi] && (*pi)<n)//这里还要要求数组的下标是合法的
{
pBTNode root = BuyBTNode(a[*pi]);
(*pi)++;
root->_left = BinaryTreeCreate(a, n, pi);
(*pi)++;
root->_right = BinaryTreeCreate(a, n, pi);
return root;
}
else
return NULL;
}
char arr[] = { 'A','B','#','C','#',
'#','D','E','#','F',
'#','G','#','#','H','#','#' };
int len = sizeof(arr) / sizeof(arr[0]);
pBTNode root;
int i = 0;
root = BinaryTreeCreate(arr, len, &i);
这样会创建出这样一个二叉树:
二叉树的遍历
三种遍历方式: 前序遍历、中序遍历、后序遍历
前序遍历PrevOrder
的规则
- 根节点
- 遍历左子树
- 遍历右子树
所以结果应该是:A B C D E F G H
中序遍历 InOrder
的规则
- 遍历左子树
- 根节点
- 遍历右子树
所以结果应该是:B C A E F G D H
后序遍历 PostOrder
的规则
- 遍历左子树
- 遍历右子树
- 根节点
所以结果应该是:C B G F E H D A
递归遍历二叉树
//前序遍历
void BinaryTreePrevOrder(pBTNode root)
{
if (root == NULL)
return;
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
//中序遍历
void BinaryTreeInOrder(pBTNode root)
{
if (root->_left != NULL)
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
if (root->_right != NULL)
BinaryTreeInOrder(root->_right);
}
//后序遍历
void BinaryTreePostOrder(pBTNode root)
{
if (root->_left != NULL)
BinaryTreePostOrder(root->_left);
if (root->_right != NULL)
BinaryTreePostOrder(root->_right);
printf("%c ",root->_data);
}
可以看出使用递归的方式是非常简单的,但是递归在树太深的时候也是无法进行递归遍历的,递归次数一旦非常多的时候容易发生栈溢出 (Stack Overflow),每次调用函数就会创建栈桢,一般情况下栈空间只有几兆,一旦递归次数过多就会溢出!所以应该使用非递归的方式对二叉树进行遍历!
非递归遍历二叉树
使用非递归遍历二叉树需要使用 栈
这种数据结构来辅助完成!
接下来看看非递归版本的前序、中序、后序遍历:
// 前序遍历-非递归遍历
void BTreePrevOrderNonR(pBTNode root)
{
pBTNode cur = root;
pBTNode top = NULL;
Stack s;
StackInit(&s);
while (cur || StackEmpty(&s) != 0)
{
//访问左路节点,左路节点进栈
while (cur)
{
printf("%c ", cur->_data);
StackPush(&s, cur);
cur = cur->_left;
}
//栈里面出来节点,表示左树已经访问过
top = StackTop(&s);
StackPop(&s);
//子问题访问右树
cur = top->_right;
}
printf("\n");
StackDestory(&s);
}
//中序遍历-非递归遍历
void BTreeInOrderNonR(pBTNode root)
{
pBTNode cur = root;
pBTNode top = NULL;
Stack s;
StackInit(&s);
while (cur || StackEmpty(&s) != 0)
{
//访问左路节点,左路节点进栈
while (cur)
{
StackPush(&s, cur);
cur = cur->_left;
}
//栈里面出来节点,表示左树已经访问过
top = StackTop(&s);
printf("%c ", top->_data);
StackPop(&s);
//子问题访问右树
cur = top->_right;
}
printf("\n");
StackDestory(&s);
}
后序遍历比较特殊,需要单独拿出来说!
在前序遍历、中序遍历中根节点都不是最后才会被访问的元素,然后在后序遍历中,根节点是最后访问的元素,所以应该定义一个指针 prev
来防止Top出来的的右指针在不为空的情况下重复进入!
//后序遍历-非递归遍历
void BTreePostOrderNonR(pBTNode root)
{
pBTNode cur = root;
pBTNode top = NULL;
pBTNode prev = NULL;
Stack s;
StackInit(&s);
while (cur || StackEmpty(&s) != 0)
{
while (cur != NULL)
{
StackPush(&s, cur);
cur = cur->_left;
}
top = StackTop(&s);
if (top->_right == NULL || top->_right == prev)
{
printf("%c ", top->_data);
prev = top;
StackPop(&s);
}
else
{
cur = top->_right;
}
}
}
接着看看另外一种遍历方式,要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
void BTreePostOrderNonR_02(pBTNode root)
{
pBTNode cur = root;
pBTNode top = NULL;
pBTNode prev = NULL;
Stack s;
StackInit(&s);
StackPush(&s,root);
while (StackEmpty(&s) != 0)
{
cur = StackTop(&s);
if ((cur->_left == NULL && cur->_right == NULL) ||
(prev != NULL && (prev == cur->_left || prev == cur->_right)))
{
printf("%c ", cur->_data);
prev = cur;
cur = StackTop(&s);
StackPop(&s);
}
else
{
if (cur->_right != NULL)
StackPush(&s, cur->_right);
if (cur->_left != NULL)
StackPush(&s, cur->_left);
}
}
printf("\n");
StackDestory(&s);
}
这样非递归遍历二叉树的方式从表面上看没有用到递归,但是却用到了递归的思想,利用栈这种数据结构,将后遍历的节点先压栈,最后出栈便可以的到我们想要的结果,又避开了直接使用递归的明显缺点,这样利用栈作为辅助数据结构的方法便是将递归问题转化为非递归问题的根本所在!
层序遍历二叉树、满二叉树的判断
层序遍历就是一层一层的遍历,这里需要用到队列这种数据结构!
层序遍历的原理也是非常简单,先将根节点入栈,在根节点出栈的时候将根节点的左节点和右节点入栈,然后在左节点和右节点出栈的时候就分别将它们的左节点和右节点入栈,这样就把每一层的节点入队列,出队列的时候自然就是层序遍历了,代码如下:
//层序遍历,需要使用队列
void BTreeLevelOrder(pBTNode root)
{
Queue qu;
QueueInit(&qu);
if (root != NULL)
QueuePush(&qu, root);
while (QueueEmpty(&qu) != 0)
{
DataType ret = QueueFront(&qu);
printf("%c ", ret->_data);
QueuePop(&qu);
if (ret->_left != NULL)
QueuePush(&qu, ret->_left);
if (ret->_right != NULL)
QueuePush(&qu, ret->_right);
}
printf("\n");
QueueDestory(&qu);
}
判断是否是满二叉树其实就是考察层序遍历的应用,在遍历的过程中只要遇到一个是NULL,那么后面都应该为空才能算是完全二叉树,否则直接返回0,代码如下:
// 判断完全二叉树 ,是就返回1,不是返回0
int IsCompleteBTree(pBTNode root)
{
Queue qu;
QueueInit(&qu);
if (root != NULL)
QueuePush(&qu, root);
while (QueueEmpty(&qu) != 0)
{
DataType ret = QueueFront(&qu);
QueuePop(&qu);
if (ret != NULL)
{
QueuePush(&qu, ret->_left);
QueuePush(&qu, ret->_right);
}
else
break;
}
while (QueueEmpty(&qu) != 0)
{
DataType ret = QueueFront(&qu);
if (ret != NULL)
return 0;
else
QueuePop(&qu);
}
//QueueDestory(&qu);
return 1;
}
求二叉树的镜像
如图所示,这就是互为镜像的两个二叉树,接下来使用递归和非递归方式求出二叉树的镜像:
//求镜像的递归方式
void GetMirror_R(pBTNode root)
{
pBTNode tmp = NULL;
if (root == NULL)
return;
GetMirror(root->_left);
GetMirror(root->_right);
tmp = root->_left;
root->_left = root->_right;
root->_right = tmp;
}
//非递归方式
void GetMirror(pBTNode root)
{
pBTNode tmp = NULL;
Queue qu;
QueueInit(&qu);
if (root != NULL)
QueuePush(&qu, root);
while (QueueEmpty(&qu) != 0)
{
DataType ret = QueueFront(&qu);
QueuePop(&qu);
if (ret->_left != NULL)
QueuePush(&qu, ret->_left);
if (ret->_right != NULL)
QueuePush(&qu, ret->_right);
tmp = ret->_left;
ret->_left = ret->_right;
ret->_right = tmp;
}
}
非递归方式其实就是运用层序遍历的道理,比较容易实现,两次翻转得到源镜像:
关于栈和队列的接口详见:https://github.com/zouchanglin/Stack-Queue
二叉树的其他问题
//返回节点个数
size_t BTreeSize(pBTNode root)
{
if (root == NULL)
return 0;
return BTreeSize(root->_left) + BTreeSize(root->_right) +1;
}
//返回叶子节点的个数
size_t BTreeLeafSize(pBTNode root)
{
if (root == NULL)
return 0;
if (root->_left == NULL && root->_right == NULL)
return 1;
return BTreeLeafSize(root->_left) + BTreeLeafSize(root->_right);
}
//返回第K层节点个数
size_t BTreeKLevelSize(pBTNode root, size_t k)
{
if (root == NULL)
return 0;
if (k == 1)
return k;
return BTreeKLevelSize(root->_left, k - 1) + BTreeKLevelSize(root->_right, k - 1);
}
//返回树的深度
size_t BTreeDepth(pBTNode root)
{
int leftDeph = 0;
int rightDeph = 0;
if (root == NULL)
return 0;
leftDeph = BTreeDepth(root->_left)+1;
rightDeph = BTreeDepth(root->_right)+1;
return leftDeph > rightDeph ? leftDeph : rightDeph;
}
//返回值为x的节点地址
pBTNode BTreeFind(pBTNode root, BTDataType x)
{
pBTNode ret = NULL;
if (root == NULL||root->_data == x)
return root;
ret = BTreeFind(root->_left, x);
if (ret != NULL)
return ret;
return BTreeFind(root->_right, x);
}
//销毁函数
void BinaryTreeDestory(pBTNode* proot)
{
if (proot == NULL || *proot == NULL)
return;
if ((*proot)->_left != NULL)
BinaryTreeDestory(&((*proot)->_left));
if ((*proot)->_right != NULL)
BinaryTreeDestory(&((*proot)->_right));
free(*proot);
*proot = NULL;
}
二叉树的性质
Github地址
https://github.com/zouchanglin/BinaryTree
如有不对之处,欢迎指正:[email protected]