二叉树经典面试题4~判断一棵树是否是完全二叉树
一.问题描述
有一棵树判断该树是否是完全二叉树?
二.问题分析
1.完全二叉树的定义?
判断一棵树是否是完全二叉树,首先要知道什仫是完全二叉树?完全二叉树就是除最后一层外,每一层上的结点数均达到最大值;在最后一层上只缺少右边的若干结点。
- 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
- 完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
- 一棵二叉树至多只有最下面的两层上的结点的度数可以小于2,并且最下层上的结点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。
2.如何判断一棵树是完全二叉树?
在了解了完全二叉树的概念之后,相信读者应该都会判定一颗树是否是完全二叉树了,根据完全二叉树的定义,我们知道完全二叉树的前h-1是满二叉树,而最后一层又是从左到右没有间隔的,如果对这棵树进行层序遍历会发生什仫情况呢?详细介绍见下。在这里我提到两种判断完全二叉树的方法:
1).标记法
设置标记flag=false,如上图所示,从根结点开始层序遍历入队列,如果队列不为空,一直循环。遇到第一个没有左孩子或者右孩子的结点,设置标记位flag=true,如果继续入队列再次遇到存在孩子的结点一定不是完全二叉树。
- //设置标志位的方法
- bool IsCompleteBinaryTree1()
- {
- bool flag=false;
- queue<Node *>q;
- q.push(_root);
- Node *cur=q.front();
- while (cur)
- {
- //有右孩子无左孩子
- if(cur->_left == NULL && cur->_right != NULL)
- return false;
- //标志位为true并且当前结点存在孩子
- if(flag == true && (cur->_left || cur->_right))
- return false;
- //如果当前结点少一个孩子
- if(cur->_left == NULL || cur->_right == NULL)
- flag=true;
- if(cur->_left)
- q.push(cur->_left);
- if(cur->_right)
- q.push(cur->_right);
- q.pop();
- if(!q.empty())
- cur=q.front();
- else
- cur=NULL;
- }
- return true;
- }
2).剩余队列判空法
突然发现使用上述标记法要考虑的情况很多,不太方便,于是又提出了另一种方法,就是剩余队列判空法
这个方法同样的用到了队列这种辅助结构,那仫如何只通过队列这个结构来判断一棵树是否是完全二叉树呢?试想一下,如果我们把一棵树的所有结点,包括叶子结点的左右空孩子都通过层序遍历入队列会发生什仫情况?详细的分析见下图:
- //利用辅助队列的办法
- bool IsCompleteBinaryTree2()
- {
- queue<Node *>q;
- q.push(_root);
- Node *cur=q.front();
- //按照层序遍历的办法入队列直到遇到第一个NULL停止
- while (cur)
- {
- q.push(cur->_left);
- q.push(cur->_right);
- q.pop();
- cur=q.front();
- }
- //如果队列中全为NULL则是完全二叉树,否则不是
- while(!q.empty())
- {
- if(q.front())
- return false;
- q.pop();
- }
- return true;
- }
三.完整的代码实现
- template<class T>
- struct BinaryTreeNode
- {
- T _data;
- BinaryTreeNode<T> *_left;
- BinaryTreeNode<T> *_right;
- BinaryTreeNode(const T& data)
- :_data(data)
- ,_left(NULL)
- ,_right(NULL)
- {}
- };
- template<class T>
- class BinaryTree
- {
- typedef BinaryTreeNode<T> Node;
- public:
- BinaryTree()
- :_root(NULL)
- {}
- BinaryTree(const T*a,size_t size,const T& invalid)
- {
- size_t index=0;
- _root=_CreatTree(a,size,index,invalid);
- }
- //设置标志位的方法
- bool IsCompleteBinaryTree1()
- {
- bool flag=false;
- queue<Node *>q;
- q.push(_root);
- Node *cur=q.front();
- while (cur)
- {
- //有右孩子无左孩子
- if(cur->_left == NULL && cur->_right != NULL)
- return false;
- //标志位为true并且当前结点存在孩子
- if(flag == true && (cur->_left || cur->_right))
- return false;
- //如果当前结点少一个孩子
- if(cur->_left == NULL || cur->_right == NULL)
- flag=true;
- if(cur->_left)
- q.push(cur->_left);
- if(cur->_right)
- q.push(cur->_right);
- q.pop();
- if(!q.empty())
- cur=q.front();
- else
- cur=NULL;
- }
- return true;
- }
- //利用辅助队列的办法
- bool IsCompleteBinaryTree2()
- {
- queue<Node *>q;
- q.push(_root);
- Node *cur=q.front();
- //按照层序遍历的办法入队列直到遇到第一个NULL停止
- while (cur)
- {
- q.push(cur->_left);
- q.push(cur->_right);
- q.pop();
- cur=q.front();
- }
- //如果队列中全为NULL则是完全二叉树,否则不是
- while(!q.empty())
- {
- if(q.front())
- return false;
- q.pop();
- }
- return true;
- }
- protected:
- Node* _CreatTree(const T*a,size_t size,size_t& index,const T& invalid)
- {
- assert(a);
- Node *root=NULL;
- if(index < size && a[index] != invalid)
- {
- root=new Node(a[index]);
- root->_left=_CreatTree(a,size,++index,invalid);
- root->_right=_CreatTree(a,size,++index,invalid);
- }
- return root;
- }
- protected:
- Node *_root;
- };
- void testIsCompleteTree()
- {
- //int array[]={0,1,3,'#','#','#',2,4,'#','#',5}; //false
- //int array[]={0,1,3,'#','#',4,'#','#',2,5,'#','#','#'}; //true
- //int array[]={0,1,3,'#','#',4,'#','#',2}; //true
- int array[]={0,1,3,'#','#','#',2};
- size_t size=sizeof(array)/sizeof(array[0]);
- BinaryTree<int> bt(array,size,'#');
- bool ret=bt.IsCompleteBinaryTree2();
- if(ret == true)
- cout<<"是完全二叉树"<<endl;
- else
- cout<<"不是完全二叉树"<<endl;
- }