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