目录
1.树
1.树的基本概念
2.结点之间的的关系描述(还是看上面的图)
3.结点之间的属性描述
4.有序树和无序树
5.森林
6.遍历顺序
1.前序遍历:从根结点——>根结点左子树——>根结点的右子树(中 左 右)
2.中序遍历:左子树——>根——>右子树(左 中 右)
3.后序遍历:左子树——>右子树——>根(左 右 中)
4.层序遍历:一层一层的去遍历
2.二叉树
1.二叉树的种类
(1)满二叉树:满二叉树的任意节点,要么度为0,要么度为2.换个说法即要么为叶子结点,要么同时具有 左右分支
(2)完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1至h-1) 的结点数都达到最大个 数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
(3)二叉搜索树:设立结点便于搜索,结点具有一定顺序,搜索一个结点的时间复杂度为O(log n) 级别
(4)平衡二叉搜索树:左子树和右子树的高度差的绝对值并不能大于1,其实上图也是一个平衡二叉 搜索树
2.二叉树的存储方式
1.链式存储:用指针去指向下一个结点(这种情况用的最多)
2.线式存储:用一个数组来保存二叉树,用一个字符数组来保存二叉树,下标可以去代替指针
3.二叉树的遍历顺序
1.深度优先搜索(递归法和非递归法:迭代,都可以处理)
2.广度优先搜索:
3.总结
4.相关例题
第一题:美国血统
第二题:新二叉树
5.寄语:(写给每一个正在低谷期努力的人)
树是有n个结点的有限集(一种递归定义的数据结构)
当n=0时,说明这个树是一个空树
当树不是空树时,有以下特征:
1.有且只有一个根节点
2.没有后继的结点是叶子结点(终端结点)
3.有后继的结点(除了根结点)都称之为分支结点(非终端结点)
4.除了根结点外,其余结点都有且仅有一个前驱
5.每个结点都有0个或者多个后继
子树的概念:子树的概念是相对于某一个结点而言的,子树也是树。
比如说,B延伸所形成的树就是A的子树,E延伸所形成的树,就是B的子树,所以说,子树没有绝对的概念,只不过是相对的
树的图解
1.祖先结点:相对于E来说上面的B,A都是祖先结点
2.父亲结点:即B,就是其上一个结点
3.孩子结点:即K,L指自己延伸出去的结点
4.兄弟结点;即F,指的是和自己来自于同一个结点的结点
5.堂兄弟结点:一般是GHIJ,有可能会将F加入进来
6.什么是两个结点之间的路径:从上面的结点到下面的结点的路径
7.什么是路径长度:就是判断经过了几条边
1.结点的层次(深度):(默认从1开始,但是有可能也是0)从上往下数:如图所示
2.结点的高度:从下往上数;和深度相反,KML是高度为1,到A是高度为4
3.树的高度:总共几层;这里就是4
4.结点的度:有几个分支,比如说A的度就是3,B的度为2;
5.树的度:各结点度的最大值。比如说在下图就是3,各结点最大分支数就是3;’
有序树——从逻辑上看,树中的结点的各子树从左到右都是有次序的(有逻辑顺序),不能交换
比如说家谱的记录,在同一层,都是按出生先后来记录的,不能改变顺序
无序树——从逻辑上看,树中结点的各子树从左到右都是无次序的,可以交换
比如说中国行政区的列举,你先列举河北还是先列举山东,都是一样的无伤大雅,可以去改变顺序
所以说,一棵树到底是有序树还是无序树,主要看的就是这棵树存的是什么,是否结点的左右位置会反映某些逻辑关系
森林是m棵互不相交的树的集合;
森林连上同一个根结点就是一棵树,当然了,允许有空树也就允许有空森林,当m=0的时候,这个森林就是一个空森林
(因为二叉树的考频更高,所以这里的遍历顺序是以二叉树为模版的)
这里说的前,中,后序遍历其实底层逻辑就是递归,通过递归的方式遍历这棵树,这三种遍历方式都属于dfs(深度优先搜索),不了解的可以看这篇博客:第一周算法训练之深度优先搜索
void qian(shu* root)//前序遍历
{
if (root == NULL)
{
return;
}
printf("%d ", root->data);
qian(root->left);
qian(root->right);
}
void zhong(shu* root)//中序遍历
{
if (root == NULL)
{
return;
}
zhong(root->left);
printf("%d ", root->data);
zhong(root->right);
}
void hou(shu* root)//后序遍历
{
if (root == NULL)
{
return;
}
hou(root->left);
hou(root->right);
printf("%d ", root->data);
}
总和代码:
#include
#include
typedef struct tree//创建树的结构体
{
int data;//树的代表的元素
struct tree* left;//左指针
struct tree* right;//右指针
} shu;
// 新建结点
shu* jie(int x)
{
shu* node = (shu*)malloc(sizeof(shu));
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
// 创建一个二叉树,返回根结点指针
shu* chuang()
{
shu* node1 = jie(1);
shu* node2 = jie(2);
shu* node3 = jie(3);
shu* node4 = jie(4);
shu* node5 = jie(5);
shu* node6 = jie(6);
// 构建二叉树的关系
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
// 前序遍历二叉树
void qian(shu* root)
{
if (root == NULL)
{
return;
}
printf("%d ", root->data);
qian(root->left);
qian(root->right);
}
// 中序遍历二叉树
void zhong(shu* root)
{
if (root == NULL)
{
return;
}
zhong(root->left);
printf("%d ", root->data);
zhong(root->right);
}
// 后序遍历二叉树
void hou(shu* root)
{
if (root == NULL)
{
return;
}
hou(root->left);
hou(root->right);
printf("%d ", root->data);
}
int main()
{
shu* root = chuang();
qian(root); // 前序遍历
printf("\n");
zhong(root); // 中序遍历
printf("\n");
hou(root); // 后序遍历
printf("\n");
return 0;
}
运行结果:
其实这种遍历方式就相当于bfs广度优先搜索,是需要依靠队列来实现的一种算法
void ceng(shu* root)//层序遍历
{
Queue q; // 创建一个队列
QueueInit(&q); // 初始化队列
if (root) // 如果根节点存在
QueuePush(&q, root); // 将根节点入队
while (!QueueEmpty(&q)) // 当队列不为空时
{
shu* front = QueueFront(&q); // 获取队列的第一个元素(即树的结点)
QueuePop(&q); // 将队列的第一个元素出队
printf("%d ", front->data); // 打印出队的结点的数据
if (front->left) // 如果左子节点存在
QueuePush(&q, front->left); // 将左子节点入队
if (front->right) // 如果右子节点存在
QueuePush(&q, front->right); // 将右子节点入队
}
QueueDestory(&q); // 销毁队列
}
以下两个都是满二叉树
(通俗来说就是除了底层以外,其它层都是满的(且从左到右需要连续)(这里主要看的就是p2与p3))
(左子树的所有结点都小于根节点,右子树的所有结点都大于根结点)
typedef struct tree//创建树的结构体
{
int data;//树的代表的元素
struct tree* left;//左指针
struct tree* right;//右指针
} shu;
假设数组第i个元素,2*i+1,代表左指针,2*i+2代表右指针。
ps:这种做法会非常局限一般不会用到。因为只适用于满二叉树,这就会让我们很被动,所以就不详细介绍了
包括前序遍历,中序遍历,后序遍历
(可以用栈去模拟递归,递归的实现本身就是用栈这种数据结构去实现的)
包括层序遍历
二叉树是一种基础的数据结构,我们应当去了解其分类,以及存储方式还有他的遍历顺序
同时我们也应当了解他的定义方式,要能够自己去定义一个二叉树,在二叉树章节中,我们会发现递归出现的频率非常高,所以在这里我也再次复习一下我们的递归三部曲
1.明白这个递归函数到底要干嘛,以及它的参数是什么
2.明白这个递归的结束条件
3.单层递归都需要去遍历哪些元素
题解:通过前序第一个可以确定根节点,然后在中序就可以判断出来左子树和右子树,然后一直递归,先调用左子树,将左子树的结点输出,在调用右子树,将右子树的结点输出,最后输出最后的根结点
AC代码:
#include
using namespace std;
void dfs(string a,string b)
{
if((int)b.size()==0)//如果前序遍历里的数组为空的话,说明遍历完了,该返回上一层了
{
return ;
}
int flag=a.find(b[0]);//找到根结点的坐标
dfs(a.substr(0,flag),b.substr(1,flag));//遍历左子树
dfs(a.substr(flag+1),b.substr(flag+1));//遍历右子树
printf("%c",b[0]);//输出最后的根结点
}
int main()
{
char x[30];//中序遍历
char y[30];//前序遍历
scanf("%s",x);
scanf("%s",y);
dfs(x,y);
return 0;
}
题解:由于是顺序输出,且第一个就是根结点,我们只需要用深搜去一搜到底就可以,找出所有的情况,但是这个地方不需要回溯,只需要找出先后出现的就可以,所以在内部要先搜索左子树,再搜索右子树
AC代码:
#include
using namespace std;
char c[27][4];
void dfs(char t,int n)
{
if(t=='*')
{
return ;
}
for(int i=0;i
你也走了很远的路吧
每个人在最悲伤的时候会突然消失一阵子
到底发生了什么,我来告诉你
世间降临了一场暴雨
天上所有的星星都自杀身亡
但你要相信从此以后晴空万里
耐心点 你会做到的
那些曾经最孤单 最无望 最失落,最安静的经历
其实都不是你最潦倒的时光
你憋着这口气,最终能独自上岸
你耐住了孤单 扛过了无望,不再怕失落,也学会与安静为伴
虽然疼都是别人给的,但伤都是自己好的
你要忍,忍到春暖花开
你要走,走到灯火通明
你要看过世界辽阔 再评判是好是坏
你要铆足劲变好,再站在不敢想象的人身边 ,旗鼓相当
你要变成想象中的样子,这件事,一步都不能让
愿我们都有足够的运气与勇气,去遇见命里不同的风
愿你的世界,星光满载,初心不改,走过汹涌的人潮,历经生活的磨难,终遇良人