面试算法之二叉树操作集锦

19 篇文章 0 订阅
 

面试算法之二叉树操作集锦


目录(?)[+]

开学了,找工作也正式拉开了序幕,每天光自己看书,也很没劲,和大家一起分享分享,交流一下笔试面试过程中的各种算法题目,如有问题,欢迎指正,希望大家一起进步。。。

下面是对数据结构二叉树的一些基本操作,可能在面试中都会涉及到。我们都知道二叉树的定义本身就是一种递归定义,所以对树的大部分操作都可以通过递归的方式进行,但递归不是万能的,因为递归的本身是一件很浪费内存资源的操作,所以在选择算法的时候要权衡各种因素,选取最合理的算法。下图Fig 1 是下面代码中举例会用到的图:

Fig 1

在本文中,所讨论的二叉树采取以下的定义方式:

[cpp]  view plain copy
  1. template<typename Type>  
  2. struct BiNode{  
  3.     Type data;  
  4.     BiNode *left;  
  5.     BiNode *right;  
  6. };  
下面就是各种对二叉树的操作,也行大家在各种面试书中看过了,这些题目在面试中可能会经常出现,所以要秒杀它们。。。

1.二叉树的创建

下面的创建是采用递归的方式进行创建,节点的内容为字符。节点的创建方式是先序的方式,先创建根节点,然后是左子树,最后是右子树。

[cpp]  view plain copy
  1. /** 
  2.  *Create Binary Tree 
  3.  */  
  4. BiNode<char> * CreateBiTree()  
  5. {  
  6.     BiNode<char> *root;  
  7.     char data;  
  8.     cin>>data;  
  9.   
  10.     if(data == '$')  
  11.         return NULL;  
  12.   
  13.     root = new BiNode<char>;  
  14.     root->data = data;  
  15.   
  16.     root->left = CreateBiTree();  
  17.     root->right = CreateBiTree();  
  18.   
  19.     return root;  
  20. }  

2.二叉树的各种遍历

下面的算法是二叉树的各种遍历,包括先序,中序,层次遍历,其中有非递归和递归的算法,关于后序遍历这里没有列举,因为后序的非递归相对比较复杂,每个节点要进出栈两次,在面试的过程中一般面试官不是变态的话,不会考后序的非递归算法的。

2.1 先序遍历

下面是先序遍历的递归算法:

[cpp]  view plain copy
  1. /** 
  2.  * recursively pre-order traverse the binary tree 
  3.  * 先序遍历递归算法 
  4.  */  
  5. template <typename Type>  
  6. void PreOrder( BiNode<Type> *root )  
  7. {  
  8.     if(root == NULL)      
  9.         return;  
  10.       
  11.     cout<<root->data<<endl;  
  12.   
  13.     PreOrder(root->left);  
  14.     PreOrder(root->right);  
  15. }  
下面是先序遍历的非递归算法,非递归的思想就是通过栈来模拟递归的过程,非递归的先序遍历就是在每访问一个节点后,讲该节点的右孩子压入栈,然后再将左孩子压栈。

[cpp]  view plain copy
  1. /** 
  2.  * non-recursively pre-order traverse the binary tree 
  3.  * 先序遍历非递归算法 
  4.  */  
  5. template <typename Type>  
  6. void PreOrder_NonRecursive( BiNode<Type> *root )  
  7. {  
  8.     if(root == NULL)      
  9.         return;  
  10.       
  11.     stack<BiNode<Type> *> nodeStack;  
  12.     nodeStack.push(root);  
  13.   
  14.     while(!nodeStack.empty())  
  15.     {  
  16.         BiNode<Type> *node = nodeStack.top();  
  17.         nodeStack.pop();  
  18.   
  19.         cout<<node->data<<endl;  
  20.   
  21.         if(node->right)  
  22.             nodeStack.push(node->right);  
  23.         if(node->left)  
  24.             nodeStack.push(node->left);  
  25.     }  
  26. }  

2.2 中序遍历

下面是中序遍历的递归算法:

[cpp]  view plain copy
  1. /** 
  2.  * recursively in-order traverse the binary tree 
  3.  * 中序遍历递归算法 
  4.  */  
  5. template <typename Type>  
  6. void InOrder( BiNode<Type> *root )  
  7. {  
  8.     if(root == NULL)      
  9.         return;  
  10.   
  11.     InOrder(root->left);  
  12.   
  13.     cout<<root->data<<endl;  
  14.   
  15.     InOrder(root->right);  
  16. }  
下面是中序遍历的非递归算法,中序遍历非递归的思想就是将节点的沿着左子树的方向一直入栈,直到左子树为空,然后弹出栈里的元素进行访问,如果该节点存在右子树,则重复执行上述操作。
[cpp]  view plain copy
  1. /** 
  2.  * non-recursively in-order traverse the binary tree 
  3.  * 中序遍历非递归算法 
  4.  */  
  5. template <typename Type>  
  6. void InOrder_NonRecursive( BiNode<Type> *root )  
  7. {  
  8.     if (root == NULL)  
  9.         return;  
  10.   
  11.     stack<BiNode<Type> *> nodeStack;  
  12.     BiNode<Type> *node = root;  
  13.   
  14.     while (node != NULL || !nodeStack.empty())  
  15.     {  
  16.         if (node != NULL)  
  17.         {  
  18.             nodeStack.push(node);  
  19.             node = node->left;  
  20.         }  
  21.         else  
  22.         {  
  23.             node = nodeStack.top();  
  24.             nodeStack.pop();  
  25.   
  26.             cout<<node->data<<endl;  
  27.   
  28.             node = node->right;  
  29.         }  
  30.     }  
  31. }  
2.3 层次遍历
二叉树的层次遍历就是按照节点的深度从上往下,从左往右依次访问树中的每一个节点。
下面这种方法是通过队列来完成的,首先将根节点入队列,然后重复进行如下操作:读取队头节点元素,并将节点的左右孩子写入队列,直到队列为空。
[cpp]  view plain copy
  1. /** 
  2.  * level order traverse the binary tree 
  3.  * method 1 
  4.  */  
  5. template <typename Type>  
  6. void LevelOrder_1( BiNode<Type> *root )  
  7. {  
  8.     if (root == NULL)  
  9.         return;  
  10.   
  11.     queue<BiNode<Type> *> nodeQueue;  
  12.     nodeQueue.push(root);  
  13.   
  14.     while (!nodeQueue.empty())  
  15.     {  
  16.        BiNode<Type> *node = nodeQueue.front();  
  17.        nodeQueue.pop();  
  18.   
  19.        cout<<node->data<<" ";  
  20.   
  21.        if(node->left)  
  22.            nodeQueue.push(node->left);  
  23.        if(node->right)  
  24.            nodeQueue.push(node->right);  
  25.     }  
  26. }  
上面的算法不能清晰的按层次单独的打印二叉树的每层数据,下面有二种方法可以代替这种方法。
[cpp]  view plain copy
  1. /** 
  2.  * level order traverse the binary tree 
  3.  * method 2 
  4.  */  
  5. template <typename Type>  
  6. void LevelOrder_2( BiNode<Type> *root )  
  7. {  
  8.     if (root == NULL)  
  9.         return;  
  10.   
  11.     //GetBinTreeHeight()函数用于获取二叉树的高度,后面会有介绍  
  12.     for (int i = 1; i <= GetBinTreeHeight(root); ++i)  
  13.     {  
  14.         PrintKthLevelOrder(root, i);  
  15.         cout<<endl;  
  16.     }  
  17. }  
  18.   
  19. /** 
  20.  * print the k(th) level node of binary tree 
  21.  * 打印二叉树的第K层的节点 
  22.  * 
  23.  * @param   k   the level, its value must be 1 <= k <= tree height 
  24.  */  
  25. template <typename Type>  
  26. void PrintKthLevelOrder( BiNode<Type> *root, int k)  
  27. {  
  28.     if (root == NULL)  
  29.         return;  
  30.   
  31.     if(k == 1)  
  32.     {  
  33.         cout<<root->data<<" ";  
  34.         return;  
  35.     }  
  36.   
  37.     PrintKthLevelOrder(root->left, k - 1);  
  38.     PrintKthLevelOrder(root->right, k - 1);  
  39. }  
上面的代码完成了独立的遍历每一层的节点。但我们会发现,遍历每一层节点都会从根节点往下开始,这样会存在大量的重复操作,一般的面试官是不会满意这种算法的。下面就是通过STL vector来存储遍历的节点,过程和通过队列访问类型,但用了两个index来标识每一层。具体可以参考编程之美3.10节。下面是代码:
[cpp]  view plain copy
  1. /** 
  2.  * level order traverse the binary tree 
  3.  * method 3 
  4.  */  
  5. template <typename Type>  
  6. void LevelOrder_3( BiNode<Type> *root )  
  7. {  
  8.     if (root == NULL)  
  9.         return;  
  10.   
  11.     vector<BiNode<Type> *> nodeVec;  
  12.     nodeVec.push_back(root);  
  13.   
  14.     int cur, last;  
  15.     cur = 0, last = 1;  
  16.   
  17.     while(cur < nodeVec.size())  
  18.     {  
  19.         cout<<nodeVec[cur]->data<<" ";  
  20.   
  21.         if(nodeVec[cur]->left != NULL)  
  22.             nodeVec.push_back(nodeVec[cur]->left);  
  23.         if(nodeVec[cur]->right != NULL)  
  24.             nodeVec.push_back(nodeVec[cur]->right);  
  25.   
  26.         ++cur;  
  27.   
  28.         if (cur == last)  
  29.         {  
  30.             last = nodeVec.size();  
  31.           
  32.             cout<<endl;  
  33.         }  
  34.     }  
  35. }  

3.二叉树的高度

二叉树的高度可以通过后序遍历的思想,递归的统计节点的左子树和右子树的高度,然后取左右子树高度的最高值,然后加1,就是该层节点的高度。代码如下:
[cpp]  view plain copy
  1. /** 
  2.  * calculate the height of binary tree 
  3.  */  
  4. template <typename Type>  
  5. int GetBinTreeHeight( BiNode<Type> *root)  
  6. {  
  7.     if (root == NULL)  
  8.         return 0;  
  9.   
  10.     int lHeight = GetBinTreeHeight(root->left);  
  11.     int rHeight = GetBinTreeHeight(root->right);  
  12.   
  13.     if(lHeight < rHeight)  
  14.         return rHeight + 1;  
  15.   
  16.     return lHeight + 1;  
  17. }  

4.二叉树第K层节点的个数

也是通过后序遍历的思想,分别求节点左右子树在第K层的节点个数,然后求和。这里对传入的k,随着递归深度的加深,逐渐减1,直到k为1。
[cpp]  view plain copy
  1. /** 
  2.  * calculate the node counts in k(th) level 
  3.  * 
  4.  * @param   k   the level, its value must be 1 <= k <= tree height 
  5.  */  
  6. template <typename Type>  
  7. int GetNodeCountsKthLevel( BiNode<Type> *root, int k)  
  8. {  
  9.     //检测k否超过二叉树的高度  
  10.     if (root == NULL || k < 1 || k > GetBinTreeHeight(root))  
  11.         return 0;  
  12.   
  13.     if(k == 1)  
  14.         return 1;  
  15.   
  16.     return GetNodeCountsKthLevel(root->left, k - 1) \  
  17.         + GetNodeCountsKthLevel(root->right, k - 1);  
  18. }  
这里为了增强代码的鲁棒性,加入了对传入参数k的合法性的检验k本身的取值范围应该是:1 =< k <= tree height

5.叶子节点的个数

和前面的道理一样,在二叉树的操作中,递归才是王道。。。
[cpp]  view plain copy
  1. /** 
  2.  * calculate the leaf node counts 
  3.  */  
  4. template <typename Type>  
  5. int GetLeavesCounts( BiNode<Type> *root)  
  6. {  
  7.     if (root == NULL)  
  8.         return 0;  
  9.   
  10.     if(root->left == NULL && root->right == NULL)  
  11.         return 1;  
  12.   
  13.     return GetLeavesCounts(root->left) + GetLeavesCounts(root->right);  
  14. }  

6.二叉树节点的个数

[cpp]  view plain copy
  1. /** 
  2.  * calculate the tree's node counts 
  3.  */  
  4. template <typename Type>  
  5. int GetNodeCounts( BiNode<Type> *root)  
  6. {  
  7.     if(root == NULL)  
  8.     return 0;  
  9.   
  10.     return GetNodeCounts(root->left) + GetNodeCounts(root->right) + 1;  
  11. }  
也可以通过传入参数,通过先序遍历的思想,每访问一个节点就将计数器加1,直到遍历完所有节点为止。

7.二叉排序树转换成排序的双向链表

这个已经在前面的博客中写过,详见: http://blog.csdn.net/anonymalias/article/details/9204825

8.二叉树的子结构

二叉树的子结构的定义是:一个二叉树为另一个二叉树的子集,如下图所示:

Fig 2 B为A的一个子结构
那么对于这个题目的解题思路是:在A中查找与B根节点相同的节点X,找到后将该节点X的左右子树与B的左右子树依次比较,如果B的所有节点都在X的左右子树中,那么就认为B是A的子结构,如果B的所有节点不都在X的左右子树中,那么在A中继续查找另外一个X节点,直到结束。下面是代码:
[cpp]  view plain copy
  1. /** 
  2.  * judge the binary tree 'rootB' is a substructure of 'rootA'or not 
  3.  */  
  4. template <typename Type>  
  5. bool IsSubStruct(BiNode<Type> *rootA, BiNode<Type> *rootB)  
  6. {  
  7.     if (rootA == NULL || rootB == NULL)  
  8.         return false;  
  9.   
  10.     bool result = false;  
  11.   
  12.     if (rootA->data == rootB->data)  
  13.         result = ISSameStruct(rootA, rootB);  
  14.   
  15.     if(!result)  
  16.         result = IsSubStruct(rootA->left, rootB);  
  17.     if(!result)  
  18.         result = IsSubStruct(rootA->right, rootB);  
  19.   
  20.     return result;  
  21. }  
  22.   
  23. //用于判断二叉树B是否是A开始的一部分  
  24. template<typename Type>  
  25. bool ISSameStruct(BiNode<Type> *rootA, BiNode<Type> *rootB)  
  26. {  
  27.     if(rootB == NULL)  
  28.         return true;  
  29.       
  30.     if(rootA == NULL)  
  31.         return false;  
  32.   
  33.     if(rootA->data != rootB->data)  
  34.         return false;  
  35.   
  36.     return ISSameStruct(rootA->left, rootB->left) && ISSameStruct(rootA->right, rootB->right);  
  37. }  

8.二叉树的镜像

二叉树镜像的概念就是左右子树交换,所以判断起来也很简单,代码如下:
[cpp]  view plain copy
  1. /** 
  2.  * judge the binary tree 'rootA' is a mirror of 'rootB' or not 
  3.  */  
  4. template<typename Type>  
  5. bool ISMirror(BiNode<Type> *rootA, BiNode<Type> *rootB)  
  6. {  
  7.     if(rootA == NULL && rootB == NULL)  
  8.         return true;  
  9.   
  10.     if(rootA == NULL || rootB == NULL)  
  11.         return false;  
  12.   
  13.     if(rootA->data != rootB->data)  
  14.         return false;  
  15.   
  16.         return ISMirror(rootA->left, rootB->right) && ISMirror(rootA->right, rootB->left);  
  17. }  
如果将ISMirror递归部分换成如下的代码,就是判断两棵二叉树是否相同。
[cpp]  view plain copy
  1. return ISMirror(rootA->left, rootB->left) && ISMirror(rootA->right, rootB->right);  

9.平衡二叉树的判断

我们都知道平衡二叉树的定义:空树或左右子树的高度差不超过1,且左右子树也都是平衡二叉树。代码如下:
[cpp]  view plain copy
  1. /** 
  2.  * judge the binary tree whether it is a balanced tree 
  3.  */  
  4. template <typename Type>  
  5. bool IsBalanced(BiNode<Type> *root)  
  6. {  
  7.     int height = 0;  
  8.   
  9.     return SubIsBalanced(root, height);  
  10. }  
  11.   
  12. template <typename Type>  
  13. bool SubIsBalanced(BiNode<Type> *root, int &height)  
  14. {  
  15.     if(root == NULL)  
  16.     {  
  17.         height = 0;  
  18.         return true;  
  19.     }  
  20.   
  21.     int lH, rH;  
  22.     int result = SubIsBalanced(root->left, lH) && SubIsBalanced(root->right, rH);  
  23.   
  24.     if (result)  
  25.     {  
  26.         if(lH - rH <= 1 && lH - rH >= -1)  
  27.         {  
  28.             height = (lH > rH ? lH + 1 : rH + 1);  
  29.             return true;  
  30.         }  
  31.     }  
  32.   
  33.     return false;  
  34. }  

10.完全二叉树的判断

完全二叉树的定义如下:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。判断一棵树是否是完全二叉树,我见过最简单的方法是:通过广度遍历即层次遍历的思想,将各个节点入队列,对于存在空洞的节点( 左右孩子的节点存在NULL),把它的两个孩子也入队列,当访问到队列中为NULL的节点,根据完全二叉树的定义,此时二叉树已经结束,即队列中的其他元素全部为NULL,否则该树不是完全二叉树。代码如下:
[cpp]  view plain copy
  1. /** 
  2.  * judge the binary tree whether it is a completed tree 
  3.  */  
  4. template <typename Type>  
  5. bool IsCompletedBiTree(BiNode<Type> *root)  
  6. {  
  7.     if(root == NULL)  
  8.         return true;  
  9.   
  10.     queue<BiNode<Type> *> nodeQue;  
  11.     nodeQue.push(root);  
  12.   
  13.     while(!nodeQue.empty())  
  14.     {  
  15.         BiNode<Type> *node = nodeQue.front();  
  16.         nodeQue.pop();  
  17.   
  18.         if (node == NULL)  
  19.         {  
  20.             while (!nodeQue.empty())  
  21.             {  
  22.                 if(nodeQue.front() != NULL)  
  23.                     return false;  
  24.   
  25.                 nodeQue.pop();  
  26.             }  
  27.   
  28.             return true;  
  29.         }  
  30.           
  31.         nodeQue.push(node->left);  
  32.         nodeQue.push(node->right);  
  33.     }  
  34.   
  35.         //实际上不会执行到这一步  
  36.     return true;  
  37. }  

11.满二叉树的判断

满二叉树的判断相对比较简单,可以通过判断每个节点的左右子树的高度是否相同来实现,满二叉树的所以节点的左右子树的高度都是一样的。代码如下:
[cpp]  view plain copy
  1. /** 
  2.  * judge the binary tree whether it is a full tree 
  3.  */  
  4. template <typename Type>  
  5. bool IsFullBiTree(BiNode<Type> *root)  
  6. {  
  7.     if(root == NULL)  
  8.         return true;  
  9.   
  10.     int height;  
  11.     return SubIsFullBiTree(root, height);  
  12. }  
  13.   
  14. template <typename Type>  
  15. bool SubIsFullBiTree(BiNode<Type> *root, int &height)  
  16. {  
  17.     if(root == NULL)  
  18.     {  
  19.         height = 0;  
  20.         return true;  
  21.     }  
  22.   
  23.     int lH, rH;  
  24.     if (SubIsFullBiTree(root->left, lH) && SubIsFullBiTree(root->right, rH))  
  25.     {  
  26.         if (lH == rH)  
  27.         {  
  28.             height = lH + 1;  
  29.             return true;  
  30.         }  
  31.     }  
  32.   
  33.     return false;  
  34. }  

12.重建二叉树

根据二叉树的先序和中序遍历的结果(不含有重复的节点),重建此二叉树,该题的的解决思路也是通过二叉树递归定义的思想。我们知道二叉树先序遍历的一个节点,在中序遍历中会把以该节点为根的二叉树分为左右两部分,根据这点,可以递归的重建二叉树,具体代码如下:
[cpp]  view plain copy
  1. /** 
  2.  * rebuild the binary tree 
  3.  */  
  4. template <typename Type>  
  5. BiNode<Type> * RebuildBiTree(const Type *pre, const Type *in, int len)  
  6. {  
  7.     if(pre == NULL || in == NULL || len <= 0)  
  8.         return NULL;  
  9.   
  10.     BiNode<Type> * root = new BiNode<Type>;  
  11.     root->data = pre[0];  
  12.   
  13.     int index;  
  14.     for (index = 0; index < len; ++index)  
  15.     {  
  16.         if (in[index] == pre[0])  
  17.             break;  
  18.     }  
  19.           
  20.     //can not find the 'pre[0]' in the 'in[]'  
  21.     if(index == len)  
  22.         return NULL;  
  23.   
  24.     root->left = RebuildBiTree(pre + 1, in, index);  
  25.     root->right = RebuildBiTree(pre + index + 1, in + index + 1, len - index - 1);  
  26.   
  27.     return root;  
  28. }  

13.判断序列是否是二叉排序树的后序遍历序列

我们都知道二叉排序树的中序遍历的结果是一个递增序列,后序遍历序列最后的元素是根节点,通过最后的元素将遍历序列分割成两部分,左半部分都小于根节点的值,右半部分都大于该节点的值,如果不能分成这两部分,那么该序列就不是二叉排序树的后序遍历序列。代码如下:
[cpp]  view plain copy
  1. /** 
  2.  * judge the serial is post-order traversal of binary search tree 
  3.  */  
  4. template <typename Type>  
  5. bool IsBSTPostOrder(const Type *post, int len)  
  6. {  
  7.     if(post == NULL || len <= 0)  
  8.         return false;  
  9.   
  10.     int index;  
  11.       
  12.     //查找小于根节点的左子树节点  
  13.     for (index = 0; index < len - 1; ++index)  
  14.     {  
  15.         if(post[index] > post[len - 1])  
  16.             break;  
  17.     }  
  18.   
  19.     //判断剩下的节点是否都为右子树的节点,即是否都大于根节点的值  
  20.     for (int i = index; i < len - 1; ++i)  
  21.     {  
  22.         if(post[i] < post[len - 1])  
  23.             return false;  
  24.     }  
  25.   
  26.     bool result = true;  
  27.   
  28.     if(index > 0)  
  29.         result = IsBSTPostOrder(post, index);  
  30.     if(result && index < len - 1)  
  31.         result = IsBSTPostOrder(post + index, len - index - 1);  
  32.   
  33.     return result;  
  34. }  

就先写这么多吧,后面还会继续添加,累死了。。。
有新的问题大家可以提出来,一起讨论,共同进步...<^_^>。。。

Sept 2nd - 3rd, 2013 @lab
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值