数据结构—二叉树总结

  • 写在前面
  • 二叉树遍历
    • 递归实现先、中、后序遍历
    • 非递归遍历
      • 先序非递归
      • 中序非递归
      • 后序非递归
      • 层次遍历
  • 二叉树还原
    • 先序中序建树
    • 后序中序建树
    • 层次中序建树
  • 二叉树应用
    • 二叉查找树
    • 平衡二叉树(AVL树)
    • 并查集
    • 哈夫曼树
  • 参考资料

写在前面

  1. 树的定义:树是n(n>=0)个结点的有限集合,n=0时,称为空树。在任意一棵非空树中应满足:①有且仅有一个特定的称为的结点。②当n大于1时,其余结点可以分为m(m>0)个互不相交的有限集合,其中每一个集合本身又是一棵树,并且称为根节点的子树。树是一种递归的数据结构。

  2. 二叉树的定义:二叉树是n(n>=0)个结点的有限集合:①n=0时,为空二叉树。②n>0时,由一个根结点和两个互不相交的被称为根的左子树和右子树组成。右子树和左子树分别是一棵二叉树。二叉树是有序树,若将其左右子树颠倒,则成为另一棵不同的二叉树。

  3. 二叉树和度为2的有序树的区别:①度为2的树至少有三个结点,而二叉树可以为空。②度为2的有序树的孩子结点的左右次序是相对的,而二叉树的孩子结点的左右次序是绝对的。



二叉树遍历

二叉树的遍历是指通过一定的顺序访问二叉树的所有结点。遍历方法一般有四种:先序遍历、中序遍历、后序遍历以及层次遍历,其中前三种一般用深度优先搜索(DFS)实现,而层次遍历一般用广度优先搜索(BFS)实现。另外前三种也可以通过递归实现。

递归实现先、中、后序遍历

先序:
void preOrder1(BinTree *root) //递归先序遍历
{if(root!=NULL){cout<<root->data<<" ";preOrder1(root->lchild);preOrder1(root->rchild);}
}
中序:
void inOrder1(BinTree *root) //递归中序遍历
{if(root!=NULL){inOrder1(root->lchild);cout<<root->data<<" ";inOrder1(root->rchild);}
}
后序:
void postOrder1(BinTree *root) //递归后序遍历
{if(root!=NULL){postOrder1(root->lchild);postOrder1(root->rchild);cout<<root->data<<" ";}
}

非递归遍历

先序、中序、后序是通过栈实现非递归。
层次遍历是通过队列实现

先序非递归

void preOrder2(BinTree *root) //非递归先序遍历
{stack<BinTree*> s;BinTree *p=root;while(p!=NULL||!s.empty()){while(p!=NULL){cout<<p->data<<" ";s.push(p);p=p->lchild;}if(!s.empty()){p=s.top();s.pop();p=p->rchild;}}
}

中序非递归

void inOrder2(BinTree *root) //非递归中序遍历
{stack<BinTree*> s;BinTree *p=root;while(p!=NULL||!s.empty()){while(p!=NULL){s.push(p);p=p->lchild;}if(!s.empty()){p=s.top();cout<<p->data<<" ";s.pop();p=p->rchild;}}
}

后序非递归

这里有两种思路。
第一种是要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。

void postOrder3(BinTree *root) //非递归后序遍历
{stack<BinTree*> s;BinTree *cur; //当前结点BinTree *pre=NULL; //前一次访问的结点s.push(root);while(!s.empty()){cur=s.top();if((cur->lchild==NULL&&cur->rchild==NULL)||(pre!=NULL&&(pre==cur->lchild||pre==cur->rchild))){cout<<cur->data<<" "; //如果当前结点没有孩子结点或者孩子节点都已被访问过s.pop();pre=cur;}else{if(cur->rchild!=NULL)s.push(cur->rchild);if(cur->lchild!=NULL)s.push(cur->lchild);}}
}

第二种思路是对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。

void postOrder2(BinTree *root) //非递归后序遍历
{stack<BTNode*> s;
BinTree *p=root;
BTNode *temp;
while(p!=NULL||!s.empty())
{while(p!=NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点
{BTNode *btn=(BTNode *)malloc(sizeof(BTNode));btn->btnode=p;btn->isFirst=true;s.push(btn);p=p->lchild;
}
if(!s.empty())
{temp=s.top();s.pop();if(temp->isFirst==true) //表示是第一次出现在栈顶{temp->isFirst=false;s.push(temp);p=temp->btnode->rchild;}else //第二次出现在栈顶{cout<<temp->btnode->data<<" ";p=NULL;}
}
}
}

层次遍历

void LevelorderTraversal ( BinTree BT )
{Queue Q;BinTree T;if ( !BT ) return; /* 若是空树则直接返回 */Q = CreatQueue(); /* 创建空队列Q */AddQ( Q, BT );while ( !IsEmpty(Q) ) {T = DeleteQ( Q );printf("%d ", T->Data); /* 访问取出队列的结点 */if ( T->Left )   AddQ( Q, T->Left );if ( T->Right )  AddQ( Q, T->Right );}
}

二叉树还原

二叉树还原是指根据二叉树的遍历序列构造一棵二叉树。还原二叉树有一个结论:中序序列可以与先序序列、后序序列、层次序列中的任意一个来构建唯一的二叉树,而后三者两两搭配或是三个一起都无法构建唯一一棵二叉树。原因是,先序、后序、层次均是提供根节点,作用是相同的,都必须由中序序列来区分出左右子树。

先序中序建树

//先序 中序建树 返回根节点 输出后序
Node preIn(int preL,int preH, int inL,int inH)
{if(preL>preH)return NULL; //先序序列为空,到达递归边界Node root = new node;root->data = pre[preL];int k=inL;for(k=inL;k<=inH;k++){if(in[k]==pre[preL]){break;}}//左子树先序是[preL+1,preL+k-inL],中序是[inL,k-1]root->left = preIn(preL+1,preL+k-inL,inL,k-1);//右子树先序是[preL+k-inL+1,preH],中序是[k+1,inH].root->right = preIn(preL+k-inL+1,preH,k+1,inH);return root;
}
int num=0;
void postPrint(Node root)
{if(root==NULL)return;postPrint(root->left);postPrint(root->right);post[num++]=root->data;return;
}

后序中序建树

//后序 中序建树 返回根节点 输出先序Node postIn(int inL,int inH,int postL,int postH)
{if(postL>postH)return NULL;Node root = new node;root->data = post[postH];int k= inL;for(k = inL;k<=inH;k++){if(in[k]==post[postH]){break;}}//左子树 中序[inL,k-1], 后序[postl,postL+k-inL-1]root->left = postIn(inL,k-1,postL,postL+k-inL-1);//右子树 中序[k+1,inH] , 后序[postL+k-inL,postH-1]root->right = postIn(k+1,inH,postL+k-inL,postH-1);return root;
}void prePrint(Node root) //num initiate 0
{if(root==NULL)return;pre[num++]=root->data;prePrint(root->left);prePrint(root->right);return;
}

层次中序建树

//层级 中序建树 返回根节点
Node levelIn(vector<int>layer,int inL,int inH)
{if(layer.size()==0)return NULL;Node root = new node;root->data = layer[0];int k;for(k=inL;k<=inH;k++){if(in[k]==layer[0]){break;}}vector<int>layerLeft;vector<int>layerRight;for(int i=1;i<layer.size();i++){bool isLeft = false;for(int j=inL;j<k;j++){if(layer[i]==in[j]){isLeft = true;break;}}if(isLeft){layerLeft.push_back(layer[i]);}else{layerRight.push_back(layer[i]);}}root->left = levelIn(layerLeft,inL,k-1);root->right = levelIn(layerRight,k+1,inH);return root;
}

二叉树应用

二叉树的应用主要有 二叉查找树(BST),平衡二叉树(AVL),哈夫曼树,堆和并查集。

二叉查找树

二叉查找树(Binary Search Tree),是一种特殊的二叉树,又称为排序二叉树、二叉搜索树、二叉排序树。其递归定义如下:①要么二叉查找树是一棵空树。②要么二叉查找树由根结点、左子树、右子树组成,其中左子树和右子树均是二叉查找树,且左子树上的所有结点的数据域均小于或等于根结点的数据域,右子树上的所有结点的数据域均大于根结点的数据域。
其中需要注意的是,即便是一组相同的数字,如果插入它们的顺序不同,最后生成的二叉查找树也不相同
另外,二叉查找树有一个实用的性质:对二叉查找树进行中序遍历,遍历的结果是有序的

平衡二叉树(AVL树)

平衡二叉树是由前苏联两位数学家提出,也称为AVL树。AVL树仍是一棵二叉查找树,只是在其基础上增加了“平衡”的要求。所谓平衡是指,对AVL树的任意结点来说,其左子树和右子树的高度之差的绝对值不超过1,其中左右子树的高度差称为该结点的平衡因子。平衡因子可以借助子树的高度间接求出。
AVL树插入时需要调整结点以满足平衡,具体调整情况如下表(BF表示平衡因子):

树形 判定条件 调整方法
LL BF(root)=2,BF(root->lchild)=1 对root进行右旋
LR BF(root)=2,BF(root->lchild)=-1 先对root->lchild进行左旋,再对root进行右旋
RR BF(root)=-2,BF(root->rchild)=-1 对root进行左旋
RL BF(root)=-2,BF(root->rchild)=1 先对root->rchild进行右旋,再对root进行左旋

并查集

并查集是一种维护集合的数据结构,支持以下两个操作:①合并,合并两个集合。②查找:判断两个元素是否在一个集合。
并查集的实现,是通过一个数组实现的,即int father[N]; 其中father[i]表示元素i的父亲结点,而父亲结点本身也是这个集合内的元素。如果father[i]==i,则说明元素i是该集合的根结点,但对同一个集合来说,只能存在一个根结点,且将其作为所属集合的标识
并查集的一个性质,同一集合中一定不会产生环,即并查集产生的每一个集合都是一棵树

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子结点的值,那么称这样的堆为大顶堆,这时每个结点的值都是以它为根结点的子树的最大值;如果父亲结点的值小于或等于孩子结点的值,那么称这样的堆为小顶堆,这时每个结点的值都是以它为根结点的子树的最小值。

哈夫曼树

从树根结点到任意结点的路径长度(经过的边数)与该结点上权值的乘积称为该结点的带权路径长度。树中所有叶结点的带权路径长度之和称为该树的带权路径长度(WPL)。
在含有N个带权叶子节点的二叉树中,其中带权路径长度最小的二叉树称为哈夫曼树,也称为最优二叉树


参考资料

  • 《数据结构考研复习指导》 王道论坛组编 电子工业出版社
  • (二叉树的非递归遍历,作者:海子)https://www.cnblogs.com/dolphin0520/archive/2011/08/25/2153720.html
  • 《算法笔记》 胡凡 曾磊主编 机械工业出版社

数据结构——二叉树总结相关推荐

  1. 数据结构 -- 二叉树

          这篇文章介绍的是经典的数据结构--二叉树,在这篇文章里介绍了几乎二叉树的所有操作.       二叉树给我们最重要的印象莫过于递归,因为这棵树就是递归的,所以,我在解决各个问题时大部分都用 ...

  2. 数据结构 - 二叉树 - 面试中常见的二叉树算法题

    数据结构 - 二叉树 - 面试中常见的二叉树算法题 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图 ...

  3. 数据结构——二叉树的递归算法

    二叉树的结构定义: typedef struct BiNode {TElemType data;struct BiNode *lchild;struct BiNode *rchild; }BiNode ...

  4. 数据结构——二叉树的层次遍历进阶

    之前的一个博客 数据结构--二叉树的层次遍历看完这个,可以简单实现下面的问题 问题: 1.计算二叉树的最大宽度(二叉树的最大宽度是指二叉树所有层中结点个数的最大值. 2.用按层次顺序遍历二叉树的方法, ...

  5. 数据结构----二叉树叶子结点到根节点的高度计算

    数据结构----二叉树叶子结点到根节点的高度计算 代码: #include<stdio.h> #include<stdlib.h> typedef struct bstTree ...

  6. 数据结构 二叉树的存储结构_线程二叉树| 数据结构

    数据结构 二叉树的存储结构 线程二叉树 (Threaded Binary Tree ) A binary tree can be represented by using array represen ...

  7. 二叉树----数据结构:二叉树的三种遍历及习题

    二叉树----数据结构:二叉树的三种遍历,利用递归算法. 关于二叉树的遍历,应用非常广泛,不单单是访问打印结点,还可以进行一系列的操作,如赋值.删除.查找.求二叉树的深度等等. 有递归和非递归两种算法 ...

  8. 数据结构-二叉树入门Go语言实现

    数据结构-二叉树入门Go语言实现 之前我们一直在谈的是一对一的线性结构,可现实中,还有很多一对多的情况需要处理,所以我们需要研究这种一对多的数据结构--"树",考虑它的各种特性,来 ...

  9. 数据结构——二叉树——特点及性质

    数据结构--二叉树--特点及性质 二叉树(Binary Tree)是n(n=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的.分别称为根结点的左子树和右子树的二 ...

最新文章

  1. HTML5---19.地理定位的接口使用
  2. 女神节爆猛料!. NET程序员男女比例公布!
  3. vue 传递 对象 路由_vue中路由参数传递可能会遇到的坑
  4. Android视频点播-边播边缓存-方案
  5. Insus NET Utility
  6. 2022计算机三级数据库总结和经验(有免费题库)
  7. 2020年系统集成项目管理工程师课教程
  8. 1068 万绿丛中一点红 (20分)测试点分析
  9. FireFox火狐浏览器配置页面about:config参数说明
  10. Android 通过AlarmClock设置系统闹钟
  11. day04 java学习
  12. java web 蓝牙打印_android 蓝牙打印机示例
  13. 进程创建的优化设计(上)
  14. redis放入对象的几种方式
  15. 浅谈STM32的三种Boot模式
  16. BeautifulSoup基础学习笔记
  17. 单片机17种常见的电路设计模块
  18. A卡速度表以及软件的设置参数(二…
  19. matplotlib显示灰度图为紫色的问题
  20. windows创建软链接

热门文章

  1. Skia深入分析8——Skia的GPU绘图
  2. centos7的syslog知识点
  3. fwrite和fread函数的用法小结
  4. 【spring Cloud 入门-4】简单的服务实例健康自检
  5. oul可以用作c语言常量吗,吉大15秋学期《C语言程序设计》在线作业二 答案
  6. HTML特殊转义字符
  7. 【汇正财经】什么是成长投入策略?
  8. 【堆】 大根堆和小根堆的建立
  9. C# Excel 数据导入mysql数据库
  10. $ 8 : gets函数与puts函数