数据结构学习--第五章

二、树的基本概念

树是一种逻辑结构

基本术语

在这里插入图片描述

  1. 祖先:根A到结点K的唯一路径上的任意结点。(K的祖先:A、B、E)
  2. 子孙:若B是K的祖先,则K为B的子孙。(B的子孙:E、F、K、L)
  3. 双亲:路径上最接近K的结点。(K的双亲:E根节点A无双亲)
  4. 孩子:E为K的双亲,K为E的孩子。(E的孩子:K、L)
  5. 兄弟:结点K和结点L有相同的双亲E,既K和L为兄弟。(K与L为兄弟)
  6. 堂兄弟:同一层结点(E、F、G、H、I、J为堂兄弟)

这里是引用

  1. 结点的度:一个结点的孩子个数。(A的度为3)
  2. 树的度:树中结点的最大度数。
  3. 分支结点(非终端节点):度大于0的结点
  4. 叶子结点(终端结点):度为0的结点

这里是引用

  1. 结点的层次:从根节点开始定义,根节点为第一层。(一共四层结点)
  2. 结点深度:从根节点开始自顶向下组成累加。
  3. 结点的高度:从叶节点开始自底向上组层累加。
  4. 树的高度(或深度):树中结点的最大层数(图中树的高度为4)

1.有序树(无序树):树中结点各子树从左到右是有次序(无序)的,不能互换(可互换)。
2. 路径:两个结点之间所经过的结点序列构成的。(树中的路径是从上往下的)
3. 路径长度:路径上所经过边的个数。

树的性质

  1. 结点数 = 总度数+1 (加的1为根节点)、
  2. 度为m的树、m叉树 的区别在这里插入图片描述
  3. 度为m的树第 i 层至多有mi-1
    m叉树第i层至多有**mi-1**个结点
    在这里插入图片描述
  4. 高度为h的m叉树至多有 (mh-1)/(m-1)
    1+m+m2+……+mn
    在这里插入图片描述
  5. 高度为h的m叉树至少有h个结点,高度为h、度为m的树至少有h+(m-1)个节点
    在这里插入图片描述
  6. 具有n个结点的m叉树的最小高度为logm(n(m-1)+1)【向上取整】

每一层都有孩子
(mh-1-1)/(m-1) < n ≤ (mh-1)/(m-1)
mh-1-1 < n/(m-1) ≤ mh-1
h-1< logm[n(m − 1) + 1] h
𝑚𝑖𝑛 = logm[n(m − 1) + 1]

二、二叉树

二叉树的定义及其主要特征

特点:

  1. 每个结点至多只有棵子树,并且二叉树的子树有左右之分
  2. 二叉树的五种形态
    (a)空二叉树 (b)只有根节点 ©只有左子树 (d)左右子树都有 (e)只有右子树
  3. 二叉树和度为2的有序树区别:
    (1)度为2的树至少有三个结点,二叉树可以为空
    (2)度为2的树的孩子的左右次序是相对于另一孩子而言的,若某个结点只有一个孩子,则无序区分左右。二叉树的结点次序不是相对于另一结点而言的。

几个特殊的二叉树

满二叉树

一颗高度为h,且含有2h-1个结点的二叉树。
在这里插入图片描述

特点:

  1. 只有最后一层有叶子结点
  2. 不存在度为1的结点
  3. 按层序序号为1开始编号,结点 i 的左孩子为 2i,右孩子为2i+1;结点i的父节点为 [i/2]向下取整

完全二叉树

每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应
在这里插入图片描述

特点:

  1. 只有最后两层可能有叶节点
  2. 最多只有一个度为1的结点
  3. 按层序序号为1开始编号,结点 i 的左孩子为 2i,右孩子为2i+1;结点i的父节点为 [i/2]向下取整
  4. i ≤ n/2 为分支结点,i>n/2 为叶子节点(向下取整)
  5. 如果某结点只有一个孩子,那么一定是左孩子

(若对二叉树的根结点从0开始编号,则相应的i号结点的双亲结点的编号为(i-1)/2,左孩子的编号为2i+1,右孩子的编号为2i+2。)

二叉排序数

定义:(左小右大)
左子树上所有结点的关键字小于根结点的关键字;
右子树上所有结点的关键字大于根结点的关键字。
左子树和右子树又各是一棵二叉排序树

平衡二叉树

树上任一结点的左子树右子树深度只差不超过1

二叉树的性质

  1. 非空二叉树上的叶子结点树等于度为2的结点数+1(n0 = n2 + 1)

n = n0 + n1 + n2
n = n1 + 2n2 +1
→n0 = n2+1

  1. 二叉树第 i层至多有2i-1个结点
    m叉树第i层至多mi-1个结点在这里插入图片描述
  2. 高度为h的二叉树至多有2h-1个结点在这里插入图片描述
  3. 具有n个(n>0)结点的完全二叉树高度为log 2 (n + 1)【向上取整】
  4. 第i个结点所在层次为log 2 (n + 1)【向上取整】
  5. 完全二叉树最多只有一个度为1的结点,即n1 =0或1

n0 = n2 + 1 → n0 + n2一定是奇数

若完全二叉树有2k(偶数)个结点,则必有n1 = 1,n0 =k,n2 =k-1
若完全二叉树有2k-1个(奇数)个结点,则
必有n1 = 0,n0 =k,n2 =k-1

二叉树的存储结构

顺序存储

用一组地址连续的存储单元依次自上而下、自左到右存储完全二叉树上的结点元素。

#define MaxSize 100
struct TreeNode{
	ElemType value;
	bool isEmpty;
};
TreeNode t[MaxSize];

在这里插入图片描述
二叉树的顺序存储结构,只适合存储完全二叉树。
二叉树的顺序存储中,一定要把二叉树的结点编号与完全二叉树对应起来

链式存储

typedef struct BiTNode{
	ElemType data;//数据域
	struct BiTNode *lchild,*rchild;
	//左指针域,右指针域
}BiTNode,*BiTree;

n个结点的二叉链表共有n+1个指针域
【每个结点贡献两个空指针,共2n个,指向n-1(除根节点)个结点,还剩下2n-(n-)=n+1 】
在这里插入图片描述

扩展:
三叉链表

typedef struct BiTNode{
	ElemType data;//数据域
	struct BiTNode *lchild,*rchild;
 	struct BiTNode *parent;//父节点指针
}BiTNode,*BiTree;

方便找父亲

三、二叉树的遍历

递归

  1. 先序遍历(PreOrder)【左右】
    第一次路过时访问结点
void PreOrder(BiTree T){
	if(T!=NULL){
		visit(T);
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	}
}
  1. 中序遍历(InOrder)【左右】
    第二次路过时访问结点
void InOrder(BiTree T){
	if(T!=NULL){
		InOrder(T->lchild);
		visit(T);
		InOrder(T->rchild);
	}
}
  1. 后序遍历(PostOrder)【左右
    第二次路过时访问结点
void InOrder(BiTree T){
	if(T!=NULL){
		InOrder(T->lchild);
		InOrder(T->rchild);
		visit(T);
	}
}
  1. 每个结点都只访问一次:时间复杂度:O(n)
  2. 在递归遍历中,递归栈的栈深恰好为树的深度,最坏情况下,二叉树是有n个结点且深度为n的单只树遍历算法的空间复杂度为O(n)

非递归

  1. 中序遍历
    、沿着根的左孩子,依次入栈,直到左孩子为空,转去执行②
    、栈顶元素出栈并访问:若其右孩子为空,继续执行②;若其右孩子不空,将右子树转执行①
    在这里插入图片描述
  1. 沿着根A、B、D依次入栈; (底)【A B D】(顶) 【】
  2. D左孩子为空,栈顶D出栈并访问;(底)【A B 】(顶) 【D】
  3. D右孩子不空,右孩子G入栈并查看其左孩子; (底)【A B G】(顶) 【D】
  4. G左孩子为空,G出栈并访问;(底)【A B 】(顶) 【D G】
  5. G右孩子为空,栈顶B出栈访问;(底)【A 】(顶) 【D G B】
  6. B右孩子不空,E入栈并查看其左孩子;(底)【A E】(顶) 【D G B】
  7. E左孩子为空,栈顶E出栈并访问;(底)【A 】(顶) 【D G B E】
  8. E右孩子为空,栈顶A出栈并访问;(底)【】(顶) 【D G B E A】
  9. A右孩子不空,C入栈并查看其左孩子;(底)【C】(顶) 【D G B E A】
  10. C左孩子F入栈,并查看F左孩子(底)【C F】(顶) 【D G B E A】
  11. F左孩子为空,栈顶F出栈并访问;(底)【C】(顶) 【D G B E A F】
  12. F右孩子为空,栈顶C出栈并访问;(底)【】(顶) 【D G B E A F C】

入栈看左指针,出栈看右指针

void InOeder2(BiTree){
	InitStack(S);BiTree p = T;
	while(p||!IsEmpty(S)){//当栈不空或p不空时循环
		if(p){
			Push(S,p);
			p=p->lchild;
		}else{
			Pop(S,p);visit(p);//访问
			p=p->rchild;
		}
	}
}
  1. 先序与中序类似
void PreOeder2(BiTree){
	InitStack(S);BiTree p = T;
	while(p||!IsEmpty(S)){//当栈不空或p不空时循环
		if(p){
			visit(p);Push(S,p);//访问
			p=p->lchild;
		}else{
			Pop(S,p);
			p=p->rchild;
		}
	}
}
  1. 后序遍历
    ① 、 沿着根的左孩子,依次入栈,直到左孩子为空。
    ② 、 读栈顶元素:若其右孩子不空且为被访问过,将右子树转执行①;否则栈顶元素出栈并访问。
void PostOrder(BiTree T){
	InitSack(S);
	BiTree 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;//右子树存在且未被访问
			//右子树都是顺都是按右下到左上依次出栈,r指针也从右下依次记录到左上
			else {
				pop(S,p);
				visit(p->>data);
				r=p;//记录最近访问过的结点
				p=NULL;
			}
		}//else
	}//while
}

二叉树的遍历(手算练习)

在这里插入图片描述
在这里插入图片描述红色:先序遍历
绿色:中序遍历
紫色:后续遍历

扩展:
二叉树的应用(求树的深度)

int treeDepth(BiTree T){
	if(T==NULL){return 0;}
	else {
		int r = treeDepth(T->rchild);
		int l = treeDepth(T->lchild);
		//树的深度=MAX(左子树深度,右子树深度)+1
		return l>r? l+1 : r+1;
	}
}

二叉树的层次遍历

算法思想:

  1. 初始化一个辅助队列
  2. 根结点入队
  3. 若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(如果有的话)
  4. 重复 3 直至队列为空
void LevelOrder(BiTree T){
	InitQueue(Q);
	EnQueue(Q,T);
	BiTree p = NULL;
	while(!IsEmpty(Q)){
		DeQueue(Q,p);
		if(p->lchild!=NULL) EnQueue(Q,p->lchild);
		if(p->rchild!=NULL) EnQueue(Q,p->rchild);
		//入的是结点指针,而非结点本身
	}
}

遍历序列构造二叉树

必定包含中序

  1. 先序序列+中序序列唯一确定一颗二叉树
  2. 后序序列+中序序列唯一确定一颗二叉树
  3. 层序序列+中序序列唯一确定一颗二叉树(最先出现的充当root)

四、线索二叉树

  1. 引入线索二叉树正是为了加快查找结点前驱和后继的速度(中序/先序/后序 前驱/后续)
  2. 利用二叉树中n+1个空指针域
  3. 将二叉树中的空指针改为指向前驱或后续的线索

规定:
若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其前驱结点;还需加两个标制域标识指针是指向孩子还是指向前驱。

在这里插入图片描述

typedef struct ThreadNode{
	ElemType data;
	struct ThreadNode *lchild,*rchild;
	int ltag,rtag;
}ThreadNode,* ThreadTree;

中序线索二叉树的构造

附设pre指针指向刚刚访问过的结点,指针p指向正在访问的结点,既pre指向p的前驱。在中序遍历中,检查p的左指针是否为空,若为空就将他指向pre;检查pre右指针是否为空,若为空就将它指向p
递归算法

void InThread(ThreadTree &p,ThreadTree &pre){
	if(p!=NULL){
		InThread(p->lchild);//线索化右子树
		//线索化当前结点
		if(p->lchild==NULL){
			p->lchild = pre;
			p->ltag = 1;
		}
		if(pre!=NULL&&pre->rchild==NULL){
			pre->rchild = p;
			p->rlag = 1;
		}
		pre = p;
		InThread(p->rchild);//线索化左子树
	}
}
void CreateInTread(ThreadTree T){
	ThreadTree pre = NULL;
	if(T!=NULL){
		InThread(T,pre);
		pre->rchild = NULL;
		pre->rtag = 1;
		//最后一个肯定无右孩子
	}
}

后续线索二叉树的构造

void PostThread(ThreadTree &p,ThreadTree &pre){
	if(p!=NULL){
		InThread(p->lchild);//线索化右子树
		InThread(p->rchild);//线索化左子树
		visit(pre,p);
	}
}
void visit(ThreadTree &pre,ThreadTree &p){
			//线索化当前结点
		if(p->lchild==NULL){
			p->lchild = pre;
			p->ltag = 1;
		}
		if(pre!=NULL&&pre->rchild==NULL){
			pre->rchild = p;
			p->rlag = 1;
		}
		pre = p;
}
void CreateInTread(ThreadTree T){
	ThreadTree pre = NULL;
	if(T!=NULL){
		InThread(T,pre);
		if(pre->rchild = NULL)pre->rtag = 1;
	}
}

先序线索二叉树的构造

与中序线索或和后续线索化的区别:

  1. 中序:左根右。
    根结点左结点的线索化在左结点遍历之后(根节点的线索化不会影响遍历);根节点右节点的线索化在右节点遍历之后(在本轮的下一轮才会修改)。在这里插入图片描述

  2. 后序:左右根
    根节点的线索化在左右节点遍历之后,不影响遍历。

  3. 先序:根左右
    根节点左结点的线索化会在左结点遍历之前,在遍历左结点之前需要判断是否是前驱线索根节点右节点的线索化在右节点遍历之后(在本轮的下一轮才会修改)。

void PreThread(ThreadTree &p,ThreadTree &pre){
	if(T!=NULL){
			visit(p,pre);
			if(p->ltag==0) PreThread(T->lchild);
			//lchild不是前驱线索
			PreThread(p->rchild);
	}
}
void visit(ThreadNode *p,ThreadNode *pre){
	if(p->lchild==NULL){
		p->lchild = pre;
		p->ltag = 1;
	}
	if(pre!=NULL&&pre->rchild==NULL){
		pre->rchild = p;
		pre->rtag = 1;
	}
	pre = p;
}
void CreatPost(ThreadTree T){
	pre = NULL;
	if(T!=NULL){
		PostThtrad(T);
		if(pre->rchild==NULL) pre->rtag = 1;
	}
}

五、线索二叉树的遍历

中序线索二叉树

中序线索二叉树的遍历

中序线索二叉树找中序后续

中序线索二叉树中寻找结点后续的规律:

  1. 若右标制为“1”,则右链为线索
  2. 否则遍历右子树中第一个访问节点的结点(右子树的最左下结点)为其后续
    在这里插入图片描述
  1. 找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNNode *p){
//循环找的最左下的结点
	while(p->ltag == 0)p=p->lchild;
	return p;
}
  1. 在中序线索:二叉树中找到结点p的后继结点
ThreadNode *NextNode(ThreadNode *p){
	//右子树最左下节点
	if(p->rtag==0) return Firstnode(p->rchild);
	else return p->rchild;
}
  1. 对中序线索二叉树进行中序遍历(利用线索实现的非递归算法)
void Inorder(ThreadNode *T){
	for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p)){
		visit(p);
	}
}

空间复杂度O(1)

中序线索二叉树找中序前驱

在这里插入图片描述
左子树的最右下结点

  1. 找到以p为根的子树中,最后一个被中序遍历的结点
Node *Lastnode(ThreadNode *p){
	//循环找到最右下结点(不一定是叶结点)
	while(p->rtag==0)p=p->rchild;
	return p;
}

  1. 在中序线索二叉树中找到结点p的前驱结点
ThreadNode *Prenode(ThreadNode *p){
	//左子树最右下结点
	if(p->ltag ==0 )return Lastnode(p->lchild);
	else return p->lchild;//ltag==1直接返回线索
}
  1. 对中序线索二叉树进行逆向中序遍历
void RevInorder(ThreadNode *T){
	for(ThreadNode *p=Lastnode(T);p!=NULL;p=Prenode(p)){
		visit(p);
	}
}

先序线索二叉树

先序线索二叉树找后续

在这里插入图片描述

先序线索二叉树找先序前驱

左右子树中的结点只有可能是根的后继不可能是前驱

三叉链表找前驱:

  1. 若能找到p的父节点,且p是左孩子。
    p的父节点既其前驱
    在这里插入图片描述

  2. 若能找到p的父节点,且p是右孩子,其左兄弟为空。
    在这里插入图片描述
    p的父节点既为其前驱

  3. 若能找到p的父节点,且p是右孩子,其左兄弟非空。
    在这里插入图片描述
    p的前驱为左兄弟子树最后一个被先序遍历的结点

  4. 若p是根节点,则p无前驱

后序线索二叉树

后序线索二叉树找后序前驱

在这里插入图片描述

后序线索二叉树找后序后继

左右根:后序遍历中,左右子树中的结点只可能是根的前驱不可能是后继

用三叉链表找后继

  1. 若能找到p的父节点。且p是右孩子。
    在这里插入图片描述
    p的父节点即为其后续

  2. 若能找到p的父节点,且p是左孩子,其右兄弟为空。
    在这里插入图片描述
    p的父节点即为其后继

  3. 若能找到p的父节点,且p是左孩子,其右兄弟非空。
    在这里插入图片描述
    p的后继为右兄弟子树中最后一个被后序遍历的结点

  4. p是根节点,则p没有后序后继

总结

在这里插入图片描述
在这里插入图片描述

六、树、森林

树的存储结构

  1. 双亲表示法
    每个结点保存指向双亲的指针
    在这里插入图片描述在这里插入图片描述
  1. 可以很快得到每个结点的双亲结点;
    求结点孩子或删除某一分支需要遍历。

在这里插入图片描述
树不都能用二叉树的存储结构来存储

存储结构描述

#define MAX_TREE_SIZE 100
typedef struct{
	ElemType data;
	int parent;
}PTNode;
typedef struct{
	PTNode nodes[MAX_TREE_SIZE];
	int n;//节点数
}PTree;
  1. 孩子表示法
    顺序+链式栈
    顺序存储各个节点,每个结点中保存孩子链表头指针
    在这里插入图片描述
    在这里插入图片描述
struct CTNode{
	int child;
	struct CTNode*next;
}
typedef struct {
	ElemType data;
	struct CTNode *firstChild;//第一个孩子
}CTBox;
typedef struct{
	CTBox nodes[MAX_TREE_SIZE];
	int n,r;//结点树和根的位置
}CTree;
  1. 孩子兄弟表示法(二叉树表示法)
    左孩子、右兄弟(链式存储)
typedef struct CSNode{
	ElemType data;
	struct CSNode *firstchild,*nextsibling;
	//第一个孩子和右兄弟指针
}CSNode,*CSTree;

在这里插入图片描述

树、森林与二叉树的转换

本质:用二叉链表存储森林
在这里插入图片描述
在这里插入图片描述

树和森林的遍历

树的先根遍历

若树非空,先访问根结点,再依次对每棵子树进行先根遍历。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

树的先根遍历序列与这棵树相应二叉树的先序序列相同。

树的后根遍历

若树非空,先依次对每棵子树进行后根遍历,最后再访问根结点。
在这里插入图片描述树的后根遍历序列与这棵树相应二叉树的中序序列相同。

树的层次遍历

用队列实现
、若树非空,则根节点入队
、若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队
、重复②直到队列为空

森林的先序遍历

若森林为非空,则按如下规则进行遍历:

  1. 访问森林中第一棵树的根结点。
  2. 先序遍历第一棵树中根结点的子树森林。
  3. 先序遍历除去第一棵树之后剩余的树构成的森林在这里插入图片描述
    在这里插入图片描述

效果等同于依次对各个树进行先根遍历
在这里插入图片描述
效果等同于依次对二叉树的先序遍历

森林的中序遍历

若森林为非空,则按如下规则进行遍历:

  1. 中序遍历森林中第一棵树的根结点的子树森林。
  2. 访问第一棵树的根结点。
  3. 中序遍历除去第一棵树之后剩余的树构成的森林。
    在这里插入图片描述
    在这里插入图片描述

总结

在这里插入图片描述

七、二叉排序数 BST

(二叉查找树)

  1. 左子树上所有结点的关键字均小于根结点的关键字;
  2. 右子树上所有结点的关键字均大于根结点的关键字。
  1. 左小右大
    左子树结点值 < 根结点值 < 右子树结点值
  2. 进行中序遍历,可以得到一个递增的有序序列

二叉排序树的查找

  1. 若树非空,目标值与根结点的值比较:
  2. 若相等,则查找成功;
  3. 小于根结点,则在左子树上查找,否则在右子树上查找。
typedef struct BSTNode{
	int key;
	struct BSTNode *lchild,*rchild;
}BSTNode ,*BSTree;

//在二叉排序树中查找值为key,的结点
BSTNode *BST_Search(BSTree T,int key){
//最坏空间复杂度:O(1)
	while(T!=NULL&&key!=T->key){
		if(key>T->key) T = T->rchild;
		else T = T->lchild;
	}
	return T;
}

递归实现:
在这里插入图片描述最坏空间复杂度:O(h)

查找效率分析

查找长度——在查找运算中,需要对比关键字的次数称为查找长度,反映了查找操作时间复杂度
在这里插入图片描述最好情况:平衡二叉树
在这里插入图片描述
最坏情况:只有左(右)孩子的单支树
在这里插入图片描述
平均时间性能上看,二叉排序树与二分查找差不多;但二叉排序树的查找不唯一,二分查找判断树唯一

二叉排序树的插入

插入的结点一定是是个新的叶子结点,且是查找失败时路径上访问的最后一个结点的左孩子或右孩子

int BST_Insert(BSTree &T,int k){
	if(T==NULL){
		T = (BSTree)malloc(sizeof(BSTNode));
		T->key = key;
		T->lchild = T->rchild = NULL;
		return 1;
	}
	else if(k==T->key) return 0//树中存在相同关键字的结点,插入失败
	else if(k<T->key)
		return BST_Insert(T->lchild,k);
	else
		return BST_Insert(T->rchild,k);
}

二叉排序树的构造

(不同元素序列可能得到不同的二叉排序树序列,也可能相同)
在这里插入图片描述

二叉排序树的删除

、 若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。
、若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。
在这里插入图片描述
在这里插入图片描述
、若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。

直接后续,子树中最小的;(右子树最左下结点,必无左子树)
在这里插入图片描述
直接前驱,子树中最大的;(左子树中最右下结点,必无右子树)
在这里插入图片描述

八、平衡二叉树 AVL

  1. 树上任一结点的左子树和右子树的高度之差不超过1
  2. 结点的平衡因子=左子树高 - 右子树高
  3. 平衡二叉树结点的平衡因子的值只可能是−1、0或1

平衡二叉树的插入

  1. 从插入点往回找到第一个不平衡结点,调整以该结点为根的子树
  2. 每次调整的对象都是最小不平衡子树

调整最先不平衡因子

LL平衡旋转(右单旋转)
  1. 由于在结点A的左孩子(L)左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。
  2. 将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

在这里插入图片描述在这里插入图片描述

RR平衡旋转(左单旋转)
  1. 由于在结点A的右孩子(R)右子树(R)上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。
  2. 将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树

在这里插入图片描述

LR平衡旋转(先左后右双旋转)
  1. 由于在A的左孩子(L)右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。
  2. 先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置

在这里插入图片描述

RL平衡旋转(先右旋再左旋)
  1. 由于在A的右孩子(R)的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。
    2… 先将A结点的右孩子B的左子树的根结点C右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置

在这里插入图片描述

总结

在这里插入图片描述

二叉平衡树的查找

比较关键字个数不超过树的深度
在这里插入图片描述

九、哈夫曼树和哈夫曼编码

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.两个串相等的充要条件是( )。A.串长度相等B.串长度任意 C.串中各位置字符任意 D.串中各位置字符均对应相等 2.对称矩阵的压缩存储:以行序为主序存储下三角中的元素,包括对角线上的元素。二维下标为( i, j ),存储空间的一维下标为k,给出k与 i, j (i<j)的关系k=( ) (1<= i, j <= n , 0<= k < n*(n+1)/2)。 A.i*(i-1)/2+j-1 B.i*(i+1)/2+j C.j*(j-1)/2+i-1 D.j*(j+1)/2+i 3.二维数组A[7][8]以列序为主序的存储,计算数组元素A[5][3] 的一维存储空间下标 k=( )。 A.38 B.43 C.26 D.29 4.已知一维数组A采用顺序存储结构,每个元素占用4个存储单元,第9个元素的地址为144,则第一个元素的地址是( )。A.108 B.180 C.176 D.112 5. 下面( )不属于特殊矩阵。 A.对角矩阵 B. 三角矩阵C. 稀疏矩阵 D. 对称矩阵 6. 假设二维数组M[1..3, 1..3]无论采用行优先还是列优先存储,其基地址相同,那么在两种存储方式下有相同地址的元素有( )个。 A. 3 B. 2 C. 1 D. 0 7. 若Tail(L)非空,Tail(Tail(L))为空,则非空广义表L的长度是( )。(其中Tail表示取非空广义表的表尾) A. 3 B. 2 C. 1 D. 0 8.串的长度是( )。 A.串中不同字母的个数 B.串中不同字符的个数C.串中所含字符的个数,且大于0 D.串中所含字符的个数 9.已知广义表(( ),(a), (b, c, (d), ((d, f)))),则以下说法正确的是( )。A.表长为3,表头为空表,表尾为((a), (b, c, (d), ((d, f))))B.表长为3,表头为空表,表尾为(b, c, (d), ((d, f)))C.表长为4,表头为空表,表尾为((d, f)) D.表长为3,表头为(()),表尾为((a), (b, c, (d), ((d, f))))10.广义表A=(a,b,c,(d,(e,f))),则Head(Tail(Tail(Tail(A))))的值为( )。(Head与Tail分别是取表头和表尾的函数) A.(d,(e,f)) B.d C.f D.(e,f)二、填空题(每空 2 分,共 8 分)。 1.一个广义表为 F = (a, (a, b), d, e, (i, j), k),则该广义表的长度为________________。GetHead(GetTail(F))= _______________。 2.一个n*n的对称矩阵,如果以行或列为主序压缩存放入内存,则需要 个存储单元。 3.有稀疏矩阵如下: 0 0 5 7 0 0 -3 0 0 0 4 0 0 2 0 它的三元组存储形式为: 。 三、综合题(共 22 分)。 1.(共8分)稀疏矩阵如下图所示,描述其三元组的存储表示,以及转置后的三元组表示。 0 -3 0 0 0 4 0 6 0 0 0 0 0 0 7 0 15 0 8 0 转置前(4分): 转置后(4分): 2. (共14分)稀疏矩阵M的三元组表如下,请填写M的转置矩阵T的三元组表,并按要求完成算法。 (1)写出M矩阵转置后的三元组存储(6分): M的三元组表: T的三元组表: i j e 2 1 3 3 2 4 4 2 5 4 3 5 5 1 6 5 3 6 i j e (2)如下提供了矩阵采用三元组存储时查找指定行号(m)和列号(n)元素值的算法框架,将代码补充完整(每空2分,共8分)。 typedefstruct{ inti,j; ElemType e; }Triple; typedefstruct{ Triple data[MAXSIZE+1]; //data[0]未用 intmu,nu,tu; //矩阵的行数,列数和非零元的个数 }TSMatrix; voidFind_TSMatrix(TSMatrix M, int m, int n, ElemType&e) //M为要查找的稀疏矩阵三元组存储,m为要查找的元素的行号,n为列号,e为查找后得到的值。 { for ( i=1 ; i<=M.tu ;i++) if( && ) { e=M.data[i].e; ; } if( ) e=0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值