二叉树先序、中序、后序遍历(递归、非递归)及层次遍历以及二叉树线索化以及线索化二叉树六种遍历、并查集(代码演示+注释解析)以及一些真题介绍

文章详细介绍了二叉树的四种遍历方法(先序、中序、后序、层序)的递归和非递归实现,并提供了相应的C++代码示例。此外,还讲解了线索二叉树的概念,如何进行二叉树的线索化以及如何通过线索进行中序、先序和后序的非递归遍历。
摘要由CSDN通过智能技术生成

1.四种遍历

先序遍历 依次遍历:根结点,左子树,右子树
中序遍历 依次遍历:左子树,根结点,右子树
后序遍历 依次遍历:左子树,右子树,根结点
层序遍历:深度优先遍历

这里给出一棵树,进行四种遍历:
在这里插入图片描述
递归:

#include<iostream>
#include<queue>
using namespace std;

typedef char DataType;

//二叉树
typedef struct TreeNode {
	DataType val;
	struct TreeNode* left;
	struct TreeNode* right;
	TreeNode(DataType data) :val(data), left(NULL), right(NULL) {};
}TNode;


//先序遍历:根左右
void PreOrder(TNode* root) {
	if (root == NULL) return;//如果节点为空 则返回上一级

	printf("%c ", root->val);//打印根节点
	PreOrder(root->left);//先遍历左子树
	PreOrder(root->right);//再遍历右子树
}

//中序遍历:左根右
void InOrder(TNode* root) {
	if (root == NULL) return;//如果节点为空 则返回上一级

	InOrder(root->left);//先遍历左子树
	printf("%c ", root->val);//左子树遍历完 打印当前节点
	InOrder(root->right);//再遍历右子树
}

//后序遍历:左右根
void PostOrder(TNode* root) {
	if (root == NULL) return;//如果节点为空 则返回上一级

	PostOrder(root->left);//先遍历左子树
	PostOrder(root->right);//再遍历右子树
	printf("%c ", root->val);//左右子树遍历完 打印当前节点
}

//层序遍历  广度优先遍历
//核心思路: 上一层出的时候带下一层节点进
//这里可以考虑用到数据结构中的队列存储节点
void LevelOrder(TNode* root) {
	queue<TNode*> q;
	if(root) q.push(root);
	while (!q.empty()) {
		TNode* t = q.front();//取出来队列前面的节点
		printf("%c ", t->val);
		q.pop();//删掉
		//上一层出 下一层进
		if (t->left) q.push(t->left);
		if (t->right) q.push(t->right);
	}
}


int main() {
	//节点初始化
	TNode* root = new TNode('A');
	TNode* B = new TNode('B');
	TNode* C = new TNode('C');
	TNode* D = new TNode('D');
	TNode* E = new TNode('E');
	TNode* F = new TNode('F');
	TNode* G = new TNode('G');
	TNode* H = new TNode('H');
	TNode* I = new TNode('I');

	//构建二叉树
	root->left = B;
	root->right = C;
	B->left = D;
	B->right = E;
	D->left = H;
	C->left = F;
	C->right = G;
	F->left = I;

	//先序遍历
	cout << "先序遍历:";
	PreOrder(root);
	puts("");

	//中序遍历
	cout << "中序遍历:";
	InOrder(root);
	puts("");

	//后序遍历
	cout << "后序遍历:";
	PostOrder(root);
	puts("");

	//层序遍历
	cout << "层序遍历:";
	LevelOrder(root);
	puts("");
	
	return 0;
}
先序遍历:A B D H E C F I G
中序遍历:H D B E A I F C G
后序遍历:H D E B I F G C A
层序遍历:A B C D E F G H I

三种遍历非递归:

 //节点总数
int TreeSize(struct TreeNode* root){
    return root==NULL?0:TreeSize(root->left)+TreeSize(root->right)+1;
}
 
//先序
int* preorderTraversal(struct TreeNode* root, int* returnSize){
    if(root==NULL){
        *returnSize = 0;
        return (int*)malloc(sizeof(int)*0);
    }
    int size = TreeSize(root);//获得树的节点数
    int* ans = (int*)malloc(sizeof(int)*size);//开辟数组
    struct TreeNode** s = (struct TreeNode*)malloc(sizeof(struct TreeNode*)*size*2);//模拟栈
    int top = 0;
    int i = 0;
    s[top++] = root;
    while(i<size){
        struct TreeNode* node = s[--top];//出栈
        if(node){
            if(node->right) s[top++] = node->right;
            if(node->left) s[top++] = node->left;
            s[top++] = node;
            s[top++] = NULL;//表明前一个根节点已经访问过子树
        }else{
            ans[i++] = s[--top]->val;
        }
 
    }
    free(s);//释放内存
    *returnSize = size;
    return ans;
}
 
 //中序
int* inorderTraversal(struct TreeNode* root, int* returnSize){
    if(root==NULL){
        *returnSize=0;
        return (int*)malloc(sizeof(int)*0);
    }
    int size = TreeSize(root);
    *returnSize = size;
    int* ans = (int*)malloc(sizeof(int)*size);
    struct TreeNode** s = (struct TreeNode*)malloc(sizeof(struct TreeNode*)*size*2);//模拟栈
    int i = 0;
    int top = 0;//栈顶
    s[top++] = root;
    while(i<size){
        struct TreeNode* node = s[--top];
        if(node){
            if(node->right) s[top++] = node->right;
            s[top++] = node;
            s[top++] = NULL;
            if(node->left) s[top++] = node->left;
        }else{
            ans[i++] = s[--top]->val;
        }
    }
    free(s);
    return ans;
}

//后序
int* postorderTraversal(struct TreeNode* root, int* returnSize){
    if(root==NULL) {
        *returnSize = 0;
        return (int*)malloc(sizeof(int)*0);  
    }
    int size = TreeSize(root);//获得树的节点数
    int* ans = (int*)malloc(sizeof(int)*size);//开辟数组
    struct TreeNode** s = (struct TreeNode*)malloc(sizeof(struct TreeNode*)*size*2);//模拟栈
    int top = 0;
    int i = 0;
    s[top++] = root;
    //先根右左进栈,之后左右根出栈
    //这里注意如果root本来就为空,下面则会发生越界 所以不建议while(top>0) 或者开头便判断是否为空
    while(i<size){
        struct TreeNode* node = s[--top];//弹出栈顶元素 并且pop掉
        if(node){//元素不为NULL
            s[top++] = node;
            s[top++] = NULL;//NULL标记前面根节点已经被访问
            if(node->right) s[top++] = node->right;
            if(node->left) s[top++] = node->left;
        }else{
            ans[i++] = s[--top]->val;
        }
        
    }
    free(s);//释放内存
    *returnSize = size;
    return ans;
}

王道版本非递归:

//节点总数
int TreeSize(struct TreeNode* root){
    return root==NULL?0:TreeSize(root->left)+TreeSize(root->right)+1;
}

//先序
int* preorderTraversal(struct TreeNode* root, int* returnSize){
    if(root==NULL){
        *returnSize = 0;
        return (int*)malloc(sizeof(int)*0);
    }
    int size = TreeSize(root);//获得树的节点数
    int* ans = (int*)malloc(sizeof(int)*size);//开辟数组
    struct TreeNode** s = (struct TreeNode*)malloc(sizeof(struct TreeNode*)*size*2);//模拟栈
    int top = 0;
    int i = 0;
    struct TreeNode* node = root;
    while(i<size){
        if(node){
            ans[i++] = node->val;
            s[top++] = node;
            node = node->left;
        }else{
           node = s[--top];
           node = node->right; 
        }
    }
    free(s);//释放内存
    *returnSize = size;
    return ans;
}

//中序
int* inorderTraversal(struct TreeNode* root, int* returnSize){
    if(!root){//空树
        *returnSize = 0;
        return (int*)malloc(sizeof(int*)*0);
    }
    int size = TreeSize(root);
    *returnSize = size;
    int* ans = (int*) malloc(sizeof(int)*size);
    struct TreeNode** s = (struct TreeNode*)malloc(sizeof(struct TreeNode*)*size*2);//模拟栈
    int i = 0;
    int top = 0;//栈顶
    struct TreeNode* node = root;
    while(i<size){
        if(node){//一直检索左孩子
            s[top++] = node;//入栈
            node = node->left;//一路向左
        }else{//出栈
            node = s[--top];//栈顶元素出栈
            ans[i++] = node->val;
            node = node->right;//右子树
        }
    }
    free(s);
    return ans;
}

//后序
int* postorderTraversal(struct TreeNode* root, int* returnSize){
    if(root==NULL) {
        *returnSize = 0;
        return (int*)malloc(sizeof(int)*0);  
    }
    int size = TreeSize(root);//获得树的节点数
    int* ans = (int*)malloc(sizeof(int)*size);//开辟数组
    struct TreeNode** s = (struct TreeNode*)malloc(sizeof(struct TreeNode*)*size*2);//模拟栈
    int top = 0;
    int i = 0;
    struct TreeNode* node = root;
    struct TreeNode* r = NULL;
    while(i<size){
        if(node){
            s[top++] = node;
            node = node->left;
        }
        else{
            node = s[top-1];
            if(node->right&&r!=node->right){//防止重复遍历右子树
                node = node->right;
            }
            else{
                r = s[--top];
                ans[i++] = r->val;
                node = NULL;
            }
        }
    }
    free(s);//释放内存
    *returnSize = size;
    return ans;
}

2.线索二叉树

(1)二叉树线索化

//线索二叉树
typedef struct ThreadTree{
	DataType val;
	struct ThreadTree* left;
	struct ThreadTree* right;
	int ltag,rtag;
}ThreadTree;

//全局变量,指向当前访问节点前驱
ThreadTree* pre = NULL;

void visit(ThreadTree* p){
	//空链线索化
	//当前左前驱
	if(p->left==NULL){
		p->ltag = 1;
		p->left = pre;
	}
	//pre右后续
	if(pre!=NULL&&pre->right==NULL){
		pre->rtag = 1;
		pre->right = p;
	}
	pre = p;
	//开始访问下一节点
}

//先序遍历二叉树,同时进行线索化
//这里跟先序遍历没有太大区别,只是在访问的过程中对节点线索化
void PreThreadTree(ThreadTree* p){
	if(!p) return ;
	visit(p);
	//易错点
	//注意前面visit可能已经修改了节点左孩子指向前驱,避免循环在这里需要判断
	if(p->ltag==0) PreThreadTree(p->left);
	PreThreadTree(p->left);
	PreThreadTree(p->right);
}

//中序遍历二叉树,同时进行线索化
//这里跟中序遍历没有太大区别,只是在访问的过程中对节点线索化
void InThreadTree(ThreadTree* p){
	if(!p) return ;
	InThreadTree(p->left);
	visit(p);
	InThreadTree(p->right);
}

//后序遍历二叉树,同时进行线索化
//这里跟后序遍历没有太大区别,只是在访问的过程中对节点线索化
void PostThreadTree(ThreadTree* p){
	if(!p) return ;
	InThreadTree(p->left);
	InThreadTree(p->right);
	visit(p);
}

int main(){
	TreadTree* root;
	init(root);//初始化树
	//先遍历线索化
	if(root){
		PreThreadTree(root);
		//注意处理先序遍历的最后一个节点,后续为空
		pre->rtag = 1;
		pre->right = NULL;
	}
	//中序遍历线索化
	if(root){
		InThreadTree(root);
		//注意处理中序遍历的最后一个节点,后续为空
		pre->rtag = 1;
		pre->right = NULL;
	}
	//后序遍历线索化
	if(root){
		PostThreadTree(root);
		//注意处理后序遍历的最后一个节点,后续为空
		if(!pre){
			pre->rtag = 1;
			pre->right = NULL;
		}
	}
}

(2)线索二叉树遍历

①中序线索二叉树遍历

图解:
在这里插入图片描述在这里插入图片描述
代码:

typedef struct ThreadTree{
	DataType val;
	struct ThreadTree* left;
	struct ThreadTree* right;
	int ltag,rtag;
}ThreadTree;

//中序线索二叉树找中序后续
//相当于非递归实现中序遍历
//首先找到以p为根的子树中第一个被中序遍历的节点
ThreadTree* FirstNode(ThreadTree* p){
	//循环找到最左下节点,即中序遍历的首节点
	while(p->ltag==0) p = p->left;
	return p;
}
//寻找节点p的后续节点
ThreadNode* NextNode(ThreadTree* p){
	//若节点p的右子树有孩子,则无法找到直接后续,需要中序遍历右子树,寻找右子树最左下节点(即后续)
	if(p->rtag==0) return FirstNode(p->right);
	//rtag==1直接得到后续
	return p->right;
}
//通过找中序后续实现中序遍历(利用线索实现非递归中序遍历)
void InOrder(ThreadTree* root){
	for(Thread* p = FirstNode(root);p!=NULL;p = NextNode(p)){
		visit(p);
	}
}


//中序线索二叉树找中序前驱
//相当于非递归实现逆中序遍历
//首先找到以p为根的子树中最后一个被中序遍历的节点
ThreadTree* LastNode(ThreadTree* p){
	//循环找到最右下节点(不一定是叶子结点)左根右(左根)
	while(p->rtag==0) p = p->right;
	return p;
/*这段代码我最开始理解错了,想着应该是叶子结点,但是是左根右,只要是最右下就行了,现在找到了最右下,即使有左孩子,此时根据中序特征,也可知左子树优先于根。这里是对应先序遍历的最后一个叶子结点。
	//此时已经是最后一个叶子节点,即先序遍历的最后一个节点
	if(p->ltag&&p->rtag)  return p;
	//此节点还有右子树
	if(p->rtag==0) return LastNode(p->right);
	//此根节点还有左子树
	if(p->ltag==0) return LastNode(p->left); 
*/
}

//寻找节点p的前驱节点
ThreadNode* PreNode(ThreadTree* p){
	//若节点p的左子树有孩子,则无法找到直接前驱,需要中序遍历左子树,寻找右子树最右下节点(即前驱)
	if(p->ltag==0) return LastNode(p->left);
	//ltag==1直接得到前驱
	return p->left;
}
//通过找中序前驱实现逆中序遍历
void RevInOrder(ThreadTree* root){
	for(Thread* p = LastNode(root);p!=NULL;p = PreNode(p)){
		visit(p);
	}
}

②先序线索二叉树遍历

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

在这里插入图片描述
代码:

typedef struct ThreadTree{
	DataType val;
	struct ThreadTree* left;
	struct ThreadTree* right;
	struct ThreadTree* parent;
	int ltag,rtag;
}ThreadTree;

//先序线索二叉树找先序后续
//相当于非递归实现先序遍历
//首先找到以p为根的子树中第一个被先序遍历的节点,即根节点
//寻找节点p的后续节点
ThreadNode* NextNode(ThreadTree* p){
	//若节点p的右子树有孩子,则无法找到直接后续
	if(p->rtag==0){
		//若有左孩子,则后续为左孩子
		if(!p->ltag) retuen p-left;
		//若无则后续为右孩子
		return p->right;//这步可省略
	}
	//rtag==1直接得到后续
	return p->right;
}
//通过找先序后续实现先序遍历(利用线索实现非递归先序遍历)
void PreOrder(ThreadTree* root){
	for(ThreadTree* p = root;p!=NULL;p = NextNode(p)){
		visit(p);
	}
}


//先序线索二叉树找先序前驱
//相当于非递归实现逆先序遍历
//首先找到以p为根的子树中最后一个被先序遍历的节点(这里注意与上面中序遍历找最后一个节点的区别)
ThreadTree* LastNode(ThreadTree* p){
	//循环找到最右下节点(一定要是叶子结点),因为根左右,此时只是最右下但是有左孩子则并不满足条件(根左),需要找到左子树的最后一个右叶子结点。
	//此时已经是最后一个叶子节点,即先序遍历的最后一个节点
	if(p->ltag&&p->rtag)  return p;
	//此节点还有右子树
	if(p->rtag==0) return LastNode(p->right);
	//此根节点(即最右的非叶子节点)还有左子树
	if(p->ltag==0) return LastNode(p->left); 
}
//寻找节点p的前驱节点
ThreadNode* PreNode(ThreadTree* p){
	//若节点p的左子树有孩子,则无法找到直接后续
	if(p->ltag==0){//满足右左根
		ThreadTree* par = p->parent;//父节点
		if(par){//能找到父节点
			//若p是左孩子或者p是右孩子且父节点左孩子为空,则父节点为其前驱
			if(par->left==p||par->ltag) rerurn par;
			//p不为左孩子且左孩子非空
			return par->left;
		}
		//找不到父节点,说明已遍历到根节点,p没有前驱
		return NULL;
	}
	//ltag==1直接得到前驱
	return p->left;
}
//通过找先序前驱实现逆先序遍历
void RevPreOrder(ThreadTree* root){
	for(ThreadTree* p = LastNode(root);p!=NULL;p = PreNode(p)){
		visit(p);
	}
}

③后序线索二叉树遍历

图解:
在这里插入图片描述
在这里插入图片描述
代码:

typedef struct ThreadTree{
	DataType val;
	struct ThreadTree* left;
	struct ThreadTree* right;
	struct ThreadTree* parent;
	int ltag,rtag;
}ThreadTree;

//后序线索二叉树找后序后续
//相当于非递归实现后序遍历
//首先找到以p为根的子树中第一个被后序遍历的节点
ThreadTree* FirstNode(ThreadTree* p){
	//循环找到最左下节点,即后序遍历的首节点
	while(p->ltag==0) p = p->left;
	return p;
}
//寻找节点p的后续节点
ThreadNode* NextNode(ThreadTree* p){//左右根
	//若节点p的右子树有孩子,则无法找到直接后续
	if(p->rtag==0){
		//后序遍历中左右子树节点只可能是根的前驱,不可能是后续
		ThreadTree par = p->parent;//找p的父节点
		if(par){//p的父节点不为空
			//如果p是左孩子且右兄弟为空||p是右孩子,则父节点即为后续
			if((par->left==p&&par->rtag)||par->right==p) return par;
			//如果p是左孩子且右兄弟非空,则后续为右兄弟子树后序遍历的第一个节点
			return FirstNode(par->right);
		}
		//par为空说明p为根节点,p无后续
		return NULL;
	}
	//rtag==1直接得到后续
	return p->right;
}
//通过找先序后续实现先序遍历(利用线索实现非递归先序遍历)
void PostOrder(ThreadTree* root){
	for(ThreadTree* p = FirstNode(p);p!=NULL;p = NextNode(p)){
		visit(p);
	}
}


//后序线索二叉树找后序前驱
//相当于非递归实现逆后序遍历
//首先找到以p为根的子树中最后一个被后序遍历的节点,即根节点
//寻找节点p的前驱节点
ThreadNode* PreNode(ThreadTree* p){
	//若节点p的左子树有孩子,则无法找到直接前驱
	if(p->ltag==0){
		//如果p有右孩子,则前驱为p->right
		if(!p->rtag) return p->right;
		//若p无右孩子则前驱为其左孩子
		rerturn p-left;//此步可省略
	}
	//ltag==1直接得到前驱
	return p->left;
}
//通过找后序前驱实现逆后序遍历
void RevPostOrder(ThreadTree* root){
	for(ThreadTree* p = root;p!=NULL;p = PreNode(p)){
		visit(p);
	}
}

下面这些题值得一看,测试自己,知识点可融会贯通

3.关于二叉树的遍历的练习题

  1. 清华大学上机复试二叉树遍历
  2. 根据先序和中序遍历输出后序遍历
  3. 根据后序和中序遍历输出先序遍历
  4. 根据后序和中序遍历输出层序遍历
  5. 二叉树的三种遍历(递归、非递归)

4.二叉树的一些拓展练习题

  1. 二叉树的最大深度
  2. 二叉树的叶子结点个数
  3. 列出叶节点
  4. 二叉树中和为目标值的路径
  5. 翻转二叉树

5.并查集

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

const int  N=1005					//指定并查集所能包含元素的个数
int pre[N];     					//存储每个结点的前驱结点,即父节点 
int rank[N];    					//树的高度 

//双亲表示法
//初始化函数,对n个结点进行初始化 
void init(int n)     				
{
    for(int i = 0; i < n; i++){
        pre[i] = i;     			//每个结点的父节点都是自己 
        rank[i] = 1;    			//每个结点构成的树的高度为 1 
    } 
}

 //查找结点x的根结点,即代表元
int find(int x)     	 		   
{
    if(pre[x] == x) return x;  		//递归出口:x的父节点为 x本身,则x为根结点 
    return find(pre[x]); 			//递归查找 
} 
 //改进查找算法:完成路径压缩,将x的前驱直接变为根结点
 //先找到根节点,再将此路径上所有节点前驱都改为根节点
int find(int x)     				
{
    if(pre[x] == x) return x;		//递归出口:x的父节点为 x本身,即x为根结点 
    return pre[x] = find(pre[x]);   //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx 
} 

//判断两个结点是否连通 
bool isSame(int x, int y)      		
{
	//判断两个结点的根结点(即代表元)是否相同 
    return find(x) == find(y);  	
}

//合并算法
void join(){
	x = find(x);						//寻找 x的代表元
    y = find(y);						//寻找 y的代表元
    if(x == y) return ;			//如果 x和 y的代表元一致,说明他们共属同一集合,则不需要合并,返回,表示合并失败;否则,执行下面的逻辑
    pre[x] = y;							//让x的根为y
}


/*
改进合并算法(加权标记法)
rank记录树高,小树合并到大树
加权标记法的核心在于对rank数组的逻辑控制,其主要的情况有:
	1、如果rank[x] < rank[y],则令pre[x] = y;
	2、如果rank[x] == rank[y],则可任意指定上级;
	3、如果rank[x] > rank[y],则令pre[y] = x;
*/
void join(int x,int y)
{
    x = find(x);						//寻找 x的代表元
    y = find(y);						//寻找 y的代表元
    if(x == y) return ;			//如果 x和 y的代表元一致,说明他们共属同一集合,则不需要合并,返回,表示合并失败;否则,执行下面的逻辑
    if(rank[x] > rank[y]) pre[y]=x;		//如果 x的高度大于 y,则令 y的根为 x
    else								//否则
    {
        if(rank[x]==rank[y]) rank[y]++;	//如果 x的高度和 y的高度相同,则令 y的高度加1
        pre[x]=y;						//让x的根为y
	}
}

  • 8
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值