【读书笔记】《王道论坛计算机考研机试指南》第三章

第三章

栈的应用

首先介绍标准模板库中的堆栈模板,通过对堆栈模板的使用可以使我们跳过对堆栈具体实现的编码,而专注于堆栈在程序中的应用。我们用

stack <int> S;

定义一个保存元素类型为int的堆栈S,这样所有有关堆栈实现的内部操作,标准模板库都已经帮我们实现了。使用

S.push(i);

向堆栈中压进一个数值为i的元素。使用

int x = S.top();

读取栈顶元素,并将其值赋予变量x。使用

S.pop();

弹出栈顶元素。
为了使用stack标准模板,我们还要在文件头部包括相应的预处理,并声明使用标准命名空间。

#include <stack>

Problem 1
在这里插入图片描述
括号匹配问题是堆栈的一个典型应用。

#include <stdio.h>
#include <stack>
using namespace std;
stack<int> S; //定义一个堆栈
char str[110]; //保存输入字符串
char ans[110]; //保存输出字符串
int main(){
    
    
	while (scanf("%s",str) != EOF) {
    
     //输入字符串
		int i;
		for (i=0;str[i]!=0;i++) {
    
     //从左到右遍历字符串
			if (str[i] == '(') {
    
     //若遇到左括号
				S.push(i);//将其数组下标放入堆栈中
				ans[i] =' '; //暂且将对应的输出字符串位置改为空格
			}
			else if (str[i] == ')') {
    
     //若遇到右括号
				if (S.empty() == false) {
    
     //若此时堆栈非空
					S.pop(); //栈项位置左括号与其匹配,从栈中弹出该已经匹配的左括号
					ans[i] = ' '; //修改输出中该位置为空格
				}
				else ans[i] = '?'; //若堆栈为空,则无法找到左括号与其匹配修改输出中该位置为?
			}
			else ans[i] =' '; //若其为其它字符,与括号匹配无关,则修改输出为空格
		}
		while(!S.empty()) {
    
     //当字符串遍历完成后,尚留在堆栈中的左括号无法匹配
			ans[S.top()] = '$'; //修改其在输出中的位置为$
			S.pop();  //弹出
		}
		ans[i] = 0; //为 了使输出形成字符串,在其最后一个字符后添加一个空字符
		puts(str);//输出原字符串
		puts(ans); //输出答案字符串
	}
	return 0;
}

Problem 2
堆栈还有一个著名的应用-----表达式的求值。
在这里插入图片描述
在这里插入图片描述
利用堆栈对表达式求值的方法:

  1. 设立两个堆栈,一个用来保存运算符,另一个用来保存数字。
  2. 在表达式首尾添加标记运算符,该运算符运算优先级最低。
  3. 从左至右依次遍历字符串,若遍历到运算符,则将其与运算符栈栈顶元素进行比较,若运算符栈栈顶运算符优先级小于该运算符或者此时运算符栈为空,则将该运算符压入堆栈。遍历字符串中下一个元素。
  4. 若运算符栈栈顶运算符优先级大于该运算符,则弹出该栈顶运算符,再从数字栈中依次弹出两个栈顶数字,完成弹出的运算符对应的运算得到结果后,再将该结果压入数字栈,重复比较此时栈顶运算符与当前遍历到的运算符优先级,视其优先级大小重复步骤3或步骤4。
  5. 若遍历到表达式中的数字,则直接压入数字栈。
  6. 若运算符堆栈中仅存有两个运算符且栈顶元素为我们人为添加的标记运算符,那么表达式运算结束,此时数字堆栈中唯一的数字即为表达式的值。
#include <stack>
#include <stdio.h>
using namespace std;
char str[220]; //保存表达式字符串
int mat[][5] = {
    
     
//优先级矩阵,若mat[i][j] == 1,则表示i号运算符优先级大于j号运算符
//运算符编码规则为+为1号,-为2号,*为3号, /为4号,人为添加在表达式首尾的标记运算符加号
1,0,0,0,0,
1,0,0,0,0,
1,0,0,0,0,
1,1,1,0,0,
1,1,1,0,0,
};
stack<int> op; //运算符栈,保存运算符编号
stack<double> in; //数字栈,运算结果可能存在浮点数所以保存元素为double
void getOp(bool &reto, int &retn, int &i) {
    
    
/*获得表达式中下一个元素函数,若函数运行结束时,引用变量reto为true,则表示该元素为一个运算符,
其编号保存在引用变量retn中;否则,表示该元素为一个数字,其值保存在引用变量retn中,引用变量i
表示遍历到的字符串下标*/
	if (i==0&&op.empty() == true) {
    
     //若此时遍历字符串第一个字符,且运算符栈为空我们人为添加编号为0的标记字符
		reto = true; //为运算符
		retn = 0; //编号为0
		return; 
	}
	if (str[i] == 0) {
    
     
	//字符串结束符'\0'的ASCII是0;
	//若此时遍历字符为空字符,则表示字符中已经被遍历完
		reto = true; //返回为运算符
		retn = 0; //编号为0的标记字符
		return; 
	}
	if (str[i] >= '0' && str[i] <= '9') {
    
     //若当前字符为数字
		reto = false; //返回为数字
	}
	else {
    
     //否则
		reto = true; //返回 为运算符
		if (str[i] == '+') {
    
     //加号返回1 
			retn = 1;
		}
		else if (str[i] == '-') {
    
     //减号返回2
			retn = 2;
		}
		else if (str[i ] == '*') {
    
     //乘号返回3
			retn = 3;
		}
		else if (str[i] == '/') {
    
     //除号返回4
			retn = 4;
		}
		i += 2; //i递增,跳过该运算字符和该运算字符后的空格
		return; 
	}
	retn = 0; //返回结果为数字
	for (;str[i] != ' ' && str[i] != 0;i++) {
    
     //若字符串未被遍历完,且下一个字符不是空格,则依次遍历其后数字,计算当前连续数字字符表示的数值
		retn *= 10;
		retn += str[i] - '0' ;
	} //计算该数字的数字值
	if (str[i] == ' ') //若其后字符为空格,则表示字符串未被遍历完
		i++; //i递增,跳过该空格
	return; 
}
int main(){
    
    
	while(gets(str)) {
    
     //输入字符串,当其位于文件尾时,gets返回0
		if (str[0] == '0'&&str[1] == 0) break; //若输入只有一个0,则退出
		bool retop;
		int retnum; //定义函数所需的引用变量
		int idx = 0; //定义遍历到的字符串下标,初始值为0
		while(!op.empty()) op.pop();
		while(!in.empty()) in.pop(); //清空数字栈和运算符栈
		while(true) {
    
     //循环遍历表达式字符串
			getOp(retop,retnum,idx); //获取表达式中下个元素
			if (retop == false) {
    
     //若该元素为数字
				in.push((double)retnum); //将其压入数字栈中
			}
			else {
    
     //否则
				double tmp;
				if (op.empty()==true||mat[retnum][op.top()] == 1) {
    
    
					op.push(retnum);
				}//若运算符堆栈为空或者当前遍历到的运算符优先级大于栈顶运算符,将该运算符压入运算符堆栈
				else {
    
     //否则
					while(mat[retnum][op.top()] == 0) {
    
     //只要当前运算符优先级小于栈顶元素运算符,则重复循环
						int ret=op.top(); //保存栈顶运算符
						op.pop(); //弹出
						double b = in.top();
						in.pop();
						double a = in.top();
						in.pop(); //从数字堆栈栈项弹出两个数字,依次保存在遍历a、b中
						if (ret == 1) tmp=a+b;
						else if (ret ==2) tmp=a-b;
						else if (ret==3) tmp=a*b;
						else tmp =a/b; //按照运算符类型完成运算
						in.push(tmp); //将结果压回数字堆栈
					}
				op.push(retnum);//将当前运算符压入运算符堆栈
				}
			}
			if (op.size() == 2 &&op.top() == 0) break; //若运算符堆栈只有两个元素,且其栈项元素为标记运算符.则表示表达式求值结束
		}
		printf("%.2f\n",in.top()); //输出数字栈中唯一的数字,即为答案
	}
	return 0;
}

哈夫曼树

给定结点的哈夫曼树可能不唯一,所以关于哈夫曼树的机试题往往需要求解的是其最小带权路径长度和。回顾一下我们所熟知的哈夫曼树求法。

  1. 将所有结点放入集合K。
  2. 若集合K中剩余结点大于2个,则取出其中权值最小的两个结点,构造他们同时为某个新节点的左右儿子,该新节点是他们共同的双亲结点,设定它的权值为其两个几子结点的权值和。并将该父亲结点放入集合K。重复步骤2或3。
  3. 若集合K中仅剩余一个结点,该结点即为构造出的哈夫曼树数的根结点,所有构造得到的中间结点(即哈夫曼树上非叶子结点)的权值和即为该哈夫曼树的帶权路径和。为了方便快捷高效率的求得集合K中权值最小的两个元素,我们需要使用堆数据结构。它可以以O (logn) 的复杂度取得n个元素中的最小元素。为了绕过对堆的实现,我们使用标准模板库中的相应的标准模板一优先队列。

利用语句

扫描二维码关注公众号,回复: 12454154 查看本文章
priority_queue<int>Q;

建立一个保存元素为int 的堆Q,但是请特别注意这样建立的堆其默认为大顶堆,即我们从堆顶取得的元素为整个堆中最大的元素。面在求哈夫曼树中,我们恰恰需要取得堆中最小的元素,于是我们使用如下语句定义一个小顶堆:

priority_queue<int,vector<int>,greater<int>> Q;

关于堆的有关操作如下:

Q.push(x);

将元素x放入堆Q中。

int a = Q.top();

取出堆顶元素,即最小的元素保存在a中。

Q.pop();

弹出堆顶元素,取出后堆会自动调整为一个新的小顶堆。
它的定义与之前我们使用过的队列一样在标准模板库queue中,所以在使用它之前我们必领做相应预处理。

#include <queue>
using namespace std;

在这里插入图片描述

#include <queue>
#include <stdio.h>
using namespace std;
priority_queue<int,vector<int>,greater<int>> Q; //建立一个小顶堆
int main(){
    
    
	int n;
	while (scanf("%d",&n) != EOF) {
    
    
		while(Q.empty() == false) Q.pop(); //清空堆中元素
		for (int i=1;i<=n;i++) {
    
     //输入n个叶子结点权值
			int x;
			scanf("%d",&x);
			Q.push(x); //将权值放入堆中
		}
		int ans = 0; //保存答案
		while(Q.size() > 1) {
    
     //当堆中元素大于1个
			int a = Q.top();
			Q.pop();
			int b = Q.top();
			Q.pop();//取出堆中两个最小元素,他们为同一个结点的左右儿子,且该双亲结点的权值为它们的和
			ans+=a+b;//该父亲结点必为非叶子结点固累加其权值
			Q.push(a + b); //将该双亲结点的权值故回堆中
		}
		printf("%d\n",ans);
	}
	return 0;
}

二叉树

众所周知,在对二叉树的遍历过程中,根据遍历每一个结点的左子树、结点本身、右子树的顺序不同可将对二叉树的遍历方法分为前序遍历、中序遍历、后序遍历。我们摒弃数据结构教科书.上复杂的遍历方式,而是使用我们在上一章所重点讨论过的递归程序来简单的实现它。假设二叉树结点由以下结构体表示:
假设二叉树结点由以下结构体表示:

struct Node {
    
    
Node *lchild; //指向其左儿子结点的指针,当其不存在左儿子时为NULL
Node *rchild; //指向其右儿子结点的指针,当其不存在右儿子时为NULL
/*
*
*
其它结点信息*/
};

我们以中序遍历为例,给出其遍历方法。

void inOrder (Node *Tree) {
    
    
if (Tree->lchild != NULL) //递归遍历左子树
	inOrder(Tree->lchild);
/*
*
*对当前结点Tree作遍历操作*/
if (Tree->rchild!=NULL) //送归遍历右子树
	inOrder(Tree->rchild);
return;

Problem 1
在这里插入图片描述
由给定的前序和中序遍历还原得到该二叉树。以前序遍历结果XDAGFE,和中序遍历结果ADGXFE为例详细讨论其还原方法。
在这里插入图片描述
由前序遍历结果的首个元素为X可知,原树必是由X为根结点。在中序遍历中,遍历结果ADGXFE以X为界分为两个子串。其中第一个子串ADG为X的左子树的中序遍历结果,第二个子串FE为X的右子树的中序遍历结果。这样我们知道X的左子树具有3个元素, X的右子树具有2个元素。根据元素的数量我们同样可以得知,在先序遍历中去除根结点X后剩余的串DAGFE中,前3个字符DAG为X的左子树的前序遍历结果,后2个字符FE为X的右子树的前序遍历结果。
在这里插入图片描述
同样的对于确定的左子树前序遍历结果DAG和中序遍历结果ADG重复以上确定过程,可知D为该子树根结点,其左儿子为A,右儿子为G。X的右子树前序遍历结果FE和中序遍历结果FE同样可以确定该子树以F为根节点,其左儿子不存在,右儿子为E。
在这里插入图片描述
这样就还原了原始二叉树。

#include <stdio.h>
#include <string.h>
struct Node {
    
     //树结点结构体
	Node *lchild; //左儿子指针
	Node *rchild; //右儿子指针
	char c; //结点字符信息
}Tree[50]; //静态内存分配数组
int loc; //静态数组中已经分配的结点个数
Node *creat() {
    
     //申请一个结点空间,返回指向其的指针
	Tree[loc].lchild = Tree[loc].rchild = NULL; //初始化左右儿子为空
	return &Tree[loc++]; //返回指针,且loc累加
}
char str1[30],str2[30]; //保存前序和中序遍历结果字符串
void postOrder(Node *T) {
    
     //后序遍历
	if (T-> lchild != NULL) {
    
     //若左子树不为空
		postOrder(T-> lchild); //送归遍历其左子树
	}
	if (T->rchild != NULL) {
    
     //若右子树不为空
		postOrder(T -> rchild); //递归遍历其右子树
	}
	printf("%c",T->c); //遍历该结点,输出其字符信息
}
Node *build(int s1,int e1,int s2,int e2) {
    
     
/*由字符串的前序遍历和中序遍历还原树,并返回其根节点,其中前序遍
历结果为由str1[s1]到str2[e1],中序遍历结果为str2[s2]到str2[e2]*/
	Node* ret = creat(); //为该树根节点中请空间
	ret->c = str1[s1]; //该结点字符为前序遍历中第一个字符
	int rootIdx; 
	for (int i=s2;i<=e2;i++) {
    
    //查找该根节点字符在中序遍历中的位置
		if (str2[i] == str1[s1]) {
    
    
			rootIdx = i;
			break;
		}
	}
	if (rootIdx != s2) {
    
     //若左子树不为空
		ret -> lchild = build(s1+1,s1+(rootIdx-s2),s2,rootIdx-1); //递归还原其左子树
	}
	if (rootIdx != e2) {
    
     //若右子树不为空
		ret -> rchild = build(s1+(rootIdx-s2)+1,e1,rootIdx+1,e2); //递归还原其右子树
	}
	return ret; //返回根节点指针
}
int main(){
    
    
	while (scanf("%s",str1) != EOF) {
    
    
		scanf("%s", str2); 
		loc = 0; //初始化静态内存空间中已经使用结点个数为0
		int L1 = strlen(str1);
		int L2 = strlen(str2); //计算两个字符串长度
		Node *T = build(0,L1-1,0,L2-1); //还原整棵树,其根结点指针保存在T中
		postOrder(T); //后序遍历
		printf("\n"); //输出换行
	}
	return 0;
}

二叉排序树

二叉排序树是一棵特殊的二叉树,它是一棵二叉树但同时满足如下条件:对于树上任意一个结点,其上的数值必大于等于其左子树上任意结点数值,必小于等于其右子树上任意结点的数值。二叉排序树的存储方式与二叉树保持一致,我们更多的关注它独有的操作。我们从二叉树的插入开始了解其建树方式,对二叉排序树插入数字x:

  1. 若当前树为空,则x为其根结点。
  2. 若当前结点大于x,则x插入其左子树;若当前结点小于x,则x插入其右子树;若当前结点等于x,则根据具体情况选择插入左右子树或者直接忽略。以插入4、2、6、1、3为例,其二叉排序树变化情况如下图。
    在这里插入图片描述

由于各个数字插入的顺序不同,所得到的二叉排序树的形态也很可能不同,所以不同的插入顺序对二叉排序树的形态有重要的影响。但是,所有的二叉排序树都有一个共同的特点:若对二叉排序树进行中序遍历,那么其遍历结果必然是一个递增序列,这也是二叉排序树名字的来由,通过建立二叉排序树就能对原无序序列进行排序,并实现动态维护。

Problem 1
在这里插入图片描述

#include <stdio.h>
#include <string.h>
struct Node {
    
     //树结点结构体
	Node *lchild; //左儿子指针
	Node *rchild; //右儿子指针
	int c; //保存数字
}Tree[110]; //静态内存分配数组
int loc; //静态数组中已经分配的结点个数
Node *creat() {
    
     //申请一个结点空间,返回指向其的指针
	Tree[loc].lchild = Tree[loc].rchild = NULL; //初始化左右儿子为空
	return &Tree[loc++]; //返回指针,且loc累加
}
char str1[30],str2[30]; //保存前序和中序遍历结果字符串
void postOrder(Node *T) {
    
     //后序遍历
	if (T->lchild != NULL) {
    
     //若左子树不为空
		postOrder(T->lchild); //送归遍历其左子树
	}
	if (T->rchild != NULL) {
    
     //若右子树不为空
		postOrder(T->rchild); //递归遍历其右子树
	}
	printf("%d ",T->c); //遍历该结点,输出其字符信息
}
void inOrder(Node *T) {
    
     //后序遍历
	if (T->lchild != NULL) {
    
     //若左子树不为空
		inOrder(T-> lchild); //送归遍历其左子树
	}
	printf("%d ",T->c); //遍历该结点,输出其字符信息
	if (T->rchild != NULL) {
    
     //若右子树不为空
		inOrder(T->rchild); //递归遍历其右子树
	}
}
void preOrder(Node *T) {
    
     //后序遍历
	printf("%d ",T->c); //遍历该结点,输出其字符信息
	if (T->lchild != NULL) {
    
     //若左子树不为空
		preOrder(T->lchild); //递归遍历其左子树
	}
	if (T->rchild != NULL) {
    
     //若右子树不为空
	preOrder(T->rchild); //递归遍历其右子树
	}
}
Node *Insert(Node *T, int x) {
    
     //插入数字
	if(T==NULL){
    
    //著当前树为空
		T = creat();//建立结点
		T->c = x;//数字直接插入其根结点
		return T; //返回根结点指针
	}
	else if (x < T->c) //若x小于根结点数值
		T->lchild = Insert(T->lchild,x); //插入到左子树上
	else if (x > T->c) //若x大于根结点数值
		T->rchild = Insert(T->rchild,x); //插入到右子树上.若根结点数值与x一样,根据题目需求直接忽略
	return T; //返回根节点指针
}
int main(){
    
    
	int n;
	while (scanf("%d",&n) != EOF) {
    
    
		loc = 0;
		Node *T = NULL; //二叉排序树树根结点为空
		for (int i = 0;i < n;i++) {
    
     //依次输入n个数字
			int x;
			scanf("%d",&x);
			T = Insert(T,x);//插入到排序树中
		}
		preOrder(T); //前序遍历
		printf("\n"); 
		inOrder(T); //中序遍历 
		printf("\n");
		postOrder(T); //后序遍历
		printf("\n");
	}
	return 0;
}

在学习了二叉排序树的建立和三种方式的遍历以后,我们还要接触一种特殊的树操作-----判断两棵二叉树是否相同。判断两棵树是否相同,我们不能简单的用某一种遍历方式去遍历两棵树,并判断遍历的结果是否相同,这种方法是错误的。由于一种遍历顺序并不能唯一的确定一棵二叉树,所以两棵不同的树的某一种遍历顺序是可能相同的。如数字相同,插入顺序不同面建立的两棵二叉排序树,它们的中序遍历一定是一样的。但在之前例题中我们已经看到,包括中序遍历在内的两种遍历结果可以唯一得确定一棵二叉树,那么只需对两棵树进行包括中序遍历在内的两种遍历,若两种遍历的结果都相同,那么就可以判定两棵树是完全相同的。

Problem 2
在这里插入图片描述
对输入的数字序列构建二叉排序树,并对它们进行前序和中序的遍历,依次比较两次遍历结果是否相同,若相同则说明两棵二叉排序树相同,否则不同。

#include <stdio.h>
#include <string.h>
struct Node {
    
     //树结点结构体
	Node *lchild; //左儿子指针
	Node *rchild; //右儿子指针
	int c; //保存数字
}Tree[110]; //静态内存分配数组
int loc; //静态数组中已经分配的结点个数
Node *creat() {
    
     //申请一个结点空间,返回指向其的指针
	Tree[loc].lchild = Tree[loc].rchild = NULL; //初始化左右儿子为空
	return &Tree[loc++]; //返回指针,且loc累加
}
char str1[30],str2[30]; //保存前序和中序遍历结果字符串
int size1,size2; //保存在字符数组中的遍历得到字符个数
char *str; //当前正在保存字符串
int *size; //当前正在保存字符串中字符个数
void postOrder(Node *T) {
    
     //后序遍历
	if (T->lchild != NULL) {
    
     //若左子树不为空
		postOrder(T->lchild); //送归遍历其左子树
	}
	if (T->rchild != NULL) {
    
     //若右子树不为空
		postOrder(T->rchild); //递归遍历其右子树
	}
	str[(*size)++] = T->c+'0'; //将结点中的字符放入正在保存的字符串中
}
void inOrder(Node *T) {
    
     //后序遍历
	if (T->lchild != NULL) {
    
     //若左子树不为空
		inOrder(T->lchild); //送归遍历其左子树
	}
	str[(*size)++] = T->c+'0'; //将结点中的字符放入正在保存的字符串中
	if (T->rchild != NULL) {
    
     //若右子树不为空
		inOrder(T->rchild); //递归遍历其右子树
	}
}
Node *Insert(Node *T, int x) {
    
     //插入数字
	if(T==NULL){
    
    //著当前树为空
		T = creat();//建立结点
		T->c = x;//数字直接插入其根结点
		return T; //返回根结点指针
	}
	else if (x < T->c) //若x小于根结点数值
		T->lchild = Insert(T->lchild,x); //插入到左子树上
	else if (x > T->c) //若x大于根结点数值
		T->rchild = Insert(T->rchild,x); //插入到右子树上.若根结点数值与x一样,根据题目需求直接忽略
	return T; //返回根节点指针
}
int main(){
    
    
	int n;
	char tmp[12];
	while (scanf("%d",&n) != EOF && n != 0) {
    
    
		loc = 0; //初始化静态空间为未使用
		Node *T = NULL;
		scanf("%s",tmp); 
		for(int i=0;tmp[i]!=0;i++){
    
    
			T = Insert(T,tmp[i]-'0'); //按顺序将数字插入二叉排序树
		}
		size1 = 0; //保存在第一个字符串中的字符初始化为0
		str = str1; //将正在保存字符串设定为第一个字符串
		size=&size1; //将正在保存字符串中的字符个数指针指向si2e1
		postOrder(T); //前序遍历
		inOrder(T); //中序遍历 
		str1[size1] = 0; //向第一个字符串的最后一个字符后添加空字符,方便使用字符串函数
		while(n--!= 0) {
    
     //输入n个其它字符串
			scanf("%s",tmp);
			Node *T2 = NULL;
			for (int i=0;tmp[i]!=0;i++) {
    
     //建立二叉排序树
				T2 = Insert(T2,tmp[i]-'0'); 
			}
			size2 = 0; //第二个字符串保存字符初始化为0
			str = str2; //将正在保存字符串设定为第二个字符串
			size = &size2; //正在保存字符串中字符数量指针指向size2
			postOrder(T2); //前序遍历
			inOrder(T2); //中序遍历
			str2[size2] = 0;//字符串最后添加空字符
			puts(strcmp(str1,str2) == 0 ? "YES" : "NO"); //比较两个遍历字符串,若相同则输出YES,否则输出NO
		}
	}
	return 0;
}

同样的,我们也可以选择中序和后序的排序结果共同对两棵树进行判定。但是请注意,在选择的两种遍历方式中必须要包括中序遍历。如在数据结构中所讲的,只有包括中序的两种遍历顺序才能唯一的确定一棵二叉树。最后,我们对二叉排序树的剥除作适当的补充。二叉排序树的剥除在机试题中考察的概率非常小,在之前我们已经得到的机试题中没有对其进行任何的考察。
删除二叉排序树上的某一个结点,按如下步骤进行:

  1. 利用某种遍历找到该结点。
  2. 若该结点为叶子结点,则直接删除它,即将其双亲结点中指向其的指针改为NULL,释放该节点空间。.
  3. 若该结点仅不存在右子树,则直接将其左子树的根结点代替其位置后,删除该结点。即将其双亲结点指向其的指针改为指向其的左子树树根。
  4. 若该节点存在右子树,则找到右子树上最右下的结点(即中序遍历中该子树上第一个被遍历到的结点),将被删除结点的数值改为右子树上最右下结点的数值后,删除最右下结点。

删除二叉树的原理非常简单,即删除该结点后,其中序遍历依然保持关键字递增的顺序,只要符合这个条件,不同于上述规则的删除也是可行的。

猜你喜欢

转载自blog.csdn.net/weixin_44029550/article/details/105327896