二叉树的相关概念和结构在上一章节已经有详细介绍,传送门:二叉树:数据结构中的灵魂
BTDataType
表示二叉树的节点存储元素的类型,BTNode
表示二叉树的节点,left
和 right
分别表示节点的左右孩子节点,data
表示节点存储的元素。
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
// 申请节点
BTNode* BuyTreeNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
assert(node);
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
//建树
BTNode* CreateTree()
{
BTNode* node1 = BuyTreeNode(1);
BTNode* node2 = BuyTreeNode(2);
BTNode* node3 = BuyTreeNode(3);
BTNode* node4 = BuyTreeNode(4);
BTNode* node5 = BuyTreeNode(5);
BTNode* node6 = BuyTreeNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式在本文后重点讲解。
在看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
下面请看二叉树三种遍历方法的代码实现:
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
printf("%d ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
BinaryTreePrevOrder(root->left);
printf("%d ", root->data);
BinaryTreePrevOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
printf("%d ", root->data);
}
下面主要分析前序递归遍历,中序与后序图解类似。
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
二叉树的层次遍历通常是通过队列来实现的(这是因为我们需要利用队列先进先出的特性来保存之前被访问过的节点的孩子节点),这是一种广度优先搜索(BFS
)的策略。下面是一个基本的实现思路:
进入一个循环,条件是
队列不为空
。在循环中,我们首先取出队列的第一个节点,并访问它(比如打印节点的值)。这个过程会一直持续到队列为空,也就是所有的节点都已经被访问过了。
辅助队列代码:
// 链式结构:表示队列
typedef struct BinaryTreeNode* QDataType; //元素类型,此处类型是二叉树节点的指针
//队列的节点
typedef struct QListNode
{
QDataType data;
struct QListNode* next;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* front; //对头
QNode* rear; //队尾
int size; //队列元素个数
}Queue;
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);
q->front = q->rear = NULL;
q->size = 0;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->data = data;
newnode->next = NULL;
if (q->front == NULL)
{
q->front = q->rear = newnode;
}
else
{
q->rear->next = newnode;
q->rear = newnode;
}
q->size++;//队列元素加一
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
//队列不为空
assert(q->front);
QNode* cur = q->front;
q->front = q->front->next;
//队列只有一个元素的情况,要考虑队尾的指针,防止野指针
if (q->front == NULL)
q->rear = NULL;
free(cur);
q->size--;//队列元素减一
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
//队列不为空
assert(q->front);
return q->front->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
return q->front == NULL;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->front;
while (cur)//当cur为空时结束
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->front = q->rear = NULL;
q->size = 0;
}
二叉树层次遍历代码:
// 非递归遍历二叉树
// 层序遍历(利用队列“先进先出”-->出去一个节点就立即带入此节点的左右节点进队)
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
if (front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
QueueDestroy(&q);
}
// 一层一层访问节点
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
int levelSize = 1; //控制层数,第一层数据个数为1
while (!QueueEmpty(&q))
{
// 一层一层出数据
while (levelSize--)
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
if (front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
printf("\n");
levelSize = QueueSize(&q);
}
QueueDestroy(&q);
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL) // 空树
return 0;
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
// 二叉树的高度
int BinaryTreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int leftHeight = BinaryTreeHeight(root->left); // 左子树的高度
int rightHeight = BinaryTreeHeight(root->right); //右子树的高度
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL) // 空树
return 0;
if (k == 1) // 第一层
return 1;
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1 = BinaryTreeFind(root->left, x);
if (ret1)
{
return ret1;
}
BTNode* ret2 = BinaryTreeFind(root->right, x);
if (ret2)
{
return ret2;
}
return NULL;
}
例: 通过前序遍历的数组 “ABD##E#H##CF##G##” 构建二叉树,“#”表示的是空格,空格字符代表空树。
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi) //这里pi的类型为int*是为了递归时确保值的完整性
{
assert(*pi >= 0 && *pi < n);
if (a[(*pi)] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc error");
return;
}
root->data = a[(*pi)++];
root->left = BinaryTreeCreate(a, n, pi);
root->right = BinaryTreeCreate(a, n, pi);
return root;
}
该函数接受三个参数:a
是指向前序遍历数组的指针,n
是数组的长度,pi
是一个指向整数的指针,用于记录当前处理的元素索引。
assert(*pi >= 0 && *pi < n)
确保索引 *pi
在合法范围内。然后判断当前元素是否为特殊字符 '#'
,如果是,则表示当前节点为空,将索引 *pi 加一后返回 NULL
。root
,并为其分配内存空间。如果内存分配失败,则打印错误信息并返回。root
节点的数据域 root->data
,并将索引 *pi
加一。然后递归调用 BinaryTreeCreate
函数来构建 左子树
和 右子树
,分别赋值给 root->left
和 root->right
。由于在遍历二叉树时,前序和中序都需要保存根节点,而后续遍历不用,故使用后续遍历递归销毁二叉树最简单。
// 二叉树销毁(利用后序遍历销毁,前中序遍历都需要保存根节点)
void BinaryTreeDestory(BTNode** root)
{
assert(root);
if (*root == NULL)
return;
BinaryTreeDestory(&(*root)->left);
BinaryTreeDestory(&(*root)->right);
free(*root);
*root = NULL;
}
思路:利用队列来进行层序遍历,并在遍历过程中检查是否存在未被访问的节点。
q
,并将根节点 root
加入队列,如果根节点存在的话。NULL
节点,就跳出循环。NULL
,则说明存在非空节点未被访问,即该二叉树不是完全二叉树。函数返回 false
。true
之前,销毁队列以释放内存。// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
int levelSize = 1; //控制层数,第一层数据个数为1
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
break;
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}