目录
1.二叉链表:由一个数据和两个分别指向其左右子树的分支构成。
🎇。一、树的定义:
树(Tree)是n(n>=0) 个节点,n-1条边的有限集T。T为空时就称为空树。
⇲
1.T中有且仅有一个结点K,没有前驱,称K为树的根节点(root)
2.除根结点外,每个结点有且仅有一个直接前驱。
3.T中可以有0个或者多个后继。
4.当n>=1时,除根结点外,其余节点可以分为m个互不相交的有限集合,被称为根结点k的子树(subtree)。
⇱
✨二、树的表示
1.树形图表示法
2.嵌套集合法(文氏图表示)
3.广义表表示法
4.凹入表示法
🎇三、树的基本术语
1.树的结点:包含一个数据元素和若干个指向其子树的分支。
2.结点的度(Degree):一个节点拥有的子树的个数
3.叶子结点 leaf(或终端节点):度为零的结点
4.分支结点(非终端节点):度不为零的结点
5.树的度:树中所有结点的度的最大值。
6.结点的孩子结点(child):该结点的子树的根结点。
7.双亲节点(parent)
8.兄弟结点(sibling):同一个双亲的孩子之间。
9.堂兄弟结点:其双亲在同一个层次(双亲不相同)的结点互为堂兄弟。
10.结点的祖先:从根节点到该节点的所经过的分支上的所有结点。
11.结点的层次(level):从根开始定义,根为第一层。
12.树的深度(depth):树从根结点开始往下数,叶子结点所在的最大层数,默认从1开始,
为零时,为空树。
13.树的高度(height):等于树的深度,方向:从下往上(根节点)数。
💫四、森林(forest)
森林是m(m>=0)个互不相交的树的集合。对于树中每个结点,其子树的集合即为森林。故可以用森林和树的相互递归定义来描述树。
💌五、二叉树(binary tree)
(1)定义:每个结点至多只有两棵子树,并且二叉树的子树有左右之分,其次序不能任意颠倒。(非线性结构,一对多关系)
(2)二叉树的五种基本形态(判断相关二叉树的操作的基础)
(3)本质:递归定义的二叉树。
🔱(4)二叉树的性质:
1.在二叉树的第i层上,至多有2^( i-1) 个结点(i>=1)
2.深度为k的二叉树至多有2^k - 1 个节点(k>=1)
3.对于任意一棵二叉树T,如果其终端结点树为n,度为二的节点数为n,则n=n+1
4.树的节点数 = 总度数 + 1。
5.n个结点的二叉链表共有n+1个空链域 构造线索而二叉树。
6.如图:
7.路径:两个节点之间的路径是单调的,只能从上往下。
8.路径长度:经过几条边.
二叉树的分类:
(1)满二叉树(fullbinaryTree):最后一层都是叶子结点,其他各层的结点都有左右子树的二叉树。
二进制编号:满二叉树按照层序从1从左向右开始编号,结点i的左孩子为2*i,右孩子为(2*i+1)结点i的父节点为[i/2].
(2)完全二叉树(completeBinaryTree):一棵结点数为n的二叉树,按照二进制编号原则,当且仅当其每一个结点都与结点数为n的满二叉树中的编号从1到n的结点一一对应(非常适合用顺序存储结构的树)。
特点:1.最多只有最后两层有叶子结点;2.最多只有一个度为1的结点
3.一个结点i的左孩子为2*i ,右孩子为2*i+1,结点i的父结点为[i/2].
🕳4.i<=[n/2]为分支节点 ; i>[n/2]为叶子结点
(3)二叉排序树(以右为尊):用于元素的排序搜索
左子树上的所有结点的关键字均小于根结点的关键字,右子树上的所有结点的关键字均大于根节点的关键字。左右子树各是一棵二叉排序树。
(4)平衡二叉树:树上任意结点的左子树和右子树的深度之差不超过1
😉二叉树的存储结构
(1)顺序存储结构
const int Max = 1000;
typedef int ElemType; //数据域数据类型
typedef struct TNode BTree; //存储结构的定义
typedef struct TNode {
ElemType value;
bool isEmpty;
};
BTree t[Max];//声明顺序存储的树
(2)链式存储结构
1.二叉链表:由一个数据和两个分别指向其左右子树的分支构成。
typedef int ElemType; //数据域的数据类型
typedef struct BiTNode *PtrToNode; //定义树的结点和结点内的指针域
typedef PtrToNode BiTree;//二叉树的定义
struct BiTNode { //结点的定义(二叉链)
ElemType data; //数据域
PtrToNode ls,rs; //指向左右孩子的指针
};
2.三叉链表:二叉链表的基础上增加一个指向双亲的指针域。
typedef int ElemType; //数据域的数据类型
typedef struct BiTNode *PtrToNode; //定义树的结点和结点内的指针域
typedef PtrToNode BiTree;//二叉树的定义
struct BiTNode { //结点的定义(二叉链)
ElemType data; //数据域
PtrToNode ls,rs,parent; //指向左右孩子的指针
};
✨遍历问题:
1.先序遍历:根左右(DLR):第一次路过访问该节点
[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
一个小人从一棵二叉树根节点为起点沿二叉树外沿,逆时针走一圈回到根节点。路上遇到的元素顺序,就是先序遍历的结果
先序遍历结果为:A B D H I E J C F K G
2.中序遍历:左根右(LDR)第二次路过访问该结点
[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
可以看成二叉树每个节点,垂直方向投影下来(可以理解为每个节点从最左边开始垂直掉到地上),然后从左往右数,得到的结果便是中序遍历的结果
中遍历结果为:H D I B E J A F K C G
3.后序遍历:左右根(LRD)第三次路过访问该节点
就像剪葡萄,我们要把一串葡萄剪成一颗一颗的,
围着树的外围(从左向右)一圈,如果发现一剪刀就能剪下的葡萄(必须是一颗葡萄)(也就是葡萄要一个一个掉下来,不能一口气掉超过1个这样),就把它剪下来,组成的就是后序遍历了。
后序遍历结果:H I D J E B K F G C A
4.层序遍历(BFS):通过使用"队列"来辅助实现的
层次遍历很好理解,就是从根节点开始,一层一层,从上到下,每层从左到右,依次写值。
层次遍历结果:A B C DE F G H I J K
前中后序遍历的理解的转载自CSDN博主「正弦定理」的原创文章,遵循CC 4.0 BY-SA版权协议,链接为:https://blog.csdn.net/chinesekobe/article/details/110874773
😁线索二叉树(ThreadBinaryTree):利用树的n+1个空链域
若结点有左孩子,则用ls指示其左孩子,否则令ls域指向其前驱;若结点有右孩子,则用ls指示其右孩子,否则令ls域指向其后继。加两个标志域明确ls和rs的指向。(前驱和后继是指遍历过程中的结点的前驱和后继,以上结点结构构成的二叉链表构成的存储结构叫做线索二叉树,其中指向前去和后继的指针叫做线索,加上线索的二叉树叫做线索二叉树。
typedef int ElemType; //数据域的数据类型
typedef enum { link, Thread }PointerTag; //link==0,表示连的结点,Thread==1,表示连接的是线索
typedef struct BiTNode *PtrToNode; //定义树的结点和结点内的指针域
typedef PtrToNode BiThrTree;//二叉树的定义
struct BiTNode { //结点的定义
ElemType data; //数据域
PtrToNode ls, rs; //指向左右孩子的指针
PointerTag ltag, rtag;
};
二叉树的线索化(以中序线索二叉树为例)
实质上就是以某种次序遍历一颗二叉树使其变成线索二叉树。
线索化思路:在遍历过程中,访问结点的操作就是检查此结点的左右指针域是否为空,如果为空就将其指向其前驱或者后继。为了实现这一操作,设p为访问节点,pre为刚刚访问的结点,即p的前驱,便于修改pre的后继结点,p的前驱结点。
以下以中序线索二叉树为例:
PtrToNode pre = NULL;
void InThread(BiThrTree root) {
if (NULL == root) return ;
InThread(root->ls);
if (NULL == root->ls) { //左子树为空建立前驱线索
root->ls = pre;
root->ltag = Thread;
}
if (pre && NULL == pre->rs ) { //建立前驱节点的后继线索
pre->ls = root; //if中的pre是判空条件,作用是防止中序遍历访问第一个结点无前驱结点的特殊情况。
pre->rtag = Thread;
}
pre = root;
InThread(root->rs);
}
中序线索二叉树找中序后继
思路:找到指定结点p的中序后继next
(1)若p->rtag == Thread,则next = p->rs;
(2)若p->ltag==link,则next 为p的右子树中最左下结点.
//找到以p为根的子树中,第一个被遍历的结点
BiThrTree FirstNode(BiThrTree p) {
while (p->ltag == link) p = p->ls;
return p;
}
//在中序二叉树中找到结点p的后继结点
BiThrTree Nextnode(BiThrTree p) {
if (p->rtag == link) return FirstNode(p->rs);
return p->rs;
}
中序线索二叉树找中序前继
思路:找到指定结点p的中序前驱next
(1)若p->rtag == Thread,则next = p->ls;
(2)若p->ltag==link,则next 为p的左子树中最右下结点.
//找到以p为根的子树中,最后一个被遍历的结点
BiThrTree LastNode(BiThrTree p) {
while (p->rtag == link) p = p->rs;
return p;
}
//在中序二叉树中找到结点p的前驱结点
BiThrTree Prenode(BiThrTree p) {
if (p->ltag == link) return LastNode(p->ls);
return p->ls;
}
中序线索二叉树进行中序遍历
//找到以p为根的子树中,第一个被遍历的结点
BiThrTree FirstNode(BiThrTree p) {
while (p->ltag == link) p = p->ls;
return p;
}
//在中序二叉树中找到结点p的后继结点
BiThrTree Nextnode(BiThrTree p) {
if (p->rtag == link) return FirstNode(p->rs);
return p->rs;
}
//对中序二叉树进行中序遍历
void Inorder(BiThrTree root) {
for (BiThrTree p = FirstNode(root); p; p = Nextnode(p)) {
/* 对节点的操作 */
visit(p);
}
}
树的存储结构
1.双亲表示法(顺序存储)
每个结点保存指向双亲的“指针”
const int Max = 100000;
typedef int ElemType; /*结点数据类型*/
typedef struct PTreeNode {
ElemType data; /*结点数据*/
int parent; /*双亲指示器*/
}PTreeNode;
typedef struct PTree {
PTreeNode nodes[Max];
int r,n; /*r表示结点在数组中的位置。n表示在树中结点总数*/
}PTree;
2.孩子表示法(顺序+链式存储)
定义:把每个结点的孩子排列起来,看成一个线性表,且以单链表作存储结构,则n个结点右n个孩子结点
typedef struct CTNode {
int child;
struct CTNode* next;
}ChildPtr;
typedef struct {
int data;
ChildPtr* firstchild;
}CTBox;
typedef struct {
vector<CTBox>nodes;
int n,r;
}CTree;
/*
int dfs(int i,int s) { //i为第一个遍历的节点
ChildPtr *p=root.nodes[i].firstchild;
int n=1;
v.push_back(i);
if (NULL == root.nodes[i].firstchild) {
//操作
}
while (p) {
n = dfs(p->child,s+1)+1;
v.pop_back();
p = p->next;
}
return n;
}
*/
✨3.树的孩子兄弟表示法(表示出来是一个二叉链表)
firstchild指向此节点的第一个孩子结点,nextsibling指向下一个兄弟结点。
typedef int ElemType;
typedef struct CSNode* PtrToNode;
typedef PtrToNode CSTree;
struct CSNode {
ElemType data;
PtrToNode firstchild, nextsibling;
};
森林的存储结构
(1)森林的定义
零棵或者有限棵互不相交的树的集合称为森林。
▶树和森林的差异很小,一棵树是一个森林,任何一颗树删除根节点后剩下的部分也是森林。
(2) 树和森林的基本操作
1.树与二叉树的相互转换
只要将树转化成孩子兄弟表示法就转化成了二叉树,如果不理解孩子兄弟表示法怎么做到的,可以看看下图
2.森林与二叉树的相互转换
(3)树和森林的遍历
1.树的遍历
▶先序遍历:访问树的根结点,再依次从左到右先序遍历根的每棵子树。
▶后序遍历:依次从左到右后序遍历根的每棵子树,最后访问根结点。
▶层次遍历:从上到下、从左到右访问树的每个结点。
图5-26(a)的先序遍历:ABCEFGD 后序遍历:BEFGCDA
2.森林的遍历
▶先序遍历:若森林非空:
▫访问森林中第一棵树的根结点。
▫先序遍历第一棵树根结点的子树森林。
▫先序遍历除去第一颗子树后剩余树构成的森林。
▶后序遍历:若森林非空:
▫后遍历森林中第一棵树根结点的子树森林。
▫访问森林中第一棵树的根结点。
▫后序遍历除去第一颗子树后剩余树构成的森林。
❤森林的先序遍历和后序遍历是其对应的二叉树的先序遍历序列和中序遍历序列
图5-29(a)的森林先序遍历:ABDGMPHCEJFKNL 后序遍历:BGMPDHAEJCKNFL