网站如何做手机端页面,中国建设银行分行网站,长沙诚信做网站,智联招聘网站怎么做两份简历模板二叉树可以处理每个阶段都是两种结果的情形#xff0c;如#xff1a;大和小#xff0c;0和1#xff0c;真和假等二叉树的定义
二叉树特点#xff1a;
每个节点最多有两颗子树是有序树#xff0c;即左子树和右子树有次序
特殊二叉树
斜二叉树
特殊的表现为线性表
满二叉树…二叉树可以处理每个阶段都是两种结果的情形如大和小0和1真和假等二叉树的定义二叉树特点每个节点最多有两颗子树是有序树即左子树和右子树有次序特殊二叉树斜二叉树特殊的表现为线性表满二叉树所有分支节点都有左子树和右子树所有叶子节点在同一层完全二叉树适合用顺序结构即数组表示叶子节点只会出现在最下面两层若有n个节点其深度为[log2 n]1[]表示取整若从1开始编号对于节点i若左子树存在左儿子序号下标为i*2若右子树存在右儿子序号下标为i*21若节点存在双亲则双亲为i/2需要取整若从0开始编号则分别为i*21i*22(i-1)/2从根节点一直沿着左儿子遍历并计数可以得到树的高度计算完全二叉树的节点计算左子树的高度lheight计算右子树满树区域的高度rheight若lheight和rheight相等说明这是一颗满二叉树直接用公式n2^h -1若lheight和rheight不相等递归计算左子树和右子树的节点个数计算height的时间复杂度O(logN)由于完全二叉树的左子树和右子树至少有一颗是满二叉树所以只会有一颗子树继续递归另一颗子树满足lheightrheight的条件就退出递归同样的不是满二叉树的那颗子树也是一颗完全二叉树所以需要递归的时间复杂度为O(logN)每次递归加上计算height的时间总的算法时间复杂度就是O(logN* logN)int getNodeNum(TreeNode* root){ int lheight0,rheight0; TreeNode *proot-left; TreeNode *qroot-right; while(p){ lheight; pp-left; //沿左儿子走 } while(q){ rheight; qp-right; //沿右儿子走 } if(lheightrheight){ //root是满二叉树 return (1(lheight1))-1; } else return getNodeNum(root-left)getNodeNum(root-right)1; }二叉树的普适性质二叉树第i层最多有2^(i-1)个节点深度为k的二叉树最多有2^k -1个节点证明等比数列求和二叉树的边数等于节点数-1n0度为0的节点n1度为1的节点n2度为2的节点则n0n21证明n0n1n2-10*n01*n12*n2二叉树的顺序存储结构从上到下从左到右将二叉树的节点放在数组中适合完全二叉树节点与其双亲儿子的下标对应关系与完全二叉树相同[[2 二叉树的定义和性质#完全二叉树]]二叉树的链式存储结构用二叉链表储存每个节点含一个data域两个指针域分别是左儿子left右儿子right根据需要可增加双亲域parenttypdef int DataType typedef struct treenode_{ DataType data; struct treenode_ *left,*right; }TreeNode;左儿子或右儿子不存在就设置为NULL二叉树的遍历三种遍历经过的路线一样只是每个节点的访问时机不同一般来说默认先遍历左子树再遍历右子树但根据实际情况不同可以先遍历右子树再遍历左子树先序遍历递归先访问根节点再递归遍历左子树再递归遍历右子树递归算法void PreOrderTraversal(TreeNode* t){ if(t){ coutt-data; PreOrderTraversal(t-left); PreOrderTraversal(t-right); } }中序遍历递归先递归遍历左子树再访问根节点再递归遍历右子树递归算法void InOrderTraversal(TreeNode* t){ if(t){ PreOrderTraversal(t-left); coutt-data; PreOrderTraversal(t-right); } }后序遍历递归先递归遍历左子树再递归遍历右子树再访问根节点递归算法void PostOrderTraversal(TreeNode* t){ if(t){ PreOrderTraversal(t-left); PreOrderTraversal(t-right); coutt-data; } }中序遍历非递归基本思想利用指示指针p和stack模拟中序遍历基于指示指针或stack不为空的大循环先让指示指针p沿着左链走到底边走边将指示过的节点push然后若stack不为空就pop并且访问再让p指向该pop出来的节点的右儿子void InOrderTraversal(TreeNode* t){ TreeNode* pt; //指示指针 stackTreeNode* s; //堆栈 while(p||!s.empty()){ //大循环 while(p){ //让p沿着左链走到底并push s.push(p); pp-left; } if(!s.empty()){ //stack不为空就pop并访问 ps.top(); s.pop(); coutp-data; //pop并访问 pp-right; //访问右儿子 } } }迁移中序遍历是出栈时访问根据先序遍历访问节点的时机不同先序遍历非递归算法是在入栈时访问先序遍历非递归void InOrderTraversal(TreeNode* t){ TreeNode* pt; //指示指针 stackTreeNode* s; //创建堆栈 while(p||!s.empty()){ //大循环 while(p){ //让p沿着左链走到底并push s.push(p); coutp-data; //push并访问 pp-left; } if(!s.empty()){ //stack不为空就pop并访问 ps.top(); s.pop(); pp-right; //访问右儿子 } } }层序遍历前三种都是Depth First Search层序遍历是Breadth First Search基本思想利用queue模拟层序遍历每次迭代先pop再将pop出来的节点的所有子节点push当队列为空停止迭代进入迭代前先将根节点pushvoid LevelOrderTraversal(TreeNode* t){ if(!t)return; //空树直接返回 queueTreeNode* q; //创建队列 q.push(t); TreeNode* p; //用于接收pop出来的节点 q.push(t); //先将根节点入队 while(!q.empty()){ pq.front(); q.pop(); coutp-data; //出队并访问 if(p-left)q.push(p-left); //将左儿子入队 if(p-right)q.push(p-right); //将右儿子入队 } }推广若要获得每层最左边的节点假设将每层的最左边的节点储存在vector中可用size记录queue中全部是同一层的节点时的节点个数然后通过size次循环把queue中的节点pop并push其子节点void LevelOrderTraversal(TreeNode* t){ queueTreeNode* q; //创建队列 q.push(t); TreeNode* p; //用于接收pop出来的节点 q.push(t); //先将根节点入队 vectorTreeNode* v; while(!q.empty()){ int sizeq.size(); v.push_back(q.front()); //此时队列中的第一个节点就是最左节点 while(size--){ pq.front(); q.pop(); coutp-data; //出队并访问 if(p-left)q.push(p-left); //将左儿子入队 if(p-right)q.push(p-right); //将右儿子入队 } } }再推广若要获得每层最右边的节点就先push右子树再push左子树要获得每层单独的层序遍历序列基本思路一样也是利用size记录当前queue的大小推导遍历结果根据中序遍历和先序遍历或中序遍历和后序遍历和推导出二叉树或另一种遍历前提必须有中序遍历基本思想递归处理两个遍历序列相对应的一段先假定遍历序列都储存在数组中注意要检查边界情况根据先序和中序推二叉树关键参数对于待处理的两段相对应的序列段用preL记录先序遍历待处理序列段的左端起始下标用inL记录中序遍历待处理序列段的左端起始下标用len记录待处理序列段的长度可推出先序遍历待处理序列右端下标为preLlen-1记录为preR中序遍历待处理序列右端下标为inLlen-1记录为inR找中序遍历中的根节点对于先序遍历序列段的第一个元素左端该元素就是根节点在中序遍历序列段中通过遍历找到这个元素并记录该元素的下标记录为i找左子树和右子树的先序遍历序列段和中序遍历序列段对于中序遍历根据根节点的下标i和inL和len可以确定左子树序列长度为i-inL记录为leftLen右子树序列长度为inR-i记录为rightLen可推出中序遍历左子树的待处理序列左端为inL右子树的待处理序列左端为i1对于先序遍历可推出先序遍历左子树的待处理序列左端为preL1右子树的待处理序列左端为PreLleftLen1递归处理左子树序列段和右子树序列段边界情况len 0时返回NULL目的是建树所以在每次找到根节点后建一个节点赋为对应的dataTreeNode* solveTree(preL,inL,len){ if(len0)return NULL; //边界情况 int preRpreLlen-1; int inRinRlen-1; DataType rootpreOrder[preL]; TreeNode* tnew TreeNode; //建根节点 t-dataroot; int i; for(iinL;iinR;i){ if(inOrder[i]root)break; } int leftLeni-inL; int rightLeninR-i; t-leftsolveSequence(preL1,inL,leftLen); t-rightsolveSequence(preLleftLen1,i1,rightLen); return t; }根据先序和中序推后序假设将得到的后序遍历序列储存在数组postOrder[]中和根据先序遍历和中序遍历推二叉树不同的是推后序遍历只需要将找到的root放到后序对应的序列段的右端最后一个元素void solveSequence(preL,inL,postL,len){ if(len0)return; //边界情况 int preRpreLlen-1; int inRinRlen-1; int postRpostLlen-1; DataType rootpreOrder[preL]; postOrder[postR]root; int i; for(iinL;iinR;i){ if(inOrder[i]root)break; } int leftLeni-inL; int rightLeninR-i; t-leftsolveSequence(preL1,inL,postL,leftLen); t-rightsolveSequence(preLleftLen1,i1,postLleftLen1,rightLen); return t; }推广由后序遍历和中序遍历推二叉树或先序遍历思路相同只是根节点在后序遍历的右端