C语言二叉树的基础知识

二叉树:

什么是二叉树:

二叉树是计算机科学中的一种树形数据结构,其中每个节点最多有两个子树,通常被称为“左子树”和“右子树”。

简单地理解,满足以下两个条件的树就是二叉树:

  1. 本身是有序树(结点不能随便交换);
  2. 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2。

二叉树的性质:

经过前人的总结,二叉树具有以下几个性质:

  1. 二叉树中,第 i 层最多有 2i-1 个结点。
  2. 如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
  3. 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。

二叉树的分类:

如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
如图:

下面我们来看看二叉树的四种遍历方式:

二叉树的四种遍历方式:

1.先序遍历:在先序遍历中,我们首先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树,自己先于左子树先于右子树(中左右)。
代码如下:
void preordertraverse(struct tree*p){
	if(p==NULL){
		return;
	}
	printf("%c",p->data);
	preordertraverse(p->lchild);
	preordertraverse(p->rchild);	
}
2.中序遍历:左孩子先于父母先于右孩子,我们首先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树(左中右)。
代码如下:
void inordertraverse(struct tree*p){
	if(p==NULL){
		return;
	}
	inordertraverse(p->lchild);
	printf("%c",p->data);
	inordertraverse(p->rchild);	
}
3.后序遍历:在后序遍历中,我们首先递归地遍历左子树,然后递归地遍历右子树,最后访问根节点,左孩子先于右孩子先于父母(左右中)。
代码如下:
void postordertraverse(struct tree*p){
	if(p==NULL){
		return;
	}
	postordertraverse(p->lchild);
	postordertraverse(p->rchild);	
	printf("%c",p->data);
}
从上方可以看出,三种遍历方式其实都大差不差,唯一的区别就是代码之间的顺序。如图:

对于这样一颗满二叉树,它的三种遍历结果分别为:
先序遍历:1245367
中序遍历: 4251637
后序遍历: 4526731
再来看看层序遍历:
4.层序遍历:对于上面那一颗满二叉树,层序遍历是从上到下,从左到右的遍历,因此,他的输出结果为1234567,以下是他的代码实现:
首先,我们需要一个队列,而且队列包含一个结构体数组,来控制出入队:
struct queue {
	struct tree* data[MAX_SIZE];
	int front;
	int rear;
};
层序遍历代码如下:
void leverorder(struct tree* p) {
    // 定义一个队列 q
    struct queue q;
    // 初始化队列的前端和后端
    q.front = 0;
    q.rear = 0;
    // 如果树的根节点不为空,将其添加到队列中
    if (p != NULL) q.data[q.rear++] = p;
    // 当队列不为空时,执行循环
    while (q.front != q.rear) {
        // 从队列前端取出一个节点
        struct tree* node = q.data[q.front++];
        // 打印节点的数据
        printf("%c", node->data);
        // 如果节点的左子节点不为空,将其添加到队列的后端
        if (node->lchild != NULL) q.data[q.rear++] = node->lchild;
        // 如果节点的右子节点不为空,将其添加到队列的后端
        if (node->rchild != NULL) q.data[q.rear++] = node->rchild;
    }
}
当然,除了遍历二叉树,我们还可以创建二叉树:
用数组创建的一个二叉树:
#include <stdio.h>
// 定义二叉树的数组
int tree[100];
// 插入节点
void insert(int data, int index) {
    tree[index] = data;
}
// 打印二叉树
void print_tree(int index, int n) {
    if (index >= n || tree[index] == 0) {
        return;
    }
printf("%d ", tree[index]);
// 打印左孩子
print_tree(2*index + 1, n);
// 打印右孩子
print_tree(2*index + 2, n);
}
// 主函数
int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7};
    int n = sizeof(arr)/sizeof(arr[0]);
for (int i = 0; i < n; i++) {
    insert(arr[i], i);
}
print_tree(0, n);
return 0;
}
用先序遍历链式创建的一个二叉树(扩展二叉树):
#include <stdio.h>
#include <stdlib.h>
struct tree {
	char data;
	struct tree* lchild;
	struct tree* rchild;
};
void pret(struct tree* p) {
	if (p == NULL) {
		return;
	}
	printf("%c", p->data);
	pret(p->lchild);
	pret(p->rchild);
}

void createtree(struct tree** T) {
	char ch;
	scanf("%c", &ch);
	if (ch == '#') {
		*T = NULL;
		return;
	}
	struct tree* node = (struct tree*)malloc(sizeof(struct tree));
	node->data = ch;
	*T = node;
	createtree(&((*T)->lchild));
	createtree(&((*T)->rchild));
}
int main() {
	struct tree* p;
	createtree(&p);
	pret(p);
	return 0;
}

这段代码是用C语言编写的,它定义了一个二叉树结构,并提供了创建和打印二叉树的功能。下面是对每个部分的详细解释:

  1. struct tree:这是一个结构体,用于定义二叉树的节点。每个节点包含一个字符数据data,以及两个指向其左右子节点的指针lchildrchild
  2. pret函数:这是一个递归函数,用于前序遍历二叉树并打印每个节点的数据。如果传入的节点指针为NULL,函数将返回;否则,它将首先打印当前节点的数据,然后递归地遍历左子树和右子树。
  3. createtree函数:这也是一个递归函数,用于创建二叉树。它首先读取一个字符,如果字符是’#',则将传入的节点指针设为NULL并返回;否则,它将创建一个新的节点,将读取的字符存入节点的数据,然后递归地创建左子树和右子树。
  4. main函数:这是程序的入口点。它首先创建一个空的二叉树,然后调用createtree函数来填充这棵树,最后调用pret函数来打印这棵树。

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8c6bbe159de44a359e83c5cb9ef93142.png

例如,我们要创建这个二叉树,我们只需要按顺序输入135##8##2##即可。

对于有#这种虚结点,我们叫扩展二叉树,在扩展二叉树中,每个节点(除了补全的#节点)都有两个孩子,这使得前序遍历相当于普通遍历的前序+中序,后序遍历相当于普通遍历的后序+中序,因此,扩展二叉树的前序遍历或后序遍历就能唯一确定一棵二叉树但是,中序遍历仍然无法单独确定一棵扩展二叉树,因为它仍然无法确定根节点。

然而,需要注意的是,如果没有#这个条件,那么创建一棵二叉树,至少需要两种遍历方式才能确定一颗二叉树的结构,同样,对于遍历结果,如果仅仅只有一种,而且没有#号,也不能确定。#是对二叉树的一种扩展,对于一颗二叉树我们有:

  1. 同时给定一棵二叉树的先序序列和中序序列就能唯一确定这棵二叉树。(正确)
  2. 同时给定一棵二叉树的中序序列和后序序列就能唯一确定这棵二叉树。(正确)
  3. 同时给定一棵二叉树的先序序列和后序序列就能唯一确定这棵二叉树。(错误)
举例:我们可以用两种遍历结果创建一颗二叉树,代码如下:
#include <stdio.h>
#include <stdlib.h>
int idx = 0;//声明索引
// 定义二叉树的节点
typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;
// 创建新节点
Node* newNode(int data) {
    Node* node = (Node*)malloc(sizeof(Node));
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return node;
}
// 根据先序遍历和中序遍历创建二叉树
Node* buildTree(int* preorder, int* inorder, int Start, int End) {
    if (Start > End) {
        return NULL;
    }
Node* node = newNode(preorder[idx++]);
int Index;
for (Index = Start; Index <= End; Index++) {
    if (inorder[Index] == node->data) {
        break;
    }
}
node->left = buildTree(preorder, inorder, Start, Index - 1);
node->right = buildTree(preorder, inorder, Index + 1, End);
return node;
}
// 打印二叉树的中序遍历
void printInorder(Node* node) {
    if (node == NULL) {
        return;
    }
printInorder(node->left);
printf("%d ", node->data);
printInorder(node->right);
}
// 主函数
int main() {
    int preorder[] = {3, 9, 20, 15, 7};
    int inorder[] = {9, 3, 15, 20, 7};
    int len = sizeof(inorder) / sizeof(inorder[0]);
Node* root = buildTree(preorder, inorder, 0, len - 1);
printInorder(root);
return 0;
}
其中最重要的就是如何去创建这颗二叉树,以下是相关代码的解释:
  1. Node* buildTree(int* preorder, int* inorder, int Start, int End) {:这是一个函数定义,函数名为buildTree,它接收四个参数:一个指向前序遍历结果数组的指针preorder,一个指向中序遍历结果数组的指针inorder,以及两个整数StartEnd,表示要处理的数组部分的开始和结束位置。
  2. if (Start > End) { return NULL; }:如果开始位置大于结束位置,那么这个子树为空,返回NULL。
  3. Node* node = newNode(preorder[idx++]);:创建一个新的节点,节点的值为前序遍历结果数组的当前元素,然后将索引idx加1。
  4. if (Start == End) { return node; }:如果开始位置等于结束位置,那么这个子树只有一个节点,返回这个节点。
  5. int Index; for (Index = Start; Index <= End; Index++) { if (inorder[Index] == node->data) { break; } }:在中序遍历结果数组中找到新创建的节点的值,找到后跳出循环。
  6. node->left = buildTree(preorder, inorder, Start, Index - 1);:递归地构建左子树,左子树的节点在中序遍历结果数组中都在新创建的节点的值的左边。
  7. node->right = buildTree(preorder, inorder, Index + 1, End);:递归地构建右子树,右子树的节点在中序遍历结果数组中都在新创建的节点的值的右边。
  8. return node;:返回新创建的节点,这个节点是当前子树的根节点。
通俗来讲,前序遍历是根结点在前,然后是左右,中序遍历是左节点在前,然后是根节点和右节点,而且前序遍历的第一个必然是它的根节点,然后我们在中序遍历中找到这个节点,那么这个节点左边的数就是根节点左子树的所有节点,右边就是它右子树的所有节点,而前序遍历的第二个,就是根节点左子树的根节点,然后再到中序遍历中找到这个节点,同样的方法递归实现就可以得出它的构造,(绕死了)。

以上就是本周的学习。

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiao Ling.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值