数据结构与算法之树

  • 根节点没有父节点,其他每个结点只有一个父节点;
  • 叶节点没有子节点,其他每个结点都有一个或多个子节点;
  • 二叉树:每个结点最多只能有两个子结点。遍历方式有三种:前序(根-左-右)、中序(左-根-右)、后序(左-右-根);
  • 二叉排序树:对于每个结点,左子树所有结点 < 根结点,根结点 < 右子树所有结点;
struct BinaryTreeNode
{
    int m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
};

重建二叉树(剑指offer---面试题6)

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历都不含有重复的数字。

  • BinaryTreeNode *Construct(int *preorder, int *inorder, int length);

解题思路:

(1)根据preorder的第一个数字,遍历inorder找到该数字,即确定了根结点的位置;

(2)inorder中根结点左边为左子树,右边为右子树,再结合preorder就找到左子树和右子树对应的前序序列和中序序列;

(3)用同样的方法分别去构建左子树和右子树,即用递归完成;

树的子结构(剑指offer---面试题18)

题目:输入两颗二叉树A和B,判断B是不是A的子结构;

  • bool HasSubtree(BinaryTreeNode *pRoot1, BinaryTreeNode *pRoot2);

解题思路:第一步在树A中找到和B的根结点的值相等的结点R,第二步判断以结点R为根结点的子树是否包含和树B一样的结构;

(1)若pRoot1的值==pRoot2的值,则判断pRoot1是否包含pRoot2即DoesTree1HaveTree2(pRoot1, pRoot2);

(2)若上述步骤失败,则递归判断pRoot1的左子树是否包含pRoot2即HasSubtree(pRoot1->m_pLeft, pRoot2);

(3)若上述步骤失败,则递归判断pRoot1的右子树是否包含pRoot2即HasSubtree(pRoot1->m_pRight, pRoot2);

(4)关于步骤1中的判断函数DoesTree1HaveTree2:若pRoot2为空返回成功,若pRoot1为空或者pRoot1的值!=pRoot2的值则返回失败;否则递归判断各自的左子树和右子树是否都满足条件即DoesTree1HaveTree2(pRoot1->m_pLeft, pRoot2->m_pLeft) && DoesTree1HaveTree2(pRoot1->m_pRight, pRoot2->m_pRight);

二叉树的镜像(剑指offer---面试题19)

题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像;

  • void MirrorRecursively(BinaryTreeNode *pNode);

解题思路:前序遍历树的每一个结点,若该结点有子结点则交换它的两个子结点,递归遍历完所有结点后就得到的树的镜像;

(1)遍历树的每一个结点pNode,若pNode为空或者pNode没有子结点则直接返回;

(2)交换pNode的两个子结点即pTemp = pNode->m_pLeft; pNode->m_pLeft = pNode->m_pRight; pNode->m_pRight = pTemp;

(3)递归遍历pNode的左子树和右子树即MirrorRecursively(pNode->m_pLeft); MirrorRecursively(pNode->m_pRight); 

从上往下打印二叉树(剑指offer---面试题23)

题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印;

  • void PrintFromTopToBottom(BinaryTreeNode *pTreeRoot);

解题思路:构建辅助队列queue

(1)首先将根结点pTreeRoot入队;

(2)遍历queue中的每个元素,取出队头结点pTreeNode后先打印该结点,然后依次入队该结点pTreeNode的左子结点和右子结点;

(3)重复步骤2,直到queue为空为止;

类似题目:剑指offer面试题60、61

二叉搜索树的后续遍历序列(剑指offer---面试题24)

题目:输入一个整数数组,判断该数组是不是二叉搜索树的后续遍历的结果,如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。

  • bool VerifySequenceOfBST(int sequence[], int length);

解题思路:

(1)后续遍历的最后一个数字即为树的跟结点,根结点前面两部分分别为左子树和右子树;

(2)根据左子树 < 跟结点且右子树 > 跟结点的特点,可以找到根结点的左子树部分和右子树部分,若存在右子树某个结点 < 根结点则直接返回false;

(3)递归判断根结点的左子树和右子树是否为二叉搜索树,即VerifySequenceOfBST(pRoot->m_pLeft)、VerifySequenceOfBST(pRoot->m_pRight);

(4)若左子树和右子树都是二叉搜索树,则返回true;

二叉树中和为某一值的路径(剑指offer---面试题25)

题目:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。从树的根结点开始往下一直到叶结点所经过的结点形成一条路径;

  • void FindPath(BinaryTreeNode *pRoot, int expectedSum);

解题思路:构建辅助容器vector作为path

(1)前序方式遍历树的每一个结点,把该结点添加到路径path中去,并累加该结点的值;

(2)若该结点是叶子结点且path中结点值的和==expectedSum时,则打印该条路径;

(3)若该结点不是叶子结点,则递归访问该结点的左子结点和右子结点,即FindPath(pRoot->m_pLeft, expectedSum, path, currentSum)、FindPath(pRoot->m_pRight, expectedSum, path, currentSum);

(4)该结点访问结束后,递归函数会自动回到它的父结点,因此需要在path中删除该结点;

二叉搜索树和双向链表(剑指offer---面试题27)

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向;

  • BinaryTreeNode *Convert(BinaryTreeNode *pRoot);

解题思路:中序遍历????????????

(1)初始化pLastNode = NULL,然后调用ConvertNode(pRoot, &pLastNode)得到链表的尾结点,从而反向遍历可以得到链表的头结点;

(2)递归遍历左子树即ConvertNode(pCurrent->m_pLeft, pLastNode);

(3)调整指针即pCurrent->m_pLeft = *pLastNode、*pLastNode->m_pRight = pCurrent、*pLastNode = pCurrent;

(4)递归遍历右子树即ConvertNode(pCurrent->m_pRight, pLastNode);

注意:pLastNode指向已经转换好的链表的最后一个结点,即值最大的结点

二叉树的深度(剑指offer---面试题39)

题目一:输入一棵二叉树的根结点,求该树的深度。从根节点到叶结点依次经过的结点形成树的一条路径,最长路径的长度为树的深度;

  • int TreeDepth(BinaryTreeNode *pRoot);

解题思路:

(1)递归调用得到根结点的左子树的深度即left = TreeDepth(pRoot->m_pLeft);

(2)递归调用得到根结点的右子树的深度即right = TreeDepth(pRoot->m_pRight);

(3)返回根结点的深度即max(left, right) + 1;

题目二:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树;

  • bool IsBalanced(BinaryTreeNode *pRoot);

方法一:先调用TreeDepth计算根结点的左右子树的深度,再判断左右子树的深度是否<=1,最后分别递归判断根结点的左右子树是否都为平衡二叉树;

缺点:在判断每个结点是否平衡二叉树的时候,大部分结点会被重复遍历多次,影响性能;

解题思路:后续遍历,每个结点只遍历一次

(1)递归调用判断根结点的左子树是否为平衡二叉树并记录每个结点的深度,即IsBalancedTree(pRoot->m_pLeft, leftDepth);

(2)递归调用判断根结点的右子树是否为平衡二叉树并记录每个结点的深度,即IsBalancedTree(pRoot->m_pRight, rightDepth);

(3)若左右子树均是平衡二叉树且leftDepth和rightDepth的差值<=1,则返回true,否则返回false;

树中两个结点的最低公共祖先(剑指offer---面试题50)

  • TreeNode *GetLastCommonPartner(TreeNode *pRoot, TreeNode *pNode1, TreeNode *pNode2);

题目一:树是二叉搜索树;

解题思路:

(1)从根结点开始和两个输入结点进行比较;

(2)若当前结点 > 两个输入结点的最大值,则下一步遍历当前结点的左子结点;

(3)若当前结点 < 两个输入结点的最小值,则下一步遍历当前结点的右子结点;

(4)做当前结点介于两个输入结点之间,则该结点就是两个结点的最低公共祖先;

题目二:树是普通的树,但是有指向父结点的指针m_pParent;

解题思路:转换为求两个链表的第一个公共结点

(1)根据m_pParent找到可以从pNode1开始到根结点的一条路径,可以记为链表list1;

(2)根据m_pParent找到可以从pNode2开始到根结点的一条路径,可以记为链表list2;

(3)问题转换为求两个链表的第一个公共结点(剑指offer---面试题37);

题目三:树是普通的树,也没有m_pParent;

方法一:从根结点pRoot开始遍历一棵树,每遍历到一个结点时,判断两个输入结点是否在当前结点的子树中,如果是则再遍历当前结点的所有子结点,判断两个输入结点是否在它们的子树中。这样从上到下找到的第一个结点,它自己的子树同时包含两个输入结点且它的所有子结点的子树都不同时包含两个输入结点,则该结点就是两个结点的最低公共祖先;

缺点:判断每个结点是否包含两个输入结点的时候,部分结点会被重复遍历多次,影响性能;

解题思路:辅助链表O(n)+转换为求两个链表的最后一个公共结点

(1)构建辅助链表path1,找到从根结点pRoot到pNode1的路径并保存在path1中;

(2)构建辅助链表path2,找到从根结点pRoot到pNode2的路径并保存在path2中;

(3)问题转换为求两个链表的最后一个公共结点;

二叉树的下一个结点(剑指offer---面试题58)

题目:给定一棵二叉树和其中的一个结点,如何找出中序遍历顺序的下一个结点?树中的结点除了有两个分别指向左右子结点的指针外,还有一个指向父结点的指针m_pParent;

  • BinaryTreeNode *GetNext(BinaryTreeNode *pNode);

解题思路:

(1)若pNode有右子树则返回它的右子结点;

(2)若该结点是它父结点的左子结点则返回它的父结点;

(3)一直向上遍历,直到找到是它父结点的左子结点的结点,返回该父结点

对称的二叉树(剑指offer---面试题59)

题目:请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像是一样的,那么它就是对称的。

  • bool IsSymmetrical(BinaryTreeNode *pRoot);

解题思路:根-左-右和根-右-左两种遍历方式得到的结果相同

(1)调用IsSymmetricalTree(pRoot1, pRoot2);

(2)若都为空则返回true;若一个为空一个不为空则返回false;若两个值不相同则返回false;

(3)递归判断左右子树是否对称,即IsSymmetricalTree(pRoot1->m_pLeft, pRoot2->m_pRight)和IsSymmetricalTree(pRoot1->m_pRight, pRoot2->m_pLeft);

(4)若左右子树都是对称的则返回true,否则返回false;

把二叉树打印成多行(剑指offer---面试题60)

题目:从上到下按层打印二叉树,同一层的结点按从左到右的顺序打印,每一层打印到一行;

  • void Print(BinaryTreeNode *pRoot);

解题思路:构建辅助队列queue和两个计数器(nextLevel和toBePrinted)

(1)首先将根结点入队queue,并初始化nextLevel=0、toBePrinted=1;

(2)遍历queue中的每个元素,取出队头结点pTreeNode后先打印该结点,然后依次入队该结点pTreeNode的左子结点(nextLevel++)和右子结点(nextLevel++);

(3)queue出队且toBePrinted--,若toBePrinted==0,则打印回车换行符,设置toBePrinted = nextLevel、nextLevel = 0;

(4)重复步骤2和3,直到queue为空为止;

按之字形顺序打印二叉树(剑指offer---面试题61)

题目:请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二行按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推,例如(1)(3、2)(4、5、6、7)(15、14、13、12、11、10、9、8)等等;

  • void Print(BinaryTreeNode *pRoot);

解题思路:构建两个辅助栈stacks[2]即stack[current]和stack[next]

打印当前行current结点的时候把下一行next的结点保存到相应的栈里:若当前行current为奇数行,则先保存左子结点再保存右子结点到stack[next]中,若当前行current为偶数行,则先保存右子结点再保存左子结点到stack[next]中;

(1)初始化current=0、next=1,首先将根结点入栈stack[current];

(2)遍历stack[current]的每个元素,取出栈顶元素pTreeNode后先打印该结点,然后根据current的奇偶性把该结点pTreeNode的两个子结点保存到栈stack[next]中;

(3)若stack[current]为空,则打印回车换行符并交换current和next的值,即设置current = 1 - current、next = 1 - next;

(4)重复步骤2和3,直到stack[current]为空为止;

二叉搜索树的第k个结点(剑指offer---面试题63)

题目:给定一棵二叉搜索树,请找出其中的第k大的结点。

  • BinaryTreeNode *KthNode(BinaryTreeNode *pRoot, unsigned int k);

解题思路:中序遍历(类似面试题27:二叉搜索树与双向链表)

(1)初始化全局遍历gTimes = 0,调用KthNodeCore(pRoot, k);

(2)递归遍历左子树KthNodeCore(pRoot->m_pLeft, k);

(3)gTimes++,若gTimes == k则结点pRoot就是第k个结点;

(4)递归遍历右子树KthNodeCore(pRoot->m_pRight, k);

转载于:https://www.cnblogs.com/bo1990/p/11442056.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值