二叉树,线索二叉树学习整理,c++

本文介绍了如何通过层序、先序和后序序列生成二叉树的递归和非递归方法,以及二叉树的先序、中序、后序和层序遍历的递归和非递归实现。此外,还讨论了线索二叉树的线索化过程以及在线索二叉树中查找前驱和后继节点的方法。

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

1.二叉树的存储结构定义

#pragma once
#include<iostream>
using namespace std;

class Tnode {
public:
	char data;
	Tnode* leftC;
	Tnode* rightC;    
    //线索二叉树的标志位,普通二叉树用不到
	int lTag;
	int rTag;
	Tnode() :data(0), leftC(NULL), rightC(NULL), lTag(0), rTag(0) {}
};

2.根据某一特定序列生成二叉树(空节点用'#'代替);

2.1.由层序序列生成二叉树

2.1.1.非递归的方式


/*
*  非递归
	层序遍历生成二叉树
*/
Tnode* leveCreat1(Tnode* &tree) {
	queue<Tnode*> nodeQ;
	char ch = '\0';
	cin >> ch;

	if (ch != '#') {
		tree->data = ch;
		nodeQ.push(tree);       //根节点入队
	}
	else {
		return NULL;
	}

	Tnode* tempNode = NULL;
    //每次弹出一个节点,用户输入两个数据,第一个作为其左孩子的数据,        
    //第二个作为其右孩子的数据,同时分别入队
	while (!nodeQ.empty()) {
		tempNode = nodeQ.front();
		nodeQ.pop();
		cin >> ch;
		if (ch != '#') {
			Tnode* p=new Tnode;
			p->data = ch;
			tempNode->leftC = p;
			nodeQ.push(tempNode->leftC);
		}

		cin >> ch;
		if (ch !='#'){
			Tnode* p=new Tnode;
			p->data = ch;
			tempNode->rightC = p;
			nodeQ.push(tempNode->rightC);
		}
	}
	return tree;
}

//测试
int main(){
    Tnode* tree =new Tnode ;
    leveCreat1(tree);
    //遍历tree

}

2.1.2 递归方式

//需要用到vector装载输入
//i是第一个元素的小标,默认为0
//层序遍历序列中,节点i的左孩子下标为2*i(i初始值为1),右孩子是2*i+1,通过这个特性递归生成
void leveCreat2(Tnode* &tree, vector<char> arr,int i=0) {
	if (arr.empty()) {
		return;
	}
	char ch = arr.at(i);
	
	if (i >= arr.size()&&ch == '#') {
		tree = NULL;
	}
	tree->data = ch;

	//由于这里下标是从0开始,所以是2*i+1
	if (2 * i + 1 < arr.size() && arr.at(2 * i + 1) != '#') {
		tree->leftC = new Tnode;
		leveCreat2(tree->leftC, arr, 2 * i + 1);
	}

	if (2 * i + 2 < arr.size() && arr.at(2 * i + 2) != '#') {
		tree->rightC = new Tnode;
		leveCreat2(tree->rightC, arr, 2 * i + 2);
	}
}

//测试
int main(){

vector<char> arr{'1','2','3','4','5','6','7','#', '8', '#', '#', '9', '#', '#', '#', '#', '#', '#', '#'};
Tnode* tree = new Tnode;
leveCreat2(tree,arr);
//遍历tree

}


2.2.由先序序列生成二叉树

2.2.1 递归方式

//和先序遍历时差不多,把visit()换成了创建节点和赋值操作
Tnode* firstCreat1(Tnode* &tree) {
	char ch = '/0';
	cin >> ch;
	if (ch == '#') {
		tree = NULL;
	}
	else {
		tree = new Tnode;
		tree->data = ch;
		tree->leftC =firstCreat1(tree->leftC);
		tree->rightC =firstCreat1(tree->rightC);
	}
	return tree;
}

2.2.2 非递归方式

2.3.由后续序列生成二叉树

单独的中序序列是无法确认一颗树的

2.3.1. 递归方式

//观察后续序列会发现,后序序列的逆序其实是:按照 “根 右 左”的顺序遍历出来的
//所以就有头绪了
void finCreat1(Tnode* &tree, vector<char> arr) {
	static int count = arr.size();
	char ch = arr.at(count-1);
	count--;
	if (ch == '#') {
		tree = NULL;
	}
	else {
		tree = new Tnode;
		tree->data = ch;
		//先右后左
		finCreat1(tree->rightC, arr);
		finCreat1(tree->leftC, arr);
	}
}

2.3.2 非递归方式

3.二叉树的遍历

3.1.先序遍历

3.1.1 递归方式

/*
	递归先序遍历
*/
void firstOrder(Tnode* tree) {
	if (tree != NULL) {
		cout <<tree->data<<" ";
		firstOrder(tree->leftC);
		firstOrder(tree->rightC);
	}
}

3.1.2 非递归方式

/*
	非递归先序遍历O(n)
*/
void firstOrder2(Tnode* tree) {
	if (tree == NULL) {
		return ;
	}

	stack<Tnode*> stack;
	stack.push(tree);

	while (!stack.empty()) {
		Tnode* top = stack.top();

		cout << top->data << " ";
		stack.pop();
		//先压右孩子,在压左孩子,
		//这样出栈时就是左孩子先出右孩子后出,就是先序了
		if (top->rightC != NULL) {
			stack.push(top->rightC);
		}

		if (top->leftC != NULL) {
			stack.push(top->leftC);
		}
	}
}

void firstOrder3(Tnode* tree) {
	if (tree == NULL) {
		return;
	}
	stack<Tnode*> stack;
	while (tree!=NULL||!stack.empty()) {
		if (tree != NULL) {//如果当前节点不为空则一直访问它的左子树和压它的左子树
			cout << tree->data << " ";
			stack.push(tree);
			tree = tree->leftC;
		}
		//如果当前节点为空,则表明当前节点的父节点没有左孩子,
		//将当前节点变为当前节点的父节点,也就是栈顶节点,然后在变为父节点的右孩子,弹栈
		else
		{
			tree = stack.top();
			tree = tree->rightC;
			stack.pop();
		}
	}
}

3.2 中序遍历

3.2.1 递归方式

/*
	递归中序遍历
*/
void midOrder(Tnode* tree) {
	if (tree != NULL) {
		midOrder(tree->leftC);
		cout << tree->data << " ";
		midOrder(tree->rightC);
	}
}

3.2.2非递归方式

/*
	中序非递归遍历
*/
//和先序差不多,只不过访问节点的时机不同
void midOrder2(Tnode* tree) {
	stack<Tnode*> stack;

	while (tree!=NULL||!stack.empty ()) {
		if (tree != NULL) {
			stack.push(tree);
			tree = tree->leftC;
		}
		else {
			tree = stack.top();

			cout << tree->data<<" ";

			tree = tree->rightC;
			stack.pop();
		}
	}
}

3.3 后序遍历

3.3.1 递归方式

/*
	递归后序遍历
*/
void finOrder(Tnode* tree) {
	if (tree != NULL) {
		finOrder(tree->leftC);
		finOrder(tree->rightC);
		cout << tree->data << " ";
	}
}

3.3.2 非递归方式

/*
	非递归后序遍历
*/
//后续序列的逆序是按照: 根 右 左的顺序得到的
//把根右左的顺序存入一个栈,出栈的过程就是逆置的过程,就是后序遍历了
void finOrder2(Tnode* tree) {
	stack<Tnode*> s;

	stack<Tnode*> output;

	while (tree!=NULL||!s.empty()) {
		if (tree != NULL) {
			s.push(tree);
			tree = tree->rightC;
		}
		else {
			tree = s.top();
			tree = tree->leftC;
			s.pop();
		}
	}
	while (!output.empty()) {
		cout << output.top()->data << " ";
		output.pop();
	}
}

3.3.4 层序遍历

/*
	层序遍历
*/
//先让根节点入队,然后每次弹出一个节点,同时访问这个节点
//分别判断这个节点的左右孩子是不是空,不是空就入队,直到队列为空位置
void leveTraversal(Tnode* &tree) {
	if (tree == NULL){
		return;
	}
	queue<Tnode*> nodeQ;
	nodeQ.push(tree);
	
	while (!nodeQ.empty()) {
		Tnode* p=nodeQ.front();
		nodeQ.pop();

		cout << p->data<<"  ";
		if (p->leftC != NULL) {
			nodeQ.push(p->leftC);
		}

		if (p->rightC != NULL) {
			nodeQ.push(p->rightC);
		}
	}
}

4.中序序列+某一特点序列还原二叉树

4.1.先序 中序 序列生成二叉树

//参数:
//pre:先序序列起始指针,prelen先序序列的长度
//mid:中序序列起始指针,midlen中序序列的长度
Tnode* PreMidCre(char* pre, int prelen, char* mid, int midlen) {
	if (prelen==0 || midlen == 0) {
		return NULL;
	}

	//先序的第一个一定是根节点
	char ch = pre[0];

	int i;//中序序列中根节点的位置i

	for ( i = 0; i < midlen; i++) {
		if (mid[i] == ch) {
			break;
		}
	}
	//造根
	Tnode* root = new Tnode;
	root->data = ch;

	/*	
				  左	   根        右
	   中序mid:|4 2 5|     1       |6 3 7|
		   长度:  i        1      midlen-i-1
	
				根         左		    右
		先序pre:1       |2 4 5|      |3 6 7|
		   长度:1         i		  prelen-i-1

	*/
	//中序左边的都是左子树,中序下标是i,i从0开始,所以左子树的长度直接是i
	root->leftC = PreMidCre(pre+1, i, mid, i);
	root->rightC = PreMidCre(pre+i+1, prelen-i-1,mid+i+1,midlen-i-1);
	return root;
}

4.2.中序 后序 序列生成二叉树

/*
	中序后续序列生成二叉树
*/
//参数:分别是:指向中序数组开始的指针,中序数组长度,指向后续数组开始位置的指针,后续数组长度
Tnode* MidPostCre(char* mid, int midlen, char* post, int plen) {
	if (midlen == 0 || plen == 0) {
		return NULL;
	}

	char ch = post[plen-1];
	int i = 0;
    //循环找到根节点的位置
	for (i; i < midlen; i++) {
		if (mid[i] == ch) {
			break;
		}
	}

	Tnode* root = new Tnode;
	root->data = ch;
	/*
	*				左		 根		右
		中序mid:   |4 2 5|    1    |6 3 7|
		长度:        i       1      midlen-i-1

					左		右			根
	  后续post:	  |4 5 2|   |6 7 3|      1
	  长度:		i	   poslen-i-1	 1 
	*/
	root->leftC = MidPostCre(mid,i,post,i);
	root->rightC = MidPostCre(mid+i+1,midlen-i-1,post+i,plen-i-1);
	return root;
}

4.3 层序 中序 序列生成二叉树

/*
	中序层序序列生成二叉树
	大致流程:
	1.根据层序第一个数据,创建根节点、
	2.循环找到中序遍历中根节点的位置i。
	3.双层for循环找出层序遍历中左子树的所有节点,并存在一个char数组left中
	4.双层for循环找出层序遍历中右子树的所有节点,并存在一个char数组right中
	5.把left和right作为新的参数参入,重复上述过程,直到数组长度为0
*/

//参数:中序数组起始位置,中序数组长度,层序数组起始位置,层序数组长度
Tnode* MidLevCre(char* mid, int midlen, char* level, int levlen) {
	if (midlen == 0 || levlen == 0) {
		return NULL;
	}
	//层序第一个一定是根
	char ch = level[0];
	Tnode* root = new Tnode;
	root->data = ch;
	//层序遍历数组等于1了,说明该节点一定是叶子节点,直接返回给上层
	if (levlen == 1) {
		return root;
	}

	//循环找根
	int i = 0;
	for (i; i < midlen; i++) {
		if (mid[i] == ch) {
			break;
		}
	}
	char* left = new char;
	char* right = new char;
	int leftLen = 0;
	int righLen = 0;
	
	//构建左子树的层序遍历
	for (int k = 1; k < levlen; k++) {
		for (int j = 0; j < i; j++) {
			if (level[k] == mid[j]) {
				left[leftLen] = level[k];
				leftLen++;
			}
	}
}
	//构建右子树的层序遍历
	for (int k = 1; k < levlen; k++) {
		for (int j = i + 1; j < midlen; j++) {
		if (level[k] == mid[j]) {
			right[righLen] = level[k];
			righLen++;
		}
	}
}
	root->leftC = MidLevCre(mid,i,left,leftLen);
	root->rightC = MidLevCre(mid+i+1,midlen-i-1,right, righLen);
	return root;
}

5.普通二叉树寻找前驱后继(土方法)

//全局变量
 Tnode* pre = NULL;
 Tnode* fin = new Tnode;
 Tnode* cur = new Tnode;

//找前驱
void findLast(Tnode* cur,Tnode* target){
	if (cur->data == target->data) {
		fin = pre; 
	}
	else {
		pre = cur;
	}
}

//找后继
void findNext(Tnode* cur,Tnode* target) {
	if (pre->data == target->data) {
		fin = cur;
	}
	else{
		pre = cur;
	}
}

/*
*	找目标节点的先序前驱节点
*/
void findPre_First_Order(Tnode* tree, Tnode* target) {
	if (tree != NULL) {
		findLast(tree, target);
		findPre_First_Order(tree->leftC, target);
		findPre_First_Order(tree->rightC, target);
	}
}

/*
*	找目标节点的中序前驱节点
*/
void findPre_Mid_Order(Tnode* tree, Tnode* target) {
	if (tree != NULL) {
		findPre_Mid_Order(tree->leftC, target);
		findLast(tree, target);
		findPre_Mid_Order(tree->rightC, target);
	}
}

/*
	找目标节点的后续前驱节点
*/

void findPre_Fin_Order(Tnode* tree, Tnode* target) {
	if (tree != NULL) {
		findPre_Fin_Order(tree->leftC, target);
		findPre_Fin_Order(tree->rightC, target);
		findLast(tree, target);
	}
}
//找后继就替换findLast()为findNext

6.二叉树的线索化

6.1先序线索化

/*
	二叉树的先序线索化
*/
void preThread(Tnode* &cur,Tnode* &pre) {
	if (cur != NULL) {	//当前节点不为空

		if (cur->leftC == NULL) {		//如果左孩子为空
			cur->leftC = pre;			//左孩子指向当前节点的前驱
			cur->lTag = 1;				//左标志位值为1
		}

		if (pre!= NULL && pre->rightC == NULL) {	//前驱非空且右孩子为空
			pre->rightC = cur;			//前驱的右孩子指向当前节点
			pre->rTag = 1;				//右标志位值为一
		}

		pre= cur;						//注意这一句是在右孩子线索化之后,也就是第二个if之后,
		if (cur->lTag == 0) {			//这里如果当前节点没有左子树,由于上一步已经将他的左孩子指向了它的前驱,
										//在访问它的左孩子就会访问它的前驱,所以要判断lTag,防止爱迪魔力转圈圈,爱迪魔力转圈圈是先序线索化特有的
			preThread(cur->leftC,pre);
		}
		preThread(cur->rightC,pre);
	}
}

void FirstThread(Tnode* tree) {
	Tnode* pre = new Tnode;//指向当前节点的前驱节点
	if (tree != NULL) {
		preThread(tree,pre);
		if (pre->rightC == NULL) {		//当所有节点都访问完了后,还需要将最右边的节点的右孩子值为NULL,并修改rTag
			pre->rTag = 1;
		}
	}
}

6.2中序线索化


/*
	中序线索化
*/
void inThread(Tnode* &cur,Tnode* pre) {
	if (cur != NULL) {
		inThread(cur->leftC,pre);
		if (cur->leftC == NULL) {
			cur->leftC = pre;
			cur->lTag = 1;
		}
		if (pre != NULL && cur->rightC == NULL) {
			pre->rightC = cur;
			pre->rTag = 1;
		}
		pre = cur;
		inThread(cur->rightC,pre);
	}
}

void CreatInThread(Tnode* tree) {
	Tnode* pre = new Tnode;
	if (tree != NULL) {
		inThread(tree,pre);
		pre->rightC = NULL;
		pre->rTag = 1;
	}
}

6.2后续线索化

/*
	后续线索化 
*/
void finThread(Tnode* &cur,Tnode* &pre) {
	if (cur != NULL) {
		finThread(cur->leftC,pre);
		finThread(cur->rightC, pre);
		if (cur->leftC == NULL) {
			cur->leftC = pre;
			cur->lTag = 1;
		}
		if (pre != NULL && pre->rightC == NULL) {
			pre->rightC = cur;
			pre->rTag = 1;
		}
		pre = cur;
	}
}
void CreatFinThread(Tnode* &tree) { 
	Tnode* pre = new Tnode;
	if (tree != NULL) {
		finThread(tree,pre);
		if (pre->rightC == NULL)
			pre->rTag = 1;
	}
}

7.线索二叉树查找前驱后继

7.1 先序线索二叉树寻找 前驱 后继

//找前驱还是用土方法把

//找后继
Tnode* findNext_preOrder(Tnode* tree) {
	if (tree->rTag == 1) {
		return tree->rightC;
	}
	if(tree->rTag == 0) {
		if (tree->lTag == 0) {
			return tree->leftC;
		}
		else {
			return tree->rightC;
		}
	}
}

7.2 中序线索二叉树寻找 前驱 后继

/*
	线索二叉树寻找中序后继-----------------------------------
*/

//找最左边的节点
Tnode* findLeftLeaf(Tnode* node) {
	while (node->lTag==0) {
		node = node->leftC;
	}
	return node;
}
//找最右边的节点
Tnode* findRightLeaf(Tnode* node) {
	while (node->rTag==0) {
		node = node->rightC;
	}
	return node;
}
//找中序前驱
//一个节点的中序前驱只可能是它的左孩子或者是它左子树上最右下的叶子节点
Tnode* findLast_inOrder(Tnode* &tree) {
	if (tree->rTag == 0) {
		return findRightLeaf(tree->leftC);
	}
	else {
		return tree->leftC;
	}
}

//找中序后继
//一个节点的中序后继只可能是它的右孩子或者它右子树上最左下边的元素
Tnode* findNext_inOrder(Tnode* &tree) {
	if (tree->rTag == 0) {
		return findLeftLeaf(tree->rightC);
	}
	else {
		return tree->rightC;
	}
}
/*
* 利用线索二叉树进行中序遍历
*/
void inOrder_Threaded(Tnode* &tree) {
	for (Tnode* p = findLeftLeaf(tree); p != NULL; p = findNext_inOrder(p)) {
		cout << p->data << " "<<endl;
	}
}

/*
利用线索二叉树进行逆向中序遍历
*/
void inOrerReverse(Tnode* &tree) {
	for (Tnode* p = findRightLeaf(tree); p != NULL; p = findLast_inOrder(p)) {
		cout << p->data << " " << endl;
	}
}

7.3 后续线索二叉树寻找 前驱 后继

/*
	线索二叉树寻找后序后继
*/
//找后继前驱节点
Tnode* findLast_PostOrder(Tnode* &tree) {
	if (tree->lTag == 1) {
		return tree->leftC;
	}
	if(tree->lTag==0){
		if (tree->rTag == 1) {
			return tree->leftC;
		}
		if (tree->rTag == 0) {
			return tree->rightC;
		}
	}
}

//找后续后继节点用土方法吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值