目录
1.二叉树链式结构的实现
1.1前置说明
在学习二叉树的基本操作前,需先要创建一棵二叉树此处手动快速创建一棵简单的二叉树,二叉树真正的创建方式在文末给出。
typedef struct BTNode
{
struct BTNode* left;
struct BTNode* right;
DataType data;
}BTNode;
BTNode* BinaryTreeCreate()
{
BTNode* node1 = BuyBTNode(1);
BTNode* node2 = BuyBTNode(2);
BTNode* node3 = BuyBTNode(3);
BTNode* node4 = BuyBTNode(4);
BTNode* node5 = BuyBTNode(5);
BTNode* node6 = BuyBTNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式文末说明
1.2二叉树的遍历
1.2.1前序、中序以及后序遍历
所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历 是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。(根节点——>根的左子树——>根的右子树)
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。(根的左子树——>根节点——>根的右子树)
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。(根的左子树——>根的右子树——>根节点)
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为 根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
二叉树的遍历等操作等都要借助二叉树的概念:
1.空树
2.非空:根节点,根节点的左子树、根节点的右子树组成的。
//前序遍历 根节点-->根的左子树-->根的右子树
void PreOrder(BTNode* root)
{
if (root == NULL)
return;
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
//中序遍历 根的左子树-->根节点-->根的右子树
void InOrder(BTNode* root)
{
if (root == NULL)
return;
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
//后序遍历 根的左子树-->根的右子树-->根节点
void PostOrder(BTNode* root)
{
if (root == NULL)
return;
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
前序遍历递归图解
遍历结果如图:
1.2.2层序遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在 层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
//层序遍历
void BinaryLevelOrder(BTNode* root)
{
Queue q;
if (NULL == root)
return;
QueueInit(&q);
//将根节点入队列,当队列不空时,循环下列操作
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
struct BTNode* cur = QueueFront(&q);
//遍历当前节点
printf("%d ", cur->data);
//如果当前节点有左孩子,则入队列;有右孩子入队列
if (cur->left)
QueuePush(&q, cur->left);
if (cur->right)
QueuePush(&q, cur->right);
//遍历当前节点后删除改节点
QueuePop(&q);
}
QueueDestroy(&q);
printf("\n");
}
1.3二叉树节点个数及高度等
以以下这个二叉树为例:
以下操作都会利用到二叉树的概念,所以牢记二叉树的概念十分重要。
//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (NULL == root)
return 0;
return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (NULL == root)
return 0;
//如果当前节点没有左右孩子则为叶子节点
if (NULL == root->left&&NULL == root->right)
return 1;
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
//二叉树的高度
int BinaryTreeHeight(BTNode* root)
{
if (NULL == root)
return 0;
//先求root子树的高度
int leftHeight = BinaryTreeHeight(root->left);
int rightHeight = BinaryTreeHeight(root->left);
//root树的高度为较高的子树的高度+1
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//二叉树第k层的节点个数
int BinaryTreeLevelKSize(BTNode* root,int k)
{
if (NULL == root || k <= 0)
return 0;
//第一层节点数为1
if (1 == k)
return 1;
//第k层节点数不知道,递归在root子树中求k-1层的节点数
return BinaryTreeLeafSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
//查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, DataType x)
{
BTNode* ret = NULL;
if (NULL == root)
return NULL;
if (x == root->data)
return root;
//如果根的值域不是要找的x,递归到根的左右子树中寻找
(ret = BinaryTreeFind(root->left, x)) || (ret = BinaryTreeFind(root->right, x));
return ret;
}
1.4二叉树的创建和销毁
二叉树创建时需要在序列中体现节点孩子的实际情况,用'#’表示某节点的子树不存在,根据二叉树的定义,递归创建二叉树
int array[] = { 1, 2, 3, '#', '#', '#', 4, 5, '#', '#', 6 };
//二叉树的创建
// array保存的是:用户要创建二叉树时节点中值域的集合
// size: array中有效元素的个数
// pindex: 指向外部用来表示array下标的index
// invalid: 表示array数组中用来区分节点是否存在子树的标记
BTNode* _BinaryTreeCreate(DataType array[], int size, int* pindex, char invalid)
{
BTNode* root = NULL;
if (*pindex < size&&array[*pindex] != invalid)
{
//创建根节点
root = BuyBTNode(array[*pindex]);
//递归创建根的左子树
++(*pindex);
root->left = _BinaryTreeCreate(array, size, pindex, invalid);
///递归创建根的左子树
++(*pindex);
root->right = _BinaryTreeCreate(array, size, pindex, invalid);
}
return root;
}
BTNode* BinaryTreeCreate(DataType array[], int size, char invalid)
{
int index = 0;
return _BinaryTreeCreate(array, size, &index, invalid);
}
//二叉树的销毁
void BinaryTreeDestroy(BTNode** proot)
{
assert(proot);
if (NULL == *proot)
return;
BinaryTreeDestroy(&(*proot)->left);//递归销毁根的左子树
BinaryTreeDestroy(&(*proot)->right);//递归销毁根的右子树
free(*proot); //释放根节点空间
*proot == NULL;
}
void TestBinaryTree()
{
int array[] = { 1, 2, 3, '#', '#', '#', 4, 5, '#', '#', 6 };
BTNode* root = BinaryTreeCreate(array, sizeof(array) / sizeof(array[0]), '#');
}
1.5完全二叉树的判断
完全二叉树:层从上往下,每层节点从左至右依次摆出来
既然是按照层序的方式依次将节点摆出来,因此在检测是否为完全二叉树的时候,按照层序遍历的方式来检测。
完全二叉树所有节点的情况可以分为以下两种:
找到第一个孩子不全的节点非常关键,按照层序遍历的方式找第一个不饱和节点(指该节点要么没有孩子,要么只有一个左孩子),第一个不饱和节点之后的节点不能有孩子。
以该二叉树为例:
//判断完全二叉树
//利用二叉树的层序遍历找第一个不饱和节点的位置
int BinaryTreeComplete(BTNode* root)
{
Queue q;
int flag = 0;//标记第一个不饱和节点的位置
int CompleteTree = 1;
//空树也是完全二叉树
if (NULL == root)
return 1;
QueueInit(&q);
QueuePush(&q, root);将根节点入队列
while (!QueueEmpty(&q))
{
BTNode* cur = QueueFront(&q);
QueuePop(&q);遍历后从队列中删除该节点
if (flag)//若找到第一个未饱和的节点
{
if (cur->left || cur->right)//第一个未饱和节点的后续节点不能有孩子,
{
CompleteTree = 0;
break;
}
}
else
{
//cur为饱和的节点,左右孩子都存在
if (cur->left&&cur->right)
{
QueuePush(&q, cur->left);
QueuePush(&q, cur->right);
}
else if (cur->left)//只有左孩子,没有右孩子,第一个不饱和的节点找到
{
QueuePush(&q, cur->left);
flag = 1;
}
else if (cur->right)//只有右孩子,没有左孩子
{
QueuePush(&q, cur->right);
CompleteTree = 0;
break;
}
else
{
//左右孩子都不存在,则该节点为第一个不饱和的节点
flag = 1;
}
}
}
QueueDestroy(&q);
return CompleteTree;
}