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->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.关于二叉树的遍历的练习题
4.二叉树的一些拓展练习题
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
}
}

本文详细介绍了二叉树的四种遍历方法:先序、中序、后序及层序遍历,并提供了递归与非递归实现方式。此外还深入探讨了二叉树的线索化过程以及线索二叉树的六种遍历方法。
4万+

被折叠的 条评论
为什么被折叠?



