考研408 王道 数据结构 算法题整理(三)树

本文介绍了树的不同表示方法,包括孩子兄弟表示法、双亲表示法和左右孩子表示法。详细阐述了二叉树的递归与非递归遍历策略,如前序、中序、后序以及层次遍历。同时,讨论了完全二叉树、二叉排序树、平衡二叉树和哈夫曼树的概念。此外,还涉及并查集的基本操作及其优化,如路径压缩和小树合并到大树的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3.1.1 树的孩子兄弟结点表示法

firstchild是指向该节点第一个儿子节点的指针,nextbro是指向下一个兄弟节点的指针

typedef struct TNode{
     Elemtype  data;                     //每个节点的权值
     struct TNode *firstchild, *nextbro;
}TNode;

3.1.2 树的双亲表示法

树和森林(节点用双亲表示法存储,常用于并查集)
一般采用数组表示,data数组保存每个节点的权值,father数组保存每个节点的父亲节点在数组中的下标:

//顺序存储(一般使用)
#define MAXSIZE 100
typedef struct PNode{
    int value;              //该结点的值
    int parent;             //伪指针,用于指向双亲结点(双亲结点的数组下标)
}PNode;
typedef struct PTree{
    PNode node[MAXSIZE];    //申明一片PNode类型个数为MAXSIZE的数组用于存储树
    int n;                  //表示该树中的结点个数
}PTree;

//链式存储
typedef struct TNode{
	Elemtype  data;        //每个节点的权值
	struct TNode *father;  //father是指向该节点父亲节点的指针
}TNode;                    //树节点

3.1.3 二叉树的左右孩子结点表示法

typedef struct BTNode{
	Elemtype  data;                  //每个节点的权值
	struct BTNode *lchild,*rchild;   //指向左右孩子节点的指针
}BTNode, *BiTree;                    //二叉树节点

3.3.1 二叉树的递归遍历(先序、中序、后序)

//前序遍历
void PreOrder(BiTree T){
	if(T==NULL) return;
	visit(T);
	PreOrder(T->lchild);
	PreOrder(T->rchild);
} 
//中序遍历
void InOrder(BiTree T){
	if(T==NULL) return;
	InOrder(T->lchild);
	visit(T);
	InOrder(T->rchild);
}
//后序遍历
void PostOrder(BiTree T){
	if(T==NULL) return;
	PostOrder(T->lchild);
	PostOrder(T->rchild);
	visit(T);
}

3.3.2 树(二叉树)的层次遍历

//层序遍历
void LevelOrder(BiTree T){
	InitQueue(Q);
	BTNode *p;
	EnQueue(Q,T); //根节点入队 
	while(!IsEmpty(Q)){
		DeQueue(Q,p); //队头元素出队 
		visit(p);
		if(p->lchild!=NULL) EnQueue(Q,p->lchild);
		if(p->rchild!=NULL) EnQueue(Q,p->rchild);
	}
} 

3.3.3 完全二叉树、二叉排序树、平衡二叉树、哈夫曼树的定义和判定

1. 完全二叉树
1)当且仅当其每个结点都与高度为 h 的二叉树中编号为1 - n 的节点一一对应
2)只有可能有一个度为1的叶子结点(在倒数第二层)
3)叶子结点出现在最后两层
4)按层序编号,根节点为1,则结点 i 的左孩子为2i,右孩子为2i + 1,父结点为⌊ i / 2⌋
5)i ≤ ⌊n/2⌋为分支节点 i > ⌊n/2⌋为叶子结点

2. 二叉排序树
1)左子树结点值 < 根结点值 < 右子树结点值,且它的左右子树又递归的满足这一特性
2)对二叉排序树进行中序遍历,得到的序列是递增的有序序列

typedef struct BSTNode{
    elemtype data;
    struct BSTNode *rchild, *lchild;
}BSTNode, *BSTree;

3. 平衡二叉树
1)树上任意一个结点的左右子树的深度差不超过1
2)平衡二叉树具有更高的搜索效率

4. 哈夫曼树
1)哈夫曼树:n个结点形成的所有二叉树中,wpl值最低的树(也称为最优二叉树)
2)树的带权路径长度(WPL):所有叶结点的带权路径长度之和

3.3.4 二叉树的非递归遍历

//中序遍历的非递归实现
void InOreder2(BiTree T){
	InitSatck(S); BTNode *p=T;
	while(p||!IsEmpty(S)){
		if(p){
			Push(S,p);
			p=p->lchild;
		}
		else{
			Pop(S,p); visit(p);
			p=p->rchild;
		}
	}
} 
//前序遍历的非递归实现
void PreOrder2(BiTree T){
	InitStack(S); BTNode *p=T;
	while(p||!IsEmpty(S)){
		if(p){
			visit(p);
			Push(S,p);
			p=p->lchild;
		}
		else{
			Pop(S,p);
			p=p->rchild;
		}
	}
}
//后序遍历的非递归实现(也可在节点中增加一个标志域,记录是否已被访问)
void PostOrder2(BiTree T){
	InitStack(S); BTNode *p=T,r=NULL;
	while(p||!IsEmpty(S)){
		if(p){
			Push(S,p);
			p=p->lchild;
		}
		else{
			GetTop(S,p);
			if(p->rchild && p->rchild!=r) p=p->rchild;
			else{
				pop(S,p); visit(p->data);
				r=p; p=NULL;
			}
		}
	}
}

3.3.5 线索二叉树的遍历

//求中序线索二叉树中中序遍历下的第一个节点
ThreadNode *Firstnode(ThreadNode *p){
	while(p->ltag==0) p=p->lchild;
	return p;
} 
//求中序线索二叉树中节点p在中序遍历下的后继
ThreadNode *Nextnode(ThreadNode *p){
	if(p->rtag==0) return Firstnode(p->rchild);
	else return p->rchild;
} 
//中序线索二叉树的中序遍历
void InOrder(ThreadNode *T){
	for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p)) visit(p);
} 

3.4.1 P149.8 计算二叉树中双分支结点的个数

int num=0;
void CountDegreeTwo(BiTree T){
	if(T==NULL) return;
	if(T->lchild!=NULL&&T->rchild!=NULL) num++;
	InOrder(T->lchild);
	InOrder(T->rchild);
}

3.4.2 P149.9 交换二叉树中所有左右子树

void ChangeLeftRight(BiTree T){
	if(T){
		ChangeLeftRight(T->lchild);
		ChangeLeftRight(T->rchild);
		temp=T->lchild;
		T->lchild=T->rchild;
		T->rchild=temp;
	}
}

3.4.3 P149.10 求先序遍历第k个元素

int num=0;
int PreOrder(BiTree T,int k){
	if(T==NULL) return -11111; //若为空节点,返回特殊值 
	num++;
	if(k==num) return T->data;
	PreOrder(T->lchild);
	PreOrder(T->rchild);
}

3.4.4 P149.11 删去值为x的子树

//前序遍历(注意要判断的是左孩子和右孩子是否等于x,这样才能删除)
void PreOrder(BiTree T,int x){
	if(T==NULL) return;
	if(T->lchild){
		if(T->lchild->data==x){
			DeleteXTree(T->lchild);
			T->lchild=NULL;
		}
	}
	if(T->rchild){
		if(T->rchild->data==x){
			DeleteXTree(T->rchild);
			T->rchild=NULL;
		}
	}
	PreOrder(T->lchild);
	PreOrder(T->rchild);
} 

3.4.5 P150.13 查找二叉树中两个结点的公共祖先结点

后序遍历最后访问根结点,即在递归算法中,根是压在栈底的。本题要找p和q的最近公共祖先结点r,不失一般性, 设p在q的左边。
算法思想:**采用后序非递归算法,栈中存放二叉树结点的指针,当访问到某结点时,栈中所有元素均为该结点的祖先。**后序遍历必然先遍历到结点p,栈中元素均为P的祖先。先将栈复制到另一辅助栈中。 继续遍历到结点q时,将栈中元素从栈顶开始逐个到辅助栈中去匹配,第一个匹配(即相等)的元素就是结点P和q的最近公共祖先。

↓顺序存储,编号分别为p和q的节点的公共祖先节点

#define MAXSIZE 100
typedef struct treeNode{
    elemType value;
    bool isEmpty;
}treeNode;
 
treeNode T[MAXSIZE];
 
elemType commonParent(treeNode T, int p, int q){
    //寻找公共结点
    while (p != q){
        //谁大谁找双亲结点
        if (p > q) p /= 2;
        else q /= 2;
    }
    return T[p].value;
}

3.4.6 P150.14 求二叉树的宽度

采用层次遍历的方法求出所有节点的层次,并将所有节点和对应的层次放在一个队列中,然后通过扫描队列求出各层的节点总数。

int MaxWidth(BiTree bt){
	if(bt==NULL) return 0;
	BiTree Q[]; BTNode *p;
	front=1; rear=1; //队头,队尾 
	last=1; //同层最右节点在队列中的位置 
	tmpe=0; maxw=0; //局部宽度,最大宽度 
	Q[rear]=bt; //根节点入队 
	while(front<=last){
		p=Q[front++]; temp++;
		if(p->lchild!=NULL) Q[++rear]=p->lchild;
		if(p->rchild!=NULL) Q[++rear]=p->rchild;
		if(front>last){ //一层结束 
			last=rear;
			if(temp>maxw) maxw=temp; //更新最大宽度 
			temp=0;
		}
	}
	return maxw;
}

3.5 并查集的应用

3.5.1 写代码:定义一个并查集(用长度为n的数组实现)

#define SIZE 13
int UFSets[SIZE];

//初始化并查集
void Initial(int S[]){
	for(int i=0;i<SIZE;i++){
		S[i]=-1;
	}
} 

3.5.2 基于上述定义,实现并查集的基本操作—— 并 Union

//Union 并操作,将两个集合合并为一个
void Union(int S[], int Root1, int Root2){
	if(Root1==Root2) return; //要求两个根属于不同的集合(不是同一棵树)
	S[Root2]=Root1; //Root2的父节点是Root1 
}  //时间复杂度为O(1)

3.5.3 基于上述定义,实现并查集的基本操作—— 查 Find

//Find 查操作,找x所属集合(返回x所属根节点的下标)
int Find(int S[], int x){
	while(S[x]>=0) //循环寻找根 
		x=S[x];
	return x; //最坏时间复杂度为O(n) 
} 

3.5.4 并查集的优化(Union操作优化——小树合并到大树)

优化思路:在每次Union操作构建树的时候,尽可能让树不长高

  1. 用根节点的绝对值表示树的结点总数
  2. Union操作:让小树合并到大树
//Union优化:小树合并到大树
void Union(int S[], int Root1, int Root2){
	if(Root1==Root2) return;
	if(S[Root2]>S[Root1]){ 
		//S[Root1]小,说明Root1所连结点较多,树高,应让Root2的父节点设置为Root1 
		S[Root1]+=S[Root2]; //累加结点总数 
		S[Root2]=Root1; //小树合并到大树 
	}
	else{
		S[Root2]+=S[Root1];
		S[Root1]=Root2;
	}
} 

Union操作优化后Find操作最坏时间复杂度变为O(logn)
(该方法构造的树高不超过[logn]+1) []向下取整

3.5.5 并查集的优化(Find操作优化——压缩路径)

优化思路:先找到根节点,再将查找路径上的所有结点都挂到根节点下(从下往上挂节点)

int Find(int S[], int x){
	int root=x;
	while(S[root]>=0) root=S[root]; //循环找到根 
	while(x!=root){
		int t=S[x]; //t指向x的父节点     (t保存x的爸爸) 
		S[x]=root;  //x直接挂到根节点下  (x的爸爸变为根root) 
		x=t;		//                  (x更新为自己的爸爸t,继续循环) 
	}
	return root;    //返回根节点编号 
} 

压缩路径后树高不会超过O(α(n)) 可使时间开销降低

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nabobess

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值