目录
2.线式存储:用一个数组来保存二叉树,用一个字符数组来保存二叉树,下标可以代替指针
1.前序遍历:从根结点——>根结点左子树——>根结点的右子树(中 左 右)
一.树的概念和属性
1.树的基本概念
树是有n个结点的有限集(一种递归定义的数据结构)
当n=0时,说明这个树是一个空树
当树不是空树时,有以下特征:
1.有且只有一个根节点
2.没有后继的结点是叶子结点(终端结点)
3.有后继的结点(除了根结点)都称之为分支结点(非终端结点)
4.除了根结点外,其余结点都有且仅有一个前驱
5.每个结点都有0个或者多个后继
子树的概念:子树的概念是相对于某一个结点而言的,子树也是树。
比如说,B延伸所形成的树就是A的子树,E延伸所形成的树,就是B的子树,所以说,子树没有绝对的概念,只不过是相对的
树的图解
2.结点之间的属性描述即解释(如上图)
- 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为2
- 叶节点:度为0的节点称为叶节点; 如上图:G、H、I节点为叶节点
- 非终端节点或分支节点:度不为0的节点; 如上图:B、D、C、E、F节点为分支节点
- 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
- 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
- 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为2
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
- 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
- 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
- 森林:由m棵互不相交的树的集合称为森林
二. 二叉树概念及结构
1概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:
- 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒。
2.数据结构中的二叉树
2.结构
1.二叉树的基本形态
1.二叉树的五种基本形态
-
1.空树
-
2.只有一个根结点
-
3.根结点只有左子树
-
4.根结点只有右子树
-
5.根结点既有左子树又有右子树
2.斜着的二叉树
①左斜树:所有的结点只有左子树
②右斜树:所有的结点只有右子树
特点:每层只有一个结点,结点的个数等于二叉树的深度
2.特殊的二叉树
二叉树包括多个种类,有满二叉树,完全二叉树,平衡二叉树,二叉搜索数。
1.满二叉树
满二叉树的任意节点,要么度为0,要么度为2,即要叶子都有(两片叶子都在),要么一片都没有,这样容易理解吧
2.完全二叉树
最下面一层中有缺少一片叶子或者多片叶子就可以达到满二叉树的树是完全二叉树,即倒数第二层有一个或者几个只有一个孩子。如图,左边的是满二叉树,右边的则是完全二叉树。
3.平衡二叉树
判断「平衡二叉树」的 2 个条件:
1.是「二叉排序树」
2. 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
很明显上面的连二叉树都不是,所以直接pas
这个虽然是二叉树,仔细观察一下,却没有排序的二叉树,所以也不是。
上图才是真正符合标准的平衡二叉树。想要更了解的话,可以去平衡二叉树看看。
4.二叉搜索树
二叉搜索树又称为二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值。
- 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值。
- 它的左右子树也分别是二叉搜索树。
下图是符合标准的二叉搜索树。
- 二叉搜索树与普通的二叉树有所不同,如果我们按照中序遍历进行访问结点的值的时候我们可以发现它是一个升序的序列。
- 每个结点的左子树结点上边的值都小于该结点的值,右子树是上边的值都大于该结点的值。
3.二叉树的存储方式
1.链式存储:用指针去指向下一个结点(常用)
typedef struct tree//创建树的结构体
{
int data;//树的代表的元素
struct tree* left;//左指针
struct tree* right;//右指针
} shu;
2.线式存储:用一个数组来保存二叉树,用一个字符数组来保存二叉树,下标可以代替指针
假设数组第i个元素,2*i+1,代表左指针,2*i+2代表右指针(数组从0开始使用)
假设数组第i个元素,2*i,代表左指针,2*i+1代表右指针(数组从1开始使用)
4.二叉树的遍历顺序
前面三种主要看“种”在什么位置,在前面就是前序遍历,在中间就是中序遍历,在后面就是后序遍历,并且该三种都是dfs遍历,先是一条路走到底,在回头,在走到底。第四种则是bfs,一层一层地遍历,一层一层地搜索。
1.前序遍历:从根结点——>根结点左子树——>根结点的右子树(中 左 右)
前序遍历顺序:A-->B-->D-->H-->I-->E-->J-->C-->F-->G
void q(tree *root) //前序遍历
{
if(NULL == root){
return;
}
printf("%c ", root->data);//输出当前节点的数据
q(root->left);//将子节点作为下一个根节点遍历左孩子数
q(root->right);//将子节点作为下一个根节点遍历右孩子数
}
2.中序遍历:左子树——>根——>右子树(左 中 右)
中序遍历顺序:H-->D-->I-->B-->J-->E-->A-->F-->C-->G
void z(tree *root)//中序遍历
{
if(NULL == root){
return;
}
z(root->left);//将子节点作为下一个根节点遍历左孩子数
printf("%c ", root->data);//输出当前节点的数据
z(root->right);//将子节点作为下一个根节点遍历右孩子数
}
3.后序遍历:左子树——>右子树——>根(左 右 中)
后序遍历顺序:H-->I-->D-->J-->E-->B-->F-->G-->C-->A
void h(tree *root)//后序遍历
{
if(NULL == root){
return;
}
h(root->left);//将子节点作为下一个根节点遍历左孩子数
h(root->right);//将子节点作为下一个根节点遍历右孩子数
printf("%c ", root->data);//输出当前节点的数据
}
4.层序遍历
该遍历方式就相当于bfs广度优先搜索,是需要依靠队列来实现的一种算法,当然也可以模拟队列,重要的是队列思维。
层序遍历顺序:A-->B-->C-->D-->E-->F-->G-->H-->I-->J
void Level_Tree(Tree *tree)//层序遍历
{
if(tree == NULL){
return;
}
Tree *pos[N];
int front, rear;//对头指针和对尾指针,用于出队和入队操作
rear = N;
while(rear--){
pos[rear] = NULL;//全部置为空,方便后续判断
}
front = rear = 1;//此时为空队列,都指向第一个元素
pos[front] = tree;
rear++;//对尾指针偏移一位,用于存放新数据
while(pos[front] != NULL){
printf("%c ", pos[front]->data);
if(pos[front]->l_child != NULL){
pos[rear] = pos[front]->l_child;//左孩子节点入队
rear++;
}
if(pos[front]->r_child != NULL){
pos[rear] = pos[front]->r_child;//右孩子节点入队
rear++;//尾指针偏移
}
front++;//头指针偏移一位判断下一个元素
}
}
怕你们看不懂,我又写了一种
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); // 销毁队列
}
三.例题
一个小小的问题:输出二叉树中从每个叶子结点到根结点的路径
1.美国血统 American Heritage
输入:
ABEDFCHG
CBADEFGH
输出:
AEFDBHGC
看不懂的可以去看看find函数,substr函数,用于string,不用万能头,还需要定义#include<string.h>
#include<bits/stdc++.h>
using namespace std;
void dfs(string a,string b) {
if((int)b.size()==0) { //如果前序遍历里的数组为空的话,说明遍历完了,该返回上一层了
return ;
}
int flag=a.find(b[0]); //找到根结点的坐标,数组a中字符等于b[0]的位置
dfs(a.substr(0,flag),b.substr(1,flag));//遍历左子树,截取,0开始,截取flag个字符
dfs(a.substr(flag+1),b.substr(flag+1));//遍历右子树,从flag+1开始的字符一直到最后一个为止
printf("%c",b[0]); //输出最后的根结点
}
int main() {
string x;//中序遍历
string y;//前序遍历
cin>>x>>y;
dfs(x,y);
return 0;
}
//输入:ABEDFCHG中
// CBADEFGH前
//AEFDBHGC
2.新二叉树
输入:
6
abc
bdi
cj*
d**
i**
j**
输出:
abdicj
#include<bits/stdc++.h>
using namespace std;
char a[27][27]; //定义全局变量
int n;
void dfs(char r) {
if(r=='*') //pas
return;
else {
for(int i =0; i<n; i++) {
if(a[i][0]==r) {
printf("%c",r);
dfs(a[i][1]); //dfs递归
dfs(a[i][2]);
}
}
}
}
int main() {
cin>>n;
for(int i=0; i<n; i++)
cin>>a[i];
dfs(a[0][0]);
return 0;
}
3.求先序排列
输入:
BADC
BDCA
输出:
ABCD
个人感觉和第一题差不多
#include<bits/stdc++.h>
using namespace std;
void dfs(string a,string b) {
if (a.size()>0) {
char ch=b[b.size()-1]; //从后面开始
cout<<ch; //找根输出
int k=a.find(ch);
dfs(a.substr(0,k),b.substr(0,k)); //递归左子树
dfs(a.substr(k+1),b.substr(k,a.size()-k-1));//递归右子树;
}
}
int main() {
string a,b;
cin>>a>>b;
dfs(a,b);
return 0;
}
4.遍历问题
输入:
abc
cba
输出:
4
可以自己试着画一下图,更清楚
#include<bits/stdc++.h>
using namespace std;
char a[10002],b[10002];//分别记录前序和后序
int main() {
cin>>a>>b;
int len=strlen(a),t=1;
for(int i=0; i<=len-2; i++)
for(int j=0; j<=len-1; j++) //没有经过右止点,才有下面这个判断
if(b[j]==a[i]&&b[j-1]==a[i+1]) //有一个节点只有一个分支,即只有一个儿子
t*=2;
cout<<t;
return 0;
}
5.二叉树深度
输入:
7
2 7
3 6
4 5
0 0
0 0
0 0
0 0
输出:
4
一点也不难
#include<bits/stdc++.h>
using namespace std;
struct node {
int left, right;
};
node tree[1000001];//存储结构定义
int n, ans;
void dfs(int i, int h) {
if (i == 0)
return ; //到达叶子节点时返回
ans = max(ans, h); //更新答案
dfs(tree[i].left, h+1); //向左遍历
dfs(tree[i].right, h+1); //向右遍历
}
int main() {
cin >> n;
for(int i=1; i<=n; i++)
cin >> tree[i].left >> tree[i].right;//读入并且建树
dfs(1, 1); //从1号节点出发,当前深度为1
cout << ans ;
return 0;
}