c语言二叉树的创建与前序、中序、后序遍历(超详细)学习笔记

前言:

我学习数据结构的方式是看书加看视频,视频看的是哔哩哔哩up主的数据结构-二叉树的创建与遍历 我总结并补充他所讲的内容,他的视频适合有c语言基础的看。

我的文章有点长,希望你能够耐心看完,一定一定会有所收获的!

一、创建二叉树结构体

#include<stdio.h>
#include<stdlib.h>
typedef struct TreeNode
{
	char data;
	struct TreeNode* lChild;
	struct TreeNode* rChild;
}TreeNode;

二、二叉树的初始化的两种思路(前序顺序根左右)递归方法

1、初始化二叉树简便方法

 TreeNode* creatTree()
{
	 TreeNode* T;
	 char ch;
	 scanf_s("%c", &ch);//通过输入的ch是否是‘#’来判断该节点是否有孩子节点

	if ( ch == '#') //'#'代表传入的是空结点
	{
		//此时为空结点
		return NULL;
	}
	else
	{
		//不为空
		T = (TreeNode*)malloc(sizeof(TreeNode));
		T->data = ch;
		//创建左子树,逻辑一致,进行递归
		T->lChild = creatTree();
		//创建右子树,逻辑一致,进行递归
		T->rChild = creatTree();
		return T;
		
	}
}

递归思路演示:

输入AB##C##

创建结构体指针t 接收函数递归结束后最终返回的指针

TreeNode* t = creatTree(); 第一次调用creatTree函数 创建结构体指针T,ch = A ,ch != #为其开辟空间 并将A传入data域,T->data = ch;下一步 T->lChild = creatTree() 给A的左孩子B 进行调用creatTree函数,创建结构体指针T   B != #,为其开辟空间,并将B传入data域,T->data ;下一步 给B的左孩子# 进行调用creatTree函数,创建结构体指针T  # == #,返回return NULL 虽然没有data域 但是树节点依旧存在只是为空;此时 返回到上一步 T->lChild = creatTree() 失败,进行执行T->rChild = creatTree()  给B的右孩子# 进行调用creatTree函数, 创建结构体指针T  # == #,返回return NULL 虽然没有data域 但是树节点依旧存在只是为空;此时 继续返回到上一步,左孩子B弄完了,该执行 A的右孩子C了,给A的右孩子C 进行调用creatTree函数,创建结构体指针T   C != #,为其开辟空间,并将C传入data域,T->data; 下一步:给C的左孩子# 进行调用creatTree函数,创建结构体指针T  # == #,返回return NULL 虽然没有data域 但是树节点依旧存在只是为空;此时 返回到上一步 T->lChild = creatTree() 失败,进行执行T->rChild = creatTree()  给C的右孩子#  进行调用creatTree函数, 创建结构体指针T  # == #,返回return NULL 虽然没有data域 但是树节点依旧存在只是为空;此时 继续返回到上一步,A的T->lChild = creatTree()和T->rChild = creatTree()  都已经执行完毕,最终返回指针T 传入指针t

2、初始化二叉树(2级指针方法)

此方法  运用了2级指针 不建议使用,只是一种思路。

目的:将你输入的二叉树的所有结点以(前序顺序)传入其中,等遍历时再将其依次输出。

初始化树,函数的三个形参:树结构体类型的2级指针 Treenode** T,数组的指针, 索引的指针。

//初始化树
 void creatTree(TreeNode** T,char *data,int * index) //不想返回 又想改变 指针的地址 加上 二级指针 
{
	 char tempData;
     //取数组data的元素
	 tempData = data[*index];
	 //全局变量 无法进行直接更改,只能通过指针方式进行更改
	 *index+=1;
	if ( tempData == '#')//'#'代表传入的是空结点
	{
		//此时为空结点
		*T = NULL;
	}
	else
	{
		//不为空
		*T = (TreeNode*)malloc(sizeof(TreeNode));
        //先储存根结点
		(*T)->data = tempData;                
		//创建左子树,逻辑一致,进行递归
		creatTree(&((*T)->lChild),data,index);
		//创建右子树,逻辑一致,进行递归
		creatTree(&((*T)->rChild),data,index);
	}
}

1.为什么要2级指针? 

因为我不想返回该1级指针,看到后面你就明白了。跟第1中方法不同的是 函数返回类型由TreeNode 变成了 void ,不需要返回该指针了,就不用 创建结构体指针t 接收函数递归结束后最终返回的指针了。这种方法是跟哔哩哔哩up主演示的,也是一种思路吧,看懂了也有好处。

实质:

2级指针:把普通变量的地址传入函数后可以在函数中修改变量的值;把指针的地址传入函数后可以在函数中修改指针的值。

想创建二叉树的新结点,必须为此结点开辟新空间进行malloc申请堆区空间,这跟单链表、双链表、链栈、链队创建新结点是一样的道理,那么若你不想返回该1级指针,只能由2级指针来 改变 此1级指针的地址。

 原理:你得明白C语言中的函数参数传递是按值传递的,如果你只传递一个指针作为参数,函数内部修改这个指针的值不会影响到函数外部的指针。修改1级指针参数是无法影响函数外部的实参指针,除非你返回该指针并赋给其对象的指针。 可以看看这位博主写的C语言函数内部改变指针本身

2.为什么要数组指针?

目的是将你键盘输入的元素传递到数组里,再将数组的元素传递给二叉树结点的data域,传入结点只能一个一个传,所以只能将数组的元素一个一个传递,通过data首地址方式,将data[索引] 依次传递给 树的data域。

3.为什么要用索引指针?

目的是为了可以改变索引的值,因为data[*index ] 每次访问完一个元素,都会将*index的地址进行+1,才能访问下一个元素。 实质:实参 index 是你在函数体外定义的一个全局变量,全局变量无法进行直接修改,只能通过指针的方式进行修改。

三、前序遍历

前序遍历(根左右): ABDECFGH       ‘#’为空   (一直遵循根左右)

遍历顺序在  (1、初始化二叉树简便方法)的思路演示已经展示过了。

根结点=>左孩子=>左孩子=>左孩子=>……=>最深的左孩子=>最深的右孩子=>上一层的右孩子=>他的左孩子=>他的右孩子=>上上一层的右孩子=>他的左孩子=>他的右孩子……

先打印根结点,再进行找左孩子操作,找到该左孩子办事打印输出(因为在他子树结构里,他是根),再找这个左孩子的左孩子,再继续办事打印输出… 打印输出完最后子树的左孩子,开始找最后子树的右孩子,办事打印(因为在他子树结构里,他是根),在打印最后子树(属于左孩子)同一层次的右孩子,办事打印(因为在他子树结构里,他是根)…

//前序遍历   根 左 右
 void preOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
	 {       //先办事 打印输出
		 printf("%c ", T->data);
         //左孩子                                    
		 preOrder(T->lChild);
		 //右孩子                                   
		 preOrder(T->rChild);
	 }
 }

四、中序遍历

中序遍历(左根右):DBEAFCG (一直遵循左根右)

从最深的左孩子开始遍历

//中序遍历  左 根 右
 void inOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
 	 {   //打印左孩子
		 inOrder(T->lChild);
		 //中办事 打印输出
		 printf("%c ", T->data);
		 //打印右孩子
		 inOrder(T->rChild);
	 }
 }

五、后序遍历

 后序遍历(左右根):DEBFGCA  (一直遵循左右根)

从最深的左孩子开始遍历

 void postOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
	 {   //打印左孩子
		 postOrder(T->lChild);
		 //打印右孩子
		postOrder(T->rChild);
		//后办事 打印输出
		printf("%c ", T->data);
		
	 }
 }

六、完整代码

第一种初始化二叉树的完整代码:

注意:我使用的是vs2022 scanf的形式只能变成 scanf_s 

#include<stdio.h>
#include<stdlib.h>
typedef struct TreeNode
{
	char data;
	struct TreeNode* lChild;
	struct TreeNode* rChild;
}TreeNode;
//初始化树
 TreeNode* creatTree()
{
	 TreeNode* T;
	 char ch;
	 scanf_s("%c", &ch);//通过输入的ch是否是‘#’来判断该节点是否有孩子节点


	if ( ch == '#')'#'代表传入的是空结点
	{
		//此时为空结点
		return NULL;
	}
	else
	{
		//不为空
		T = (TreeNode*)malloc(sizeof(TreeNode));
		T->data = ch;
		//创建左子树,逻辑一致,进行递归
		T->lChild = creatTree();
		//创建右子树,逻辑一致,进行递归
		T->rChild = creatTree();
		return T;
		
	}
}
 //前序遍历   根 左 右
 void preOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
	 {       //先办事
		 printf("%c ", T->data);
         //左孩子                                     //再使用遍历的方法对Lchild进行打印 打印的是最近的左孩子 左孩子成为根,继续打印它最近的左孩子
		 preOrder(T->lChild);
		 //右孩子                                    //打印最远的右孩子
		 preOrder(T->rChild);
	 }
 }
 //中序遍历  左 根 右
 void inOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
 	 {   //打印左孩子
		 inOrder(T->lChild);
		 //中办事
		 printf("%c ", T->data);
		 //打印右孩子
		 inOrder(T->rChild);
	 }
 }
 void postOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
	 {   //打印左孩子
		 postOrder(T->lChild);
		 //打印右孩子
		postOrder(T->rChild);
		//后办事
		printf("%c ", T->data);
		
	 }
 }
 int main()
 {
	 TreeNode* T = creatTree();
	 preOrder(T);
	 printf("\n");
	 inOrder(T);
	printf("\n");
	 postOrder(T);
	 printf("\n");
	 return 0;
	 
 }

第二种初始化二叉树的完整代码:

注意:我使用的是vs2022 scanf的形式只能变成 scanf_s ,我的data[20]初始大小设置为20

#include<stdio.h>
#include<stdlib.h>
typedef struct TreeNode
{
	char data;
	sturct TreeNode* lChild;
	struct TreeNode* rChild;
}TreeNode;
//初始化树
 void creatTree(TreeNode** T,char *data,int * index) //不想返回 又想改变 指针的地址 加上 二级指针 
{
	 char tempData;
     //取数组data的元素
	 tempData = data[*index];
	 //全局变量 无法进行直接更改,只能通过指针方式进行更改
	 *index+=1;
	if ( tempData == '#')
	{
		//此时为空结点
		*T = NULL;
	}
	else
	{
		//不为空
		*T = (TreeNode*)malloc(sizeof(TreeNode));
        //先创建根结点
		(*T)->data = tempData;
		//创建左子树,逻辑一致,进行递归
		creatTree(&((*T)->lChild),data,index);
		//创建右子树,逻辑一致,进行递归
		creatTree(&((*T)->rChild),data,index);
	}
}
 //前序遍历   根 左 右
 void preOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
	 {       //先办事 打印输出
		 printf("%c ", T->data);
         //左孩子                                     //再使用遍历的方法对Lchild进行打印 打印的是最近的左孩子 左孩子成为根,继续打印它最近的左孩子
		 preOrder(T->lChild);
		 //右孩子                                    //打印最远的右孩子
		 preOrder(T->rChild);
	 }
 }
 //中序遍历  左 根 右
 void inOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
 	 {   //打印左孩子
		 inOrder(T->lChild);
		 //中办事 打印输出
		 printf("%c ", T->data);
		 //打印右孩子
		 inOrder(T->rChild);
	 }
 }
 void postOrder(TreeNode* T)
 {
	 if (T == NULL)
	 {
		 return;
	 }
	 else
	 {   //打印左孩子
		 postOrder(T->lChild);
		 //打印右孩子
		postOrder(T->rChild);
		//后办事 打印输出
		printf("%c ", T->data);
		
	 }
 }
int main()
{
	//输入数的长度
	int n;
	scanf_s("%d\n", &n);
	char data[20];
	//("%s", data);
	for (int i = 0; i < n; i++)
	{
		data[i] = getchar();
		
	}
	TreeNode* T;
	int index = 0;
    creatTree(&T, data, &index);
	preOrder(T);
	printf("\n");
	inOrder(T);
	printf("\n");
	postOrder(T);
	return 0;

七、运行结果

第一种初始化二叉树的方法运行结果:

输入AB##C## 输入方式按前序顺序

输入ABD##E##CF##G## 输入方式按前序顺序

第二种初始化二叉树的方法的运行结果:

第一行:输入 7 

第二行:输入AB##C## 输入方式按前序顺序

第一行:输入 15 

第二行:输入ABD##E##CF##G## 输入方式按前序顺序

总结: 

初始化二叉树的顺序是前序顺序,然后有三种遍历方式(前序、中序、后序)。

搞懂第一种初始二叉树的方法就好了;第二种方法 2级指针 看懂了也厉害 这种方法是up主所演示的 感兴趣还可以再看看他的视频数据结构-二叉树的创建与遍历

制作不易,真心想让你懂,还是有不足的地方,望见谅嘞。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年12月12日
下一篇 2023年12月12日

相关推荐