前言:
我学习数据结构的方式是看书加看视频,视频看的是哔哩哔哩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主所演示的 感兴趣还可以再看看他的视频数据结构-二叉树的创建与遍历。
制作不易,真心想让你懂,还是有不足的地方,望见谅嘞。