转自:
http://blog.csdn.net/pony_maggie/article/details/38390513
http://blog.csdn.net/luckyxiaoqiang/article/details/7518888/
一 二叉树的一些概念
二叉树就是每个结点最多有两个子树的树形存储结构。先上图,方便后面分析。
1 满二叉树和完全二叉树
上图就是典型的二叉树,其中左边的图还叫做满二叉树,右边是完全二叉树。然后我们可以得出结论,满二叉树一定是完全二叉树,但是反过来就不一定。满二叉树的定义是除了叶子结点,其它结点左右孩子都有,深度为k的满二叉树,结点数就是2的k次方减1。完全二叉树是每个结点都与深度为k的满二叉树中编号从1到n一一对应。
2 树的深度
树的最大层次就是深度,比如上图,深度是4。很容易得出,深度为k的树,拥有的最大结点数是2的k次方减1。
3 树的孩子,兄弟,双亲
上图中,B,C是A的孩子,B,C之间互为兄弟,A是B,C的双亲。
二如何创建二叉树
先说说二叉树的存储结构,跟很多其它模型一样,也有顺序和链式两种方式。前者虽然使用简单,但是存在浪费空间的问题,举个例子,下图的二叉树,用顺序的方式存储(0表示空,没有子树)是:
1 2 3 4 5 6 7 0 0 0 0 8 0 0 0
是不是相当浪费空间呢。
链式结构可以定义如下:
- typedef struct _BiTNode
- {
- int data;
- _BiTNode *leftChild;
- _BiTNode *rightChild;
- }BiTNode, *pBiTree;
然后就可以写一个函数来创建二叉树,过程是在控制台输入a表示退出当前这一层,不再为该层创建左右孩子。输入其它字母表示继续创建。比如下面的输入序列:
创建了如下结构的二叉树,
每个结点里的数值是随机生成的小于100的数字。同时我也写了一个自动的命令序列函数,方便测试,不用手动输入,非自动和自动创建的函数如下:
-
- int CreateBiTree(pBiTree *root)
- {
- char ch = 0;
- fflush(stdin);
- if ((ch = getchar()) == 'a')
- {
- *root = NULL;
- }
- else
- {
- *root = (BiTNode *)malloc(sizeof(BiTNode));
- if (!(*root))
- {
- return RET_ERROR;
- }
- (*root)->data = GetRandom();
- CreateBiTree(&(*root)->leftChild);
- CreateBiTree(&(*root)->rightChild);
- }
- return RET_OK;
- }
-
- int g_i = 0;
-
- int CreateBiTreeAuto(pBiTree *root)
- {
- char szOrder[] = "bbaabaa";
- char ch = 0;
- if (szOrder[g_i++] == 'a')
- {
- *root = NULL;
- }
- else
- {
- *root = (BiTNode *)malloc(sizeof(BiTNode));
- if (!(*root))
- {
- return RET_ERROR;
- }
- (*root)->data = GetRandom();
- CreateBiTreeAuto(&(*root)->leftChild);
- CreateBiTreeAuto(&(*root)->rightChild);
- }
- return RET_OK;
- }
三遍历顺序
先序遍历
先序遍历是先访问根结点,再左子树,再右子树,比如图1中的右图,先序遍历的输出如下:
A,B,D,H,I,E,J,K,C,F,G
根据上面的思想,很容易用递归的形式写出先序遍历的代码:
-
- int PreOrderVisitTree(pBiTree T, VisitType pFuncVisit)
- {
- if (T)
- {
- (*pFuncVisit)(T->data);
- if (PreOrderVisitTree(T->leftChild, pFuncVisit) == RET_OK)
- {
- if (PreOrderVisitTree(T->rightChild, pFuncVisit) == RET_OK)
- {
- return RET_OK;
- }
- }
- return RET_ERROR;
- }
- else
- {
- return RET_OK;
- }
- }
中序遍历和后序遍历
有了先序的经验,这两个就很好理解了,中序是先访问左子树, 再根结点,再右子树, 后序是先访问左子树, 再右子树,再根结点。代码更容易,只要改一下调用顺序就可以了。
不过我这里给出一种非递归的实现。递归固然是清晰明了,但是存在效率低的问题,非递归的方案用栈结构来存结点信息,通过出栈访问来遍历二叉树。它思想是这样的,当栈顶中的指针非空时,遍历左子树,也就是左子树根的指针进栈。当栈顶指针为空时,应退至上一层,如果是从左子树返回的,访问当前层,也就是栈顶中的根指针结点。如果是从右子树返回,说明当前层遍历完毕,继续退栈。代码如下:
-
- int InOrderVisitTree(pBiTree T, VisitType pFuncVisit)
- {
- ponyStack binaryTreeStack;
- InitStack(&binaryTreeStack, 4);
- Push(&binaryTreeStack, &T);
- pBiTree pTempNode;
-
- while (!IsEmptyStack(binaryTreeStack))
- {
- while((GetTop(binaryTreeStack, &pTempNode) == RET_OK) && (pTempNode != NULL))
- {
- Push(&binaryTreeStack, &(pTempNode->leftChild));
- }
- Pop(&binaryTreeStack, &pTempNode);
- if (!IsEmptyStack(binaryTreeStack))
- {
- Pop(&binaryTreeStack, &pTempNode);
- (*pFuncVisit)(pTempNode->data);
- Push(&binaryTreeStack, &(pTempNode->rightChild));
- }
- }
- return RET_OK;
- }
树是一种比较重要的数据结构,尤其是二叉树。二叉树是一种特殊的树,在二叉树中每个节点最多有两个子节点,一般称为左子节点和右子节点(或左孩子和右孩子),并且二叉树的子树有左右之分,其次序不能任意颠倒。二叉树是递归定义的,因此,与二叉树有关的题目基本都可以用递归思想解决,当然有些题目非递归解法也应该掌握,如非递归遍历节点等等。本文努力对二叉树相关题目做一个较全的整理总结,希望对找工作的同学有所帮助。
二叉树节点定义如下:
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
相关链接:
轻松搞定面试中的链表题目
题目列表:
1. 求二叉树中的节点个数
2. 求二叉树的深度
3. 前序遍历,中序遍历,后序遍历
4.分层遍历二叉树(按层次从上往下,从左往右)
5. 将二叉查找树变为有序的双向链表
6. 求二叉树第K层的节点个数
7. 求二叉树中叶子节点的个数
8. 判断两棵二叉树是否结构相同
9. 判断二叉树是不是平衡二叉树
10. 求二叉树的镜像
11. 求二叉树中两个节点的最低公共祖先节点
12. 求二叉树中节点的最大距离
13. 由前序遍历序列和中序遍历序列重建二叉树
14.判断二叉树是不是完全二叉树
详细解答
1. 求二叉树中的节点个数
递归解法:
(1)如果二叉树为空,节点个数为0
(2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1
参考代码如下:
- int GetNodeNum(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return 0;
- return GetNodeNum(pRoot->m_pLeft) + GetNodeNum(pRoot->m_pRight) + 1;
- }
2. 求二叉树的深度
递归解法:
(1)如果二叉树为空,二叉树的深度为0
(2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
参考代码如下:
- int GetDepth(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return 0;
- int depthLeft = GetDepth(pRoot->m_pLeft);
- int depthRight = GetDepth(pRoot->m_pRight);
- return depthLeft > depthRight ? (depthLeft + 1) : (depthRight + 1);
- }
3. 前序遍历,中序遍历,后序遍历
前序遍历递归解法:
(1)如果二叉树为空,空操作
(2)如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树
参考代码如下:
- void PreOrderTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- Visit(pRoot);
- PreOrderTraverse(pRoot->m_pLeft);
- PreOrderTraverse(pRoot->m_pRight);
- }
中序遍历递归解法
(1)如果二叉树为空,空操作。
(2)如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树
参考代码如下:
- void InOrderTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- InOrderTraverse(pRoot->m_pLeft);
- Visit(pRoot);
- InOrderTraverse(pRoot->m_pRight);
- }
后序遍历递归解法
(1)如果二叉树为空,空操作
(2)如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点
参考代码如下:
- void PostOrderTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- PostOrderTraverse(pRoot->m_pLeft);
- PostOrderTraverse(pRoot->m_pRight);
- Visit(pRoot);
- }
4.分层遍历二叉树(按层次从上往下,从左往右)
相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作:弹出一个节点,访问,若左子节点或右子节点不为空,将其压入队列。
- void LevelTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- queue<BinaryTreeNode *> q;
- q.push(pRoot);
- while(!q.empty())
- {
- BinaryTreeNode * pNode = q.front();
- q.pop();
- Visit(pNode);
- if(pNode->m_pLeft != NULL)
- q.push(pNode->m_pLeft);
- if(pNode->m_pRight != NULL)
- q.push(pNode->m_pRight);
- }
- return;
- }
5. 将二叉查找树变为有序的双向链表
要求不能创建新节点,只调整指针。
递归解法:
(1)如果二叉树查找树为空,不需要转换,对应双向链表的第一个节点是NULL,最后一个节点是NULL
(2)如果二叉查找树不为空:
如果左子树为空,对应双向有序链表的第一个节点是根节点,左边不需要其他操作;
如果左子树不为空,转换左子树,二叉查找树对应双向有序链表的第一个节点就是左子树转换后双向有序链表的第一个节点,同时将根节点和左子树转换后的双向有序链
表的最后一个节点连接;
如果右子树为空,对应双向有序链表的最后一个节点是根节点,右边不需要其他操作;
如果右子树不为空,对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点,同时将根节点和右子树转换后的双向有序链表的第一个节点连
接。
参考代码如下:
-
-
-
-
-
-
- void Convert(BinaryTreeNode * pRoot,
- BinaryTreeNode * & pFirstNode, BinaryTreeNode * & pLastNode)
- {
- BinaryTreeNode *pFirstLeft, *pLastLeft, * pFirstRight, *pLastRight;
- if(pRoot == NULL)
- {
- pFirstNode = NULL;
- pLastNode = NULL;
- return;
- }
-
- if(pRoot->m_pLeft == NULL)
- {
-
- pFirstNode = pRoot;
- }
- else
- {
- Convert(pRoot->m_pLeft, pFirstLeft, pLastLeft);
-
- pFirstNode = pFirstLeft;
-
- pRoot->m_pLeft = pLastLeft;
- pLastLeft->m_pRight = pRoot;
- }
-
- if(pRoot->m_pRight == NULL)
- {
-
- pLastNode = pRoot;
- }
- else
- {
- Convert(pRoot->m_pRight, pFirstRight, pLastRight);
-
- pLastNode = pLastRight;
-
- pRoot->m_pRight = pFirstRight;
- pFirstRight->m_pLeft = pRoot;
- }
-
- return;
- }
6. 求二叉树第K层的节点个数
递归解法:
(1)如果二叉树为空或者k<1返回0
(2)如果二叉树不为空并且k==1,返回1
(3)如果二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和
参考代码如下:
- int GetNodeNumKthLevel(BinaryTreeNode * pRoot, int k)
- {
- if(pRoot == NULL || k < 1)
- return 0;
- if(k == 1)
- return 1;
- int numLeft = GetNodeNumKthLevel(pRoot->m_pLeft, k-1);
- int numRight = GetNodeNumKthLevel(pRoot->m_pRight, k-1);
- return (numLeft + numRight);
- }
7. 求二叉树中叶子节点的个数
递归解法:
(1)如果二叉树为空,返回0
(2)如果二叉树不为空且左右子树为空,返回1
(3)如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数
参考代码如下:
- int GetLeafNodeNum(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return 0;
- if(pRoot->m_pLeft == NULL && pRoot->m_pRight == NULL)
- return 1;
- int numLeft = GetLeafNodeNum(pRoot->m_pLeft);
- int numRight = GetLeafNodeNum(pRoot->m_pRight);
- return (numLeft + numRight);
- }
8. 判断两棵二叉树是否结构相同
不考虑数据内容。结构相同意味着对应的左子树和对应的右子树都结构相同。
递归解法:
(1)如果两棵二叉树都为空,返回真
(2)如果两棵二叉树一棵为空,另一棵不为空,返回假
(3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假
参考代码如下:
- bool StructureCmp(BinaryTreeNode * pRoot1, BinaryTreeNode * pRoot2)
- {
- if(pRoot1 == NULL && pRoot2 == NULL)
- return true;
- else if(pRoot1 == NULL || pRoot2 == NULL)
- return false;
- bool resultLeft = StructureCmp(pRoot1->m_pLeft, pRoot2->m_pLeft);
- bool resultRight = StructureCmp(pRoot1->m_pRight, pRoot2->m_pRight);
- return (resultLeft && resultRight);
- }
9. 判断二叉树是不是平衡二叉树
递归解法:
(1)如果二叉树为空,返回真
(2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假
参考代码:
- bool IsAVL(BinaryTreeNode * pRoot, int & height)
- {
- if(pRoot == NULL)
- {
- height = 0;
- return true;
- }
- int heightLeft;
- bool resultLeft = IsAVL(pRoot->m_pLeft, heightLeft);
- int heightRight;
- bool resultRight = IsAVL(pRoot->m_pRight, heightRight);
- if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1)
- {
- height = max(heightLeft, heightRight) + 1;
- return true;
- }
- else
- {
- height = max(heightLeft, heightRight) + 1;
- return false;
- }
- }
10. 求二叉树的镜像
递归解法:
(1)如果二叉树为空,返回空
(2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树
参考代码如下:
- BinaryTreeNode * Mirror(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return NULL;
- BinaryTreeNode * pLeft = Mirror(pRoot->m_pLeft);
- BinaryTreeNode * pRight = Mirror(pRoot->m_pRight);
-
- pRoot->m_pLeft = pRight;
- pRoot->m_pRight = pLeft;
- return pRoot;
- }
11. 求二叉树中两个节点的最低公共祖先节点
递归解法:
(1)如果两个节点分别在根节点的左子树和右子树,则返回根节点
(2)如果两个节点都在左子树,则递归处理左子树;如果两个节点都在右子树,则递归处理右子树
参考代码如下:
- bool FindNode(BinaryTreeNode * pRoot, BinaryTreeNode * pNode)
- {
- if(pRoot == NULL || pNode == NULL)
- return false;
-
- if(pRoot == pNode)
- return true;
-
- bool found = FindNode(pRoot->m_pLeft, pNode);
- if(!found)
- found = FindNode(pRoot->m_pRight, pNode);
-
- return found;
- }
-
- BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot,
- BinaryTreeNode * pNode1,
- BinaryTreeNode * pNode2)
- {
- if(FindNode(pRoot->m_pLeft, pNode1))
- {
- if(FindNode(pRoot->m_pRight, pNode2))
- return pRoot;
- else
- return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2);
- }
- else
- {
- if(FindNode(pRoot->m_pLeft, pNode2))
- return pRoot;
- else
- return GetLastCommonParent(pRoot->m_pRight, pNode1, pNode2);
- }
- }
递归解法效率很低,有很多重复的遍历,下面看一下非递归解法。
非递归解法:
先求从根节点到两个节点的路径,然后再比较对应路径的节点就行,最后一个相同的节点也就是他们在二叉树中的最低公共祖先节点
参考代码如下:
- bool GetNodePath(BinaryTreeNode * pRoot, BinaryTreeNode * pNode,
- list<BinaryTreeNode *> & path)
- {
- if(pRoot == pNode)
- {
- path.push_back(pRoot);
- return true;
- }
- if(pRoot == NULL)
- return false;
- path.push_back(pRoot);
- bool found = false;
- found = GetNodePath(pRoot->m_pLeft, pNode, path);
- if(!found)
- found = GetNodePath(pRoot->m_pRight, pNode, path);
- if(!found)
- path.pop_back();
- return found;
- }
- BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot, BinaryTreeNode * pNode1, BinaryTreeNode * pNode2)
- {
- if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
- return NULL;
- list<BinaryTreeNode*> path1;
- bool bResult1 = GetNodePath(pRoot, pNode1, path1);
- list<BinaryTreeNode*> path2;
- bool bResult2 = GetNodePath(pRoot, pNode2, path2);
- if(!bResult1 || !bResult2)
- return NULL;
- BinaryTreeNode * pLast = NULL;
- list<BinaryTreeNode*>::const_iterator iter1 = path1.begin();
- list<BinaryTreeNode*>::const_iterator iter2 = path2.begin();
- while(iter1 != path1.end() && iter2 != path2.end())
- {
- if(*iter1 == *iter2)
- pLast = *iter1;
- else
- break;
- iter1++;
- iter2++;
- }
- return pLast;
- }
在上述算法的基础上稍加变化即可求二叉树中任意两个节点的距离了。
12. 求二叉树中节点的最大距离
即二叉树中相距最远的两个节点之间的距离。
递归解法:
(1)如果二叉树为空,返回0,同时记录左子树和右子树的深度,都为0
(2)如果二叉树不为空,最大距离要么是左子树中的最大距离,要么是右子树中的最大距离,要么是左子树节点中到根节点的最大距离+右子树节点中到根节点的最大距离,同时记录左子树和右子树节点中到根节点的最大距离。
参考代码如下:
- int GetMaxDistance(BinaryTreeNode * pRoot, int & maxLeft, int & maxRight)
- {
-
-
- if(pRoot == NULL)
- {
- maxLeft = 0;
- maxRight = 0;
- return 0;
- }
- int maxLL, maxLR, maxRL, maxRR;
- int maxDistLeft, maxDistRight;
- if(pRoot->m_pLeft != NULL)
- {
- maxDistLeft = GetMaxDistance(pRoot->m_pLeft, maxLL, maxLR);
- maxLeft = max(maxLL, maxLR) + 1;
- }
- else
- {
- maxDistLeft = 0;
- maxLeft = 0;
- }
- if(pRoot->m_pRight != NULL)
- {
- maxDistRight = GetMaxDistance(pRoot->m_pRight, maxRL, maxRR);
- maxRight = max(maxRL, maxRR) + 1;
- }
- else
- {
- maxDistRight = 0;
- maxRight = 0;
- }
- return max(max(maxDistLeft, maxDistRight), maxLeft+maxRight);
- }
13. 由前序遍历序列和中序遍历序列重建二叉树
二叉树前序遍历序列中,第一个元素总是树的根节点的值。中序遍历序列中,左子树的节点的值位于根节点的值的左边,右子树的节点的值位
于根节点的值的右边。
递归解法:
(1)如果前序遍历为空或中序遍历为空或节点个数小于等于0,返回NULL。
(2)创建根节点。前序遍历的第一个数据就是根节点的数据,在中序遍历中找到根节点的位置,可分别得知左子树和右子树的前序和中序遍
历序列,重建左右子树。
- BinaryTreeNode * RebuildBinaryTree(int* pPreOrder, int* pInOrder, int nodeNum)
- {
- if(pPreOrder == NULL || pInOrder == NULL || nodeNum <= 0)
- return NULL;
- BinaryTreeNode * pRoot = new BinaryTreeNode;
-
- pRoot->m_nValue = pPreOrder[0];
- pRoot->m_pLeft = NULL;
- pRoot->m_pRight = NULL;
-
- int rootPositionInOrder = -1;
- for(int i = 0; i < nodeNum; i++)
- if(pInOrder[i] == pRoot->m_nValue)
- {
- rootPositionInOrder = i;
- break;
- }
- if(rootPositionInOrder == -1)
- {
- throw std::exception("Invalid input.");
- }
-
- int nodeNumLeft = rootPositionInOrder;
- int * pPreOrderLeft = pPreOrder + 1;
- int * pInOrderLeft = pInOrder;
- pRoot->m_pLeft = RebuildBinaryTree(pPreOrderLeft, pInOrderLeft, nodeNumLeft);
-
- int nodeNumRight = nodeNum - nodeNumLeft - 1;
- int * pPreOrderRight = pPreOrder + 1 + nodeNumLeft;
- int * pInOrderRight = pInOrder + nodeNumLeft + 1;
- pRoot->m_pRight = RebuildBinaryTree(pPreOrderRight, pInOrderRight, nodeNumRight);
- return pRoot;
- }
同样,有中序遍历序列和后序遍历序列,类似的方法可重建二叉树,但前序遍历序列和后序遍历序列不同恢复一棵二叉树,证明略。
14.判断二叉树是不是完全二叉树
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全
二叉树。
有如下算法,按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时,则该节点右子树必须为空,且后面遍历的节点左
右子树都必须为空,否则不是完全二叉树。
- bool IsCompleteBinaryTree(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return false;
- queue<BinaryTreeNode *> q;
- q.push(pRoot);
- bool mustHaveNoChild = false;
- bool result = true;
- while(!q.empty())
- {
- BinaryTreeNode * pNode = q.front();
- q.pop();
- if(mustHaveNoChild)
- {
- if(pNode->m_pLeft != NULL || pNode->m_pRight != NULL)
- {
- result = false;
- break;
- }
- }
- else
- {
- if(pNode->m_pLeft != NULL && pNode->m_pRight != NULL)
- {
- q.push(pNode->m_pLeft);
- q.push(pNode->m_pRight);
- }
- else if(pNode->m_pLeft != NULL && pNode->m_pRight == NULL)
- {
- mustHaveNoChild = true;
- q.push(pNode->m_pLeft);
- }
- else if(pNode->m_pLeft == NULL && pNode->m_pRight != NULL)
- {
- result = false;
- break;
- }
- else
- {
- mustHaveNoChild = true;
- }
- }
- }
- return result;
- }