目录
1.头文件
2.源代码
3.源代码详解
1.二叉树的创建(前序遍历建立一棵二叉树)(重点)
2.二叉树的销毁
3.二叉树的节点个数
4.二叉树的叶子节点个数
5.二叉树的K层的节点个数
6.二叉树数值的查找
7.前序遍历 和 中序遍历 和 后序遍历(重点)
8.二叉树的层序遍历(重点)
9.判断二叉树是否是完全二叉树
前面一章是二叉树的概念详解
C语言 二叉树详解(自我理解版)!!!二叉树的实现-CSDN博客
这是一棵二叉树的图示,接下来的代码可以根据这个图例来进行想象
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);
根据上面的图示,可以看出二叉树的除了根节点以外,每个结点都有一个父亲节点和左右孩子节点,但对于这样用链表表示的二叉树,他是没有办法通过子节点找到父节点的。
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* NodeCreate(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
newnode->_data = x;
newnode->_left = NULL;
newnode->_right = NULL;
return newnode;
}
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if (*pi >= n)
{
return NULL;
}
if (a[*pi] == '#' || a[*pi] == '\0')
{
return NULL;
}
BTNode* newnode = NodeCreate(a[*pi]);
(*pi)++;
newnode->_left = BinaryTreeCreate(a, n, pi);
(*pi)++;
newnode->_right = BinaryTreeCreate(a, n, pi);
return newnode;
}
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
if (*root == NULL)
{
return;
}
BinaryTreeDestory(&((*root)->_left));
BinaryTreeDestory(&((*root)->_right));
free(*root);
*root = NULL;
free(root);
root = NULL;
}
// 二叉树节点个数
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);
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
if (root == NULL)
{
return 0;
}
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* left = BinaryTreeFind(root->_left, x);
if (left != NULL)
{
return left;
}
else
{
return BinaryTreeFind(root->_right, x);
}
}
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ", root->_data);
}
// 层序遍历
void BinaryTreeLevelOrder(struct BinaryTreeNode* root)
{
Qe q;
QueueInit(&q);
if (root == NULL)
{
return;
}
Queuepush(&q, root);
int Ksize = 1;
while (!QueueEmpty(&q))
{
while (Ksize)
{
struct BinaryTreeNode* data = QueueFront(&q);
QueuePop(&q);
if (data->_left)
{
Queuepush(&q, data->_left);
}
if (data->_right)
{
Queuepush(&q, data->_right);
}
printf("%c ", data->_data);
Ksize--;
}
printf("\n");
Ksize = QueueSize(&q);
}
}
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(struct BinaryTreeNode* root)
{
Qe q;
QueueInit(&q);
if (root == NULL)
{
return;
}
Queuepush(&q, root);
while (!QueueEmpty(&q))
{
struct BinaryTreeNode* data = QueueFront(&q);
QueuePop(&q);
if (data == NULL)
{
break;
}
Queuepush(&q,data->_left);
Queuepush(&q,data->_right);
}
while (!QueueEmpty(&q))
{
QueuePop(&q);
if (QueueFront(&q) != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
BTNode* NodeCreate(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
newnode->_data = x;
newnode->_left = NULL;
newnode->_right = NULL;
return newnode;
}
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if (*pi >= n)
{
return NULL;
}
if (a[*pi] == '#' || a[*pi] == '\0')
{
return NULL;
}
BTNode* newnode = NodeCreate(a[*pi]);
(*pi)++;
newnode->_left = BinaryTreeCreate(a, n, pi);
(*pi)++;
newnode->_right = BinaryTreeCreate(a, n, pi);
return newnode;
}
这里的第一个接口是 创立一个节点。
第二个接口(函数)是二叉树的创建。因为这里根据前序遍历进行创建。
// 通过前序遍历的数组构建二叉树(前序遍历在后面)可以先去看前序遍历。
这里给的三个变量,第一个是一个字符数组存储的是你要生成的二叉树的字符的前序遍历。
第二个变量是 字符数组的 元素个数
第三个变量是 用来记录你现在走到字符数组的第几位数字。当* pi >= n 说明 递归已经走到超出数组。
上面这个二叉树用字符串表示就是(前序遍历) A B D 空空 E 空空 C F 空空 G 空空
a[*pi] == '#' || a[*pi] == '\0'这串表达式说明当 你的递归走到 空(就是叶子节点下面为空)
这时候就需要返回,如果你想返回空字符的位置你可以加一个printf(“# ”)用来表示空。
newnode首先是创建一个节点,里面存储的就是a 中 pi 这个数字位置的元素。(A)
第一次pi ++ 下面 是 递归函数,因为是前序遍历所以先搞定左指针。pi ++ 在下一次进入函数后创建的点的值就为 B 因为 pi++ 字符数组的第二个元素 就是B。
然后又进入下一次 递归 现在的 二叉树已经有两个节点 A B ,但是还没有链接因为函数还没开始返回。
现在走到D节点因为D节点下面没有数据所以就返回了两个空,先是他的左边返回空了以后,有开始走他的右边结果右边也是空,在两个空都返回了,return newnode;
因为D节点的递归已经走完所以 返回了D,返回到了B,然后B开始 走他的右边 和 D同理放回E。
然后右边和左边同理。
void BinaryTreeDestory(BTNode** root)
{
if (*root == NULL)
{
return;
}
BinaryTreeDestory(&((*root)->_left));
BinaryTreeDestory(&((*root)->_right));
free(*root);
*root = NULL;
free(root);
root = NULL;
}
关于二叉树的销毁,关于二叉树销毁只能用后序遍历(建议先看后续遍历,就是先遍历全部节点,在进行操作)进行从遍历的最后一个节点开始,进行free,如果用前序遍历free的话,那你第一个根节点都free了你就找不到你的子树了,而中序遍历的话,就比如你刚把D删除了,中序遍历的话就会把 B先删除,那就找不到E节点,E节点就没被删除。所以只能用后序遍历
。这也可以说成是后序遍历的专长
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
这串代码这样理解,你先遍历左边,就是A B D这边,到了D D之后两个空返回0
D返回的就是 0 + 0 + 1. 然后B 的左边走完了,现在要走右边,右边和D同理返回1
那 B 返回的就是 1 + 1 + 1
然后C右子树也是同理返回 1 + 1 + 1
然后 A节点的左右子树都走完后 返回的值就是 3 + 3 + 1 = 7;
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);
}
子叶节点就是求 D E F G 这 4 个节点 一共有几个节点(明知故问)
因为要计算的是叶节点,首先我们要保证树不为空。
root->_left == NULL && root->_right == NULL 这串表达式说明的就是,当一个节点的左右都是空时那就说明他是一个 叶节点。
下面的return 中先遍历左数再遍历右树。就是先遍历A B D 到 D节点了发现D的左右都是空,
那这里的D结束后返回 1 然后 B就开始走 右边 E也是 空那就放回1。
最终 B返回的就是 1 + 1.右边的C也是同理返回 1 + 1;
然后 A最后返回 2 + 2 = 4,算出的就是 叶节点的个数
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);
}
假如我们要计算的是第二层的节点个数。如果树为空那就返回0
接下来直接看return 部分 先遍历树的左边,每次遍历前传入的K减一,这里我们要的第二层所以函数刚进来时的K为 2,然后进入他的左树的同时 k - 1等于 1 所以现在就到了 第二层,然后当K == 1时如果有就返回1.
B返回1 C也同理返回1.最终A返回 2.
当你K = 4 时呢,当然是每次你都减不到 K == 1 的情况因为你每次走到 第4层的时候,你的K虽然等于 1了但是,她因为是NULL节点所以直接返回0 了,所以 K == 4时就是 0.
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->_data == x)
{
return root;
}
BTNode* left = BinaryTreeFind(root->_left, x);
if (left != NULL)
{
return left;
}
else
{
return BinaryTreeFind(root->_right, x);
}
}
这串代码就是当你的一侧走到NULL节点后 就说明这一侧是没有 这个字符的,返回的就是NULL,如果在途中找到了 那就因为第二个判断条件root->_data == x时,那已经返回root了就不会往后走了,就不会遇到空,然后判断左侧的返回的这个节点是否时 NULL,如果不是NULL就直接返回左侧返回的结果。
如果左侧返回的NULL就继续走右边,如果右边也没找到就会返回NULL;
这是一张 关于 4种遍历的 路程,接下来我会解释 。
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ", root->_data);
}
关于先序遍历也可以叫前序遍历(前面我多用的是前序遍历)
前序遍历的历程就是 根 左 右。
中序遍历的历程就是 左 根 右。
后续遍历的历程就是 左 右 根。
这是什么意思呢?前序遍历就是 先操作在往左右走。
就像这里的打印的一样,你先把A打印出来了,然后 在走你的左边,打印出B后继续走B的左边,打印出D后,返回到B节点,B又走他的右边 打印E。然后C子树同理。
最终打印出 A B D E F C。
那也就明了了 中序遍历就是 你先把左边走到 底 然后 先打印左边,在打印根的,在打印右边的。左边走到底就是 D 因为D的后面是空所以 打印D,然后返回到B ,然后先打印B了以后,再去递归 B 的右边,在打印出 E。在左边打印结束后,把A打印出来,在往右边走,右边也是同理打印。
那后续遍历时怎样呢?大家可以自行想象一下,就是左右先走,在打印根。
关于二叉树 的层序遍历有两种方法既可以用 队列来解决,也可以用 栈来解决。
这里用的时队列解决。
void LevelOrder(TreeNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
int levelSize = 1;
while (!QueueEmpty(&q))
{
// 一层一层出
while (levelSize--)
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
printf("\n");
levelSize = QueueSize(&q);
}
printf("\n");
QueueDestroy(&q);
}
层序遍历,和其名字一样,层序就是根据层序一层层进行遍历。
这里用递归是解决不了的,那就需要一个新的思路,这个思路首先你要先整一个队列出来。
将 A 这个根节点先 push 进队列里。
为什么要定义一个 levelsize呢,你可以像一下如果只是打印出来,那他就是 ABCDEF,而不能一层一层打印,这个levelsize就是为了实现一层层打印而加进去的,levelsize也字如其名,就是 这一层你的元素个数。(这个是锦上添花的东西,并不是很重要)
然后层序遍历首先A是在一个队列里的目前第一个元素,那再走下一层之前他是不是就得先打印出来了,不然他不是下一层的,就像祖籍都是从祖先开始一辈一辈的往下写的。
所以在把B Cpush之前你,A就得POP出去。你POP了以后那你怎么找到A左右子树呢?
这时候就需要一个中间变量了用来存储A的结构内容,定义这个front 为A节点的临时拷贝。
然后push不是说连NULL都要写进去,不能你家都没这个人,族谱里还得莫名奇妙多一个把。一定得先从你的左树开始,因为都是从左往右数数的。然后B C进去以后,(这时把你的levelsize定义为 现在队列的个数。这样就可以实现记录每一层的元素个数的效果)
完全二叉树就是在有右树前就必须先有左树。保证中间没有空挡。
bool TreeComplete(TreeNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
int levelSize = 1;
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
break;
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
// 前面遇到空以后,后面还有非空就不是完全二叉树
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
判断完全二叉树做法就是,因为完全二叉树中间是没有空的,所以你只要判断你遇到NULL的时候他的后面是否还有节点如果有就说明不是完全二叉树。
这里的到一个循环结束和前面的层序遍历都差不多,差别就是这里的删除后的插入就算是NULL你也要插入进去,你插入NULL了以后,到前面的节点都POP完了,这个节点front等于NULL的时候就把循环break;
第二循环就是继续判断,因为你第一个循环是因为遍历到NULL了跳出来的,那你第二个循环的主要目的就是寻找 NULL后面是否还有 其他节点。
这里这颗非完全二叉树(右边这颗),你现在B C 刚被 POP 那你现在的队列里还剩。
D NULL E F ,在D pop出去后 G 传入 ,就变成了 NULL E F G。现在你的front == NULL
循环被break。
然后 在 NULL 被 POP 后,现在的front还是 NULL,没进入 if
在下一次循环,这时你的 队列还剩 E F G 所以现在的front 就是 E,有内容的节点。直接就return false。那如果后面的E F G 节点都消失的话,那最后循环就会判空,因为后面都没元素了,如果还剩元素循环就不会判空。