二叉树

来源:互联网 发布:网络编辑招聘信息 编辑:程序博客网 时间:2024/05/15 04:10
序:二叉树作为树的一种,是一种重要的数据结构,常见的二叉树有:
满二叉树:除叶子结点外,所有结点都有两个结点,叶子结点的left,right为NULL.
哈夫曼树:又称为最优二叉数,是一种带权路径最短的树。哈夫曼编码就是哈夫曼树的应用,可以用来进行编码压缩.哈夫曼树的构造见哈夫曼树的构造
完全二叉树:除了最底层的叶子结点之外,其余层全满,而且叶子层集中在左端.堆是一种特殊的完全二叉树(全满或者差一个结点就全满)
平衡二叉树:所谓平衡二叉树指的是,左右两个子树的高度差的绝对值不超过 1。包括AVL树,红黑树.
红黑树:具体见红黑树问题
下面是我总结的这几天看过的一些常见二叉树问题.

1.二叉搜索树的迭代构造

二叉搜索树是一棵排好序的树,当新插入一个节点的时候,小于当前根结点左走,大于当前根结点右走,直至走到NULL点,就是该结点的位置.

void IterativeInsert(Tree* T,node* z)//插入节点  {      node* y=NULL;      node* x=T->root;//管理两个指针,父指针y,y的子树指针x      while(x!=NULL)//一直向下遍历到z应该插入的位置      {          y=x;          if(x->value < z->value)              x=x->right;          else x=x->left;      }      z->p=y;//先将z的父指针p指向y      if(y==NULL)//若树为空,树根即为z          T->root=z;      else if(z->value < y->value)//否则分插入左边还是右边          y->left=z;      else y->right=z;  }

2.二叉搜索树的递归构造

1.若是空树,则插入至根结点位置
2.若比当前根点,插入左边.
3.否则插入右边

node* TreeInsert(node* root,node* z){if(!root)root=z;else if(root->value < z->value)root->right=TreeInsert(root->right,z);else root->left=TreeInsert(root->left,z);return root;}

3.二叉树三种递归遍历方式.

void InorderTreeWalk(node* root){if(!root ) return ;InorderTreeWalk(root->left);cout<<root->value<<' ';InorderTreeWalk(root->right);}void PriorTreeWalk(node* root){if(!root ) return ;cout<<root->value<<' ';PriorTreeWalk(root->left);PriorTreeWalk(root->right);}void PostTreeWalk(node* root){if(!root ) return ;PostTreeWalk(root->left);PostTreeWalk(root->right);cout<<root->value<<' ';}

4.二叉树三种迭代遍历方式.

深度优先原则,输出顺序是'左子树优先'

void IterativeInorderWalk(node *root)//迭代中序遍历  {      node* p=root;      stack<node*> st;//利用栈      if(!p)  //if(!p)=if(p==null)        return ;      while(p || !st.empty())//当p不为空 或者st不为空      {          while(p)//沿着左孩子方向走到最左下。同时压栈          {              st.push(p);              p=p->left;          }          p=st.top();//获取栈顶元素          cout<<p->value<<" ";          st.pop();          p=p->right;      }  }  void IterativePriorTreeWalk(node* root)//迭代先序遍历  {      node* p=root;      stack<node* > st;      if(!p)           return ;      while(p || !st.empty())      {          while(p)          {              cout<<p->value<<" ";              st.push(p);              p=p->left;          }          p=st.top();          st.pop();          p=p->right;      }  }  void IterativePostWalk(node* root)  {      node* p=root;      stack<node* > st;      node* pre=NULL;//pre表示最近一次访问的结点      if(!p) return ;      while(p || !st.empty())      {          while(p)    //沿着左孩子方向走到最左下          {              st.push(p);              p=p->left;          }          p=st.top(); //获取栈顶元素          if( !p->right || p->right ==pre)//如果p没有右孩子或者其右孩子刚刚被访问过.小窍门if (!p)表示如果p为空          {              st.pop();              cout<<p->value<<' ';              pre=p;      //最近一次访问的节点是p              p=NULL;     //使下一次循环不再执行p节点以下的压栈操作          }          else              p=p->right;      }  }  

5.怎样从根结点开始逐层打印二叉树结点数据.

广度优先原则,需要用到队列,当访问一个当前节点CurrentNode的时候,将该结点出队,同时将该CurrentNode的左右子结点入队,重复这个操作,直至队列为空.步骤:1初始化队列.2重复出队入队操作直至队列为空.

//从顶部开始逐层打印二叉树结点数据,即广度遍历二叉树,需要用到队列void printHori(node* root){if(!root) return ;queue<node*> Q;Q.push(root);while(!Q.empty()){node* Front=Q.front();Q.pop();cout<<Front->value<<' ';if(Front->left)Q.push(Front->left);if(Front->right)Q.push(Front->right);}}

6.获得树的深度

树的深度=max(左子树深度,右子树深度)+1

//获得树的深度int GetTreeDepth(node* root){if(!root)return 0;int left=GetTreeDepth(root->left);int right=GetTreeDepth(root->right);return 1+(left > right? left:right);//max(左子树,右子树)+1为整棵树高度}

7.如何判断一棵二叉树是否是平衡二叉树.

方法1:最直接的办法:获得左子树深度,获得左右子树深度,判断二者是否相差在1以内,如果是,则说明当前根结点root是平衡.否则可以判断false.继续用同样的办法判断root的左右子树是否也平衡.现在分析一下这种办法的效率:GetTreeDepth()函数类似于树的后序遍历,T(n)=2*T(n/2)+1,由主定理:T(n)=o(n).即,要找到树的深度需要将每个结点全扫描一遍.同样求isTreeBalance(left) 和 isTreeBalance(right)也需要将左右子树重复扫描一遍.这样重重复复扫描树的方式效率是多少?T(N)=2*T(N/2)+N,即T(n)=nlgn.
方法2:为了克服多次扫描节点方法的缺陷,我们试图用空间换时间的做法,或者是自底向上的思维方式.方法的方式是,先求根是否平衡,再求左右子树是否平衡.现在,我可以换一个角度,先求左右子树是否平衡,如果左右子树平衡,再考虑求根结点,否则直接返回false.这种方式需要在左右子树返回根时保存树的深度.即空间换时间.这种方法的复杂度为:T(n)=2*T(n/2)+1.T(n)=n.

//判断树是否平衡bool isTreeBalance(node* root){if(!root) return true ;int left=GetTreeDepth(root->left);int right=GetTreeDepth(root->right);if((left - right) > 1 || (right-left) > 1) return false;return isTreeBalance(root->left) && isTreeBalance(root->right);}//判断树是否平衡bool isBalanceTree(node* root,int& depth){if(!root){depth=0;return true;}int left,right;if(isBalanceTree(root->left,left) && isBalanceTree(root->right,right)){if(left-right <= 1 && right-left <= 1){depth=1+ (left > right ? left:right);return true;}}return false;}

拓展:判断一棵树是否平衡

http://hawstein.com/posts/4.1.html

实现一个函数检查一棵树是否平衡。对于这个问题而言, 平衡指的是这棵树任意两个叶子结点到根结点的距离之差不大于1。

对于这道题,要审清题意。它并不是让你判断一棵树是否为平衡二叉树。 
平衡二叉树的定义为:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 
而本题的平衡指的是这棵树任意两个叶子结点到根结点的距离之差不大于1。 这两个概念是不一样的。例如下图,
它是一棵平衡二叉树,但不满足本题的平衡条件。 (叶子结点f和l到根结点的距离之差等于2,不满足题目条件)
对于本题,只需要求出离根结点最近和最远的叶子结点, 然后看它们到根结点的距离之差是否大于1即可。
假设只考虑二叉树,我们可以通过遍历一遍二叉树求出每个叶子结点到根结点的距离。 使用中序遍历,依次求出从左到右的叶子结点到根结点的距离,递归实现。

/*判断树是否平衡,并不是判断是否是平衡二叉树*/int Max=INT_MIN,Min=INT_MAX,curLen=0;void FindDepth(node* root){if( root==NULL) {return ;}++curLen;FindDepth(root->left);if( root->left ==NULL && root->right ==NULL){if( curLen > Max )Max=curLen;else if( curLen < Min )Min=curLen;}FindDepth(root->right);--curLen;}bool isBalance(node* root){FindDepth(root);return ((Max-Min)<=1);}


8.求二叉树的最低公共祖先LCA

性质:如果两条链有公共祖先,那么公共祖先往上的结点都重合.因为如果x=x',那么x->next=x'->next必然成立.
可能性一:若是二叉搜索树.
1如果x,y小于root,则在左边找
2如果x,y大于root,则在右边找
3如果x,y在root之间,则root就是LCA
可能性二:不是二叉搜索树,甚至不是二叉树,但是,每个一节点都有parent指针
那么解法有2:
1:空间换时间:从x,y到root的链表可以保存在栈中,找出最后一个相同结点即可.
2.不用空间换时间,多重扫描法,x,y到root两条链路可能一长一短,相差为n个结点,那么长链表先前移n步,然后,二者同步前移,找到第一个相同结点即可.(树不含环,这种办法有效.)
可能性三:这是一棵只有left和right的平凡二叉树.
那么需要辅助空间,空间换时间法,先调用16.中的GetNodePath()获得两条从root->x和root->y的链表路径.然后比较两条链表,找到最后一个相同的结点即可.
//若是二叉搜索树,返回x和y的公共最低祖先LCAnode* LowestCommonAncestor1(node* root,node* x,node* y){if(!root || !x || !y) return NULL;if(x->value < root->value && y->value < root->value)return LowestCommonAncestor1(root->left,x,y);else if(x->value > root->value && y->value > root->value)return LowestCommonAncestor1(root->right,x,y);else return root;}//若不是搜索二叉树,但是,每个结点都有父结点,空间换时间法,否则需要重重复复地扫描路径node* LowestCommonAncestor2(node* x,node* y){stack<node*> st1,st2;while(x){st1.push(x);x=x->p;}while(y){st2.push(y);y=y->p;}node* pLCA=NULL;while(!st1.empty() && !st2.empty() && st1.top()==st2.top()){pLCA=st1.top();st1.pop();st2.pop();}return pLCA;}//不用空间换时间法int GetListLength(node* x){int Count=0;while(x){++Count;x=x->p;}return Count;}int Myabs(int val){return val > 0 ? val : -val;}node* LowestCommonAncestor3(node* x,node* y){int LengthX=GetListLength(x);int LengthY=GetListLength(y);node* pLong=x,*pShort=y;if(LengthX < LengthY){pLong=y;pShort=x;}for(int i=0;i<Myabs(LengthX-LengthY);++i)pLong=pLong->p;while( pLong && pShort && pLong !=pShort){pLong=pLong->p;pShort=pShort->p;}if(pLong == pShort)return pLong;return NULL;}//既不是二叉搜索树,也不没有parent指针,只是一棵平凡的二叉树bool GetNodePath(node* root, node* pNode, list<node*>& path);node* LowestCommonAncestor4(node* root,node* x,node* y){list<node*> path1;list<node*> path2;GetNodePath(root,x,path1);GetNodePath(root,y,path2);node* pLCA=NULL;list<node*>::const_iterator it1=path1.begin();list<node*>::const_iterator it2=path2.begin();while(it1 != path1.end() && it2 != path2.end() && *it1 == * it2){pLCA=*it1;++it1;++it2;}return pLCA;}

9.在二叉树中找出和为某一值的所有路径

要求所有路径,路径即root到某一结点的结点之集合.这是一个深度优先原则的搜索.我们很容易想到先序遍历.
为了跟踪路径和,我们需要一个额外的辅助栈来跟踪递归调用栈的操作过程.
在进入到下一个调用FindPath(left)和FindPath(right)时,递归栈会将root压入栈,因此我们也模仿进栈.当FindPath(left)和FindPath(right)返回,FindPath(root)运行周期到之后,局部函数变量root会被析造,root会从递归栈中弹出,因此,我们也从辅助栈中弹出root,只需要在中间加上判断条件,将满足条件的结果输出即可.改成迭代版也很简单.

void FindPath(node* root,int expectedSum,vector<int>& Path,int currentSum);//先序遍历改装版void FindPath(node* root,int expectedSum){int currentSum=0;vector<int> Path;FindPath(root,expectedSum,Path,currentSum);}void FindPath(node* root,int expectedSum,vector<int>& Path,int currentSum)//先序遍历改装版{if(!root)return ;//访问根结点,同时将root->value加入辅助栈currentSum += root->value;Path.push_back(root->value);if(root->left==NULL && root->right ==NULL && currentSum == expectedSum){for(vector<int>::size_type i=0; i < Path.size(); ++i)cout<<Path[i]<<' ';cout<<endl;}FindPath(root->left,expectedSum,Path,currentSum);FindPath(root->right,expectedSum,Path,currentSum);//递归栈中,此时返回时,会将父结点销毁,因为,局部函数生命周期已经到了.//所以辅助栈也需要和递归栈同步,将栈顶元素弹栈,同时当前路径减去栈顶元素Path.pop_back();currentSum-=root->value;}

10.编写一个程序,把一个有序整数数组放到二叉树中

这道题做法非常多,单纯是这么要求比较奇怪,因此,我选择广度优先插入,利用队列实现,其实插成一个链表,或者随便插入不知道可不可以?没有其它要求真不知道怎么弄.

node* HorizInsert(node* root,node* z){    if(!root)    {        root=z;        return root;    }    queue<node*> q;    q.push(root);    while(!q.empty())    {        node* Front=q.front();        q.pop();        if(Front->left==NULL)        {            Front->left=z;            return root;        }        if(Front->right==NULL)        {            Front->right=z;            return root;        }        if(Front->left)            q.push(Front->left);        if(Front->right)            q.push(Front->right);    }    return root;}

11.判断整数序列是不是二叉搜索树的后序遍历结果

典型的递归思维,后序遍历,根在最后,因此用根将二叉搜索树可以分成左右子树,再递归处理左右子树即可.

//判断整数序列是不是二叉搜索树的后序遍历结果  bool isPostTreeWalk(int* Last,int len){if( !Last || len <= 0)return false;int root=Last[len-1];int i=0;while(i < len-1 && Last[i] < root)//寻找左子树划分点++i;for(int j=i ; j < len-1 ; ++j){if(Last[j] < root)//如果右子树中存在node<root,则无法构成后序结果return false;}bool isLastTree=true;if(i > 0) //判断左边是不是后序树isLastTree=isPostTreeWalk(Last,i);//如果左边是后序树,继续判断右边,如果左边已经不是,则右边已经无需判断if(isLastTree && i < len-1 )isLastTree=isPostTreeWalk(Last+i,len-i-1);return isLastTree ;}

12.求二叉树的镜像

画出一个特例,我们发现只需要交换每个结点的左右指针(注意:不是数值)即可.因此在先序遍历的时候换指针即可,没有顺序要求.

//求二叉树的镜像  void MirrorRotate(node* root){if(!root) return ;swap(root->left,root->right);MirrorRotate(root->right);MirrorRotate(root->left);}

13.一棵排序二叉树(即二叉搜索树BST),令 f=(最大值+最小值)/2,设计一个算法,找出距离f值最近、大于f值的结点。复杂度应尽可能低。

BST中最大值是最右边的值,最小值是最左边的值,这样就容易求出f,再求f的父指针或者右指针都可以?(我是这么认为的).这里求父指针.
//找出距离f值最近、大于f值的结点。node* FinClearF(node* root){if(!root) return NULL;node* max,*min;max=min=root;while(min->left)min=min->left;while(max->right)max=max->right;int F=(min->value + max->value) >> 1 ;while(1){if(root->value <= F)root=root->right;else{return root;}}}

14.把二叉搜索树转变成排序的双向链表

其实就是中序遍历的迭代版本,只是将中间的访问结点代码换成了调整指针.这种办法返回的是链表的最后一个指针LastVist,因为是双链表,这也可以接受.
//迭代法node* iTreeToList(node* root){node* p=root;stack<node*> st;node* LastVist=NULL;while(p || !st.empty()){while(p){st.push(p);p=p->left;}p=st.top();st.pop();//cout<<p->value<<' ';if(LastVist){LastVist->right=p;p->left=LastVist;}LastVist=p;p=p->right;}return LastVist;}

15.打印二叉树中的所有路径.

和前边的思路类似,用辅助栈记录递归栈的运行过程,先序遍历(深搜)的过程中,遇到叶子结点(!left && !right的结点)就输出辅助栈的内容.

//输出二叉树的所有路径void OutputTreePath(node* root,list<node*>& path){if(!root) return ; path.push_back(root);if(!root->left && !root->right){for(list<node*>::const_iterator it=path.begin();it !=path.end();++it)cout<<(*it)->value<<' ';cout<<endl;}OutputTreePath(root->left,path);OutputTreePath(root->right,path);//运行到这里的时候,函数生命周期到,在返回上一层时//局部函数会析构root(当前根结点),对应地,也应该将root从path中的值弹出path.pop_back();}

16.求二叉树中从根到某一结点的一条路径.

思路一如既往还是利用辅助栈来追踪递归调用栈的运行过程,只是过程有所区别,将结点入栈,如果在结点的左边能找到一条路径,那么不需要和递归栈同步(即弹栈),直接返回true,如果左边没找到这样的一条路径,再到右边找,如果找到了,返回true.如果左右都找不到存在一条这样的路径,则说明在这个结点上不可能存在这样的路径,需要在辅助栈中弹出这个结点.再遍历其它结点.实质上也是先序遍历的改装版.

//获得二叉树中从根到某一结点的一条路径bool GetNodePath(node* root, node* pNode, list<node*>& path){if(root == pNode)//如果root==nNode,Done{path.push_back(pNode);return true;}path.push_back(root);//1用root实始化容器bool isFound = false;if(root->left)isFound = GetNodePath(root->left, pNode, path);if(!isFound && root->right)isFound = GetNodePath(root->right, pNode, path);if(!isFound)//如果1中的左右子树都没找到,说明在这个root上不存在这样的路径,需要将root弹出path.pop_back();return isFound;}

17判断B子树是否是A子树的子结构

这个似乎是有问题的,暂时还没有找到好的解决方案.求大神相助.

//判断B是否是A的子结构bool isAcontainB(node* rootA,node* rootB){if(!rootB)return true;if(!rootA)return false;if(rootA->value == rootB->value)return isAcontainB(rootA->left,rootB->left) && isAcontainB(rootA->right,rootB->right);return isAcontainB(rootA->left,rootB) || isAcontainB(rootA->right,rootB);}

18.利用先序和中序结果重建二叉树

在先序中找到根,利用根在中序中找到划分位置,再从划分位置把先序切成两部分,再依次递归即可.

//利用先序和中序结果重建二叉树node* RebuildTree(int* Prec,int * Post,int n){if(!Prec || !Post || n<1) return NULL;node* root=new node(Prec[0]);int i;for(i=0 ; i<n && Post[i] !=Prec[0] ; ++i );root->left=RebuildTree(Prec+1,Post,i);root->right=RebuildTree(Prec+1+i,Post+1+i,n-i-1);return root;}

19.求二叉树中叶子结点的个数

void CountLeaves(node* root,int& Count){if(!root ) return ;if(!root->left && !root->right)++Count;CountLeaves(root->left,Count);CountLeaves(root->right,Count);}


20.求二叉树中节点的最大距离。

如果我们把二叉树看成一个图,父子节点之间的连线是双向的,我们定义距离为两个节点之间边的个数。(来自编程之美)

特点:相距最远的两个节点,一定是两个叶子节点,或者一个结点到它的根节点。(为什么?)因为如果当前结点不是叶子结点,即它还有子结点,那么它的子结点到另一个端点的距离肯定可以达到更远。如果是根结点,那么根结点只可能有一条支路。

// 数据结构定义struct NODE{     NODE* pLeft;        // 左子树     NODE* pRight;      // 右子树     int nMaxLeft;      // 左子树中的最长距离     int nMaxRight;     // 右子树中的最长距离     char chValue;    // 该节点的值};int nMaxLen = 0;// 寻找树中最长的两段距离void FindMaxLen(NODE* pRoot){     // 遍历到叶子节点,返回     if(pRoot == NULL)     {          return;     }     // 如果左子树为空,那么该节点的左边最长距离为0     if(pRoot -> pLeft == NULL)     {          pRoot -> nMaxLeft = 0;      }     // 如果右子树为空,那么该节点的右边最长距离为0     if(pRoot -> pRight == NULL)     {          pRoot -> nMaxRight = 0;     }     // 如果左子树不为空,递归寻找左子树最长距离     if(pRoot -> pLeft != NULL)     {          FindMaxLen(pRoot -> pLeft);     }     // 如果右子树不为空,递归寻找右子树最长距离     if(pRoot -> pRight != NULL)     {          FindMaxLen(pRoot -> pRight);     }     // 计算左子树最长节点距离     if(pRoot -> pLeft != NULL)     {          int nTempMax = 0;          if(pRoot -> pLeft -> nMaxLeft > pRoot -> pLeft -> nMaxRight)          {               nTempMax = pRoot -> pLeft -> nMaxLeft;          }          else          {               nTempMax = pRoot -> pLeft -> nMaxRight;          }          pRoot -> nMaxLeft = nTempMax + 1;     }     // 计算右子树最长节点距离     if(pRoot -> pRight != NULL)     {          int nTempMax = 0;          if(pRoot -> pRight -> nMaxLeft > pRoot -> pRight -> nMaxRight)          {               nTempMax = pRoot -> pRight -> nMaxLeft;          }          else          {               nTempMax = pRoot -> pRight -> nMaxRight;          }          pRoot -> nMaxRight = nTempMax + 1;     }     // 更新最长距离     if(pRoot -> nMaxLeft + pRoot -> nMaxRight > nMaxLen)     {          nMaxLen = pRoot -> nMaxLeft + pRoot -> nMaxRight;     }}

递归总结:

1.先弄清楚递归的顺序。在递归的过程中,往往需要假设后续的调用已经完成,在此基础之上,才实现递归的逻辑。

2.分析清楚递归体的逻辑,然后写出来。

3.考虑清楚递归退出的边界条件。即在哪些地方写上return.

21.打印二叉树中某层的结点。

// 输出以root为根节点中的第level层中的所有节点(从左到右), 成功返回1,// 失败则返回0// @param// root 为二叉树的根节点 // level为层次数,其中根节点为第0层int PrintNodeAtLevel(Node* root, int level){     if(!root || level < 0)          return 0;     if(level == 0)     {          cout << root -> data << " ";          return 1;     }     return PrintNodeAtLevel(node -> lChild, level - 1) + PrintNodeAtLevel       (node -> rChild, level - 1);}

22.判断两棵二叉树是否相等

bool IsEqual(node *root1, node *root2){if (root1 != NULL && root2 != NULL){if (root1->data != root2->data){return false;}if (IsEqual(root1->left, root2->left) && IsEqual(root1->right, root2->right)){return true;}}else{if (root1 == NULL && root2 == NULL){return true;}}return false;}

原创粉丝点击