2021年四月三十日 于海棠咖啡馆
常见的树结构:计算机储存
树的特征:对于每个节点,只能由一个前驱(child node),可以有多个后继(parent node),且后继结点没有交集。没有前驱的成为根结点,没有后继的成为叶子节点。
树的递归定义:任何一颗非空的树中,有一个特殊的结点t∈D,称为根节点。其余节点可分为m个不相交的子集,成为子树。
基本名词术语:
1.结点的度:子树数目
2.树的度:max{结点的度}
3.分支节点:度非0的结点(非叶子节点)
4.树的深度/高度:最大层次
5.路径:路径长度=经过结点数-1
6.有序性:子树的顺序不能改变(线性表的有序性也是这样:顺序不能改变,而不是数学上的有序单调)
储存结构:
1.顺序存储:对于完全二叉树比较合适,对于非完全二叉树有浪费。对于退化二叉树有极大的空间浪费。
2.链式存储:
2.1.多重链表:k=树的度,每个结点k个指针域 方便但是有空间浪费
2.2.不定长结点的多重链表:k=该结点的度 不浪费空间,但是动态操作麻烦
二叉树:最多有两个子树,且严格区分左右子树(即使只有一颗子树,也要确定这个是左还是右,这一点比普通的度为2的有序树要严格)
满二叉树:叶节点集中在最下面一层
完全二叉树:只有最下面两层结点度可以小于2,且最下面一层的结点依次排列(也就是说除了最后一层都是满的,然后最后一层的全在左边,如果是数组存储的话就不会留空)
二叉树的性质:
1.具有n个结点的树右n-1个分支:除了根节点,每个节点有两个分支,所以有2*(n-1)/2=n-1个分支
2.非空二叉树第i层最多有2^(i-1)个结点:等比数列求和即可
3.深度为h的树最多有2^h - 1个结点:同上,等比求和 且在改树为完全二叉树时取最大值
4.若非空二叉树有n0个叶子结点,n2个度为2的结点 则n0=n2 + 1
5.具有n个结点的完全二叉树的深度为 h = [log(n)]-1 这也是所有树二叉树深度最小的情况
6.按照从上到下,从左往右的按层编号方法:
若从1开始编号
对于编号为i的结点,其双亲结点编号为[i/2],左孩子为2i,右孩子为2i+1(前提是这个值不超过n,若这个值超过n则不存在左/右子树)
若从0开始编号
对于编号为i的结点,其双亲结点编号为[(i-1)/2],左孩子为2i+1,右孩子为2i+2(前提是这个值不超过n,若这个值超过n则不存在左/右子树)
二叉树的基本操作:
1.创建
创建一个二叉查找树BST:
typedef struct node{
ElemType data;
struct node* left;
struct node* right;
}Node;
typedef struct tree{//指向一棵树
Node *root;
}Tree;
void insert(Tree *tree,int item){//创建一棵树 和插入结点都在同一个函数
Node *node=(Node*)malloc(sizeof(Node));
node->data=item;
node->left=NULL;
node->right=NULL;
if(tree->root==NULL){//创建树的情况
tree->root=node;
}
else{//插入结点的情况
Node *temp = tree->root;//从树根开始遍历
while(temp!=NULL){
if(item<temp->data){//小于的情况:左树
if(temp->left==NULL){//找到左叶子
temp->left=node;
return;
}
else{
temp = temp->left;
}
}
else{//大于等于:右孩子
if(temp->right==NULL){
temp->right=node;
return;
}
else{
temp = temp->right;
}
}
}
}
return ;
}
2.求根结点 略
3.求双亲结点
方法1
ode *search_parent(Tree *BSTree,ElemType item){//先层次遍历储存到queue中,然后遍历queue,时间复杂度高
if(BSTree->root->data==item) return NULL;
for(int i=0;i<max_node_num;i++) queue[i]=NULL;
layerorder(BSTree);
for(int i=0;queue[i]!=NULL;i++){
if(queue[i]->left->data==item) return queue[i];
if(queue[i]->right->data==item) return queue[i];
}
}
方法2
这个方法也可以用来查找某个结点
void search_parent(Node *node,Node *address,ElemType item){//递归 时间复杂度看情况在log n到n之间 ,虽然递归不好定位,但是可以指针传参
if(node->left->data==item||node->right->data==item){
address->data = node->data;
return ;
}
else if(item > node->data){//在右子树
search_parent(node->right,address,item);
return ;
}
else if(item < node->data){//在左子树
search_parent(node->left,address,item);
return ;
}
}
/*注意使用的时候:
int e;
scanf("%d",&e);
Node parent;
search_parent(BSTree->root,&parent,e);
printf("%d",parent.data);
*/
4.求孩子节点 略
5.删除左 /右子树 略
6.遍历二叉树
三种DFS使用递归即可
BFS需要使用队列
Node *queue[max_node_num];
void layerorder(Tree *tree){//层次遍历二叉树,并且储存到queue
int front=0,rear=0;
queue[0]=tree->root;
Node *p;
while(front<=rear){
p=queue[front];
front++;
if(p->left!=NULL){
rear++;
queue[rear]=p->left;
}
if(p->right!=NULL){
rear++;
queue[rear]=p->right;
}
}
}
由两个序列生成二叉树
1.前序遍历和中序遍历:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct binarytree//定义节点
{
char val;
struct binarytree *left, *right;
} bitree;
char pre[100];
char in[100];//存储序列
void BAK(bitree *r)//后序遍历(蹩脚英语写的脑瘫名字)
{
if (r->left != NULL)
BAK(r->left);
if (r->right != NULL)
BAK(r->right);
printf("%c", r->val);
}
bitree *constructcore(int prel, int prer, int inl, int inr)//创建树,变量分别为先序的起点终点,中序的起点终点
{
bitree *root = (bitree *)malloc(sizeof(bitree));
root->val = pre[prel];//先序遍历的第一个为根
root->left = NULL;
root->right = NULL;//一定要记得初始化 防止出现野指针
if (inl == inr)
return root;//如果长度为一 没有子树 直接返回
int rootin;
for (rootin = inl; rootin <= inr; rootin++)//寻找根在中序遍历中的位置
{
if (in[rootin] == root->val)
{
break;
}
}
int l_len = rootin - inl;
int r_len = inr - rootin;//分别求左右子树长度:一是判断是否存在,二是给子树遍历的起止定位
if (l_len > 0)//注意上方只判断了有没有子树,并没有分别判断左右子树是否存在,还要用长度判断一下
root->left = constructcore(prel + 1, prel + l_len, inl, rootin - 1);//递归左子树
if (r_len > 0)
root->right = constructcore(prel + l_len + 1, prer, rootin + 1, inr);//递归右子树
return root;
}
int main()
{
int n;
scanf("%s", in);
//getchar();
scanf("%s", pre);
//puts(in);puts(pre);
n = strlen(in);
//printf("%d",n);
bitree *tree = constructcore(0, n - 1, 0, n - 1);
BAK(tree);
}
后序遍历和中序遍历求子树:
仿照前面了,只是要把先序遍历改成后序遍历——即根节点到了最后一个,左右子树在序列中的位置对换一下即
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct binarytree
{
char val;
struct binarytree *left, *right;
} bitree;
char post[100];
char in[100];
bitree *constructcore(int postl, int postr, int inl, int inr)
{
bitree *root = (bitree *)malloc(sizeof(bitree));
root->val = post[postr];
root->left = NULL;
root->right = NULL;
if (inl == inr)
return root;
int rootin;
for (rootin = inl; rootin <= inr; rootin++)
{
if (in[rootin] == root->val)
{
break;
}
}
int l_len = rootin - inl;
int r_len = inr - rootin;
if (l_len > 0)
root->left = constructcore(postl, postl + l_len - 1, inl, rootin - 1);
if (r_len > 0)
root->right = constructcore(postl + l_len, postr - 1, rootin + 1, inr);
return root;
}
void pre_traverse(bitree *p)
{
printf("%c", p->val);
if (p->left)
pre_traverse(p->left);
if (p->right)
pre_traverse(p->right);
}
int main()
{
int n;
scanf("%s", in);
//getchar();
scanf("%s", post);
//puts(in);puts(pre);
n = strlen(in);
//printf("%d",n);
bitree *tree = constructcore(0, n - 1, 0, n - 1);
pre_traverse(tree);
}
7.求某个结点的层
8.求深度
使用递归
int get_height(Node *node){//求树高
if(node==NULL) return 0;
return 1 + max(get_height(node->left),get_height(node->right));
}
9.销毁树
10.删除某一个结点 若任然要保持其排序的特性 则需要
10.1 删除叶节点 直接删除
10.2 若无左(右)子树 则用右(左)子树的根代替被删除的节点
10.3 若左右子树都存在 则用右子树中值最小的结点(或者左子树中值最大的结点)代替被删除的结点
10# lazy deletion:任然保留结点 只是做一个记号表明其被删除 通常用于删除数量不多的情况
在图中也可以使用此来进行逻辑删除 删除点只需将其所有边的权值变为0即可
线索二叉树
1.结点结构
struct node{
ElemType data;
struct node *left,*right;
char lb,rb;//0表示指向直接前驱/后继 1表示指向左/右孩子
};
2.DFS中序遍历(不使用递归)
TBTNodeptr insucc(TBTNodeptr x){//确定直接后继
TBTNodeptr s;
s = x->right;
if(x->rb==1){
while(s->lb==1) s=s->left;
}
return s;
}
void t_order(TBTNodeptr head){
TBTNodeptr p=head;
while(1){
p=insucc(p);
if(p==head) break;
VISIT(p);
}
}
3.建立一颗线索二叉树(中序)
中序遍历二叉树时
prior为前一次访问的结点 p为当前访问的结点
若左指针域空 则让其指向prior
若prior的右指针域为空 则指向p
这里的prior使用全局变量 方便操作
//prior是一个全局变量,初始时指向根节点
void inThreading(TBTNodeptr p){
if(p==NULL) return;//递归结束的条件
inThreading(p->left);//由于是中序 先对左子树线索化
//然后对p线索化
if(p->left==NULL){
p->lb=0;
p->left=prior;
}
else p->lb = 1;
if(prior->right==NULL){
prior->rb=0;
prior->right=p;
}
else prior->rb=1;
prior=p;
inThreading(p->right);
}
平衡二叉树
二叉树的形态可能比较随意 如退化二叉树 也就是说左右树深度差很大
而平衡二叉树的任意一个节点的左右子树 深度差距不超过1