树(二叉树)系列算法编程考题
部分内容是从网上其他博客中整理而来,方便大家对相关内容有一个总结。
主要进行以下几个方面的总结
1)计算一棵树的深度(高度)非递归实现
2)计算一棵树的宽度(并输出每一层的宽度)
3)判断一棵树是否是平衡二叉树(至少两种方法)
4)输出一棵树的所有路径(从根节点到叶子结点)
5)判断两棵树是否相同
6)判断一棵树是否是另一棵树的子树
7)判断一棵树是否是对称二叉树(镜像后和自己一样)
8)将一棵树对称(镜像)后输出
9)明确二叉搜索树的前驱后继关系
10)给定一棵二叉搜索树,找出其中第K大的结点。
计算一棵树的深度(高度)非递归实现
三种实现方法
计算二叉树的高度可以采用几种不同的算法。
算法一:采用后序遍历二叉树,结点最大栈长即为二叉树的高度;
算法二:层次遍历二叉树,最大层次即为二叉树的高度;
算法三:采用递归算法,求二叉树的高度。
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
typedef struct BiTNode{
char data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
void CreateTree(BiTree &T)
{
char ch;
cin>>ch;
if(ch=='#') T=NULL;
else
{
T=(BiTree)malloc(sizeof(BiTNode));
if(!T) cout<<"生成结点错误!"<<endl;
T->data=ch;
CreateTree(T->lchild);
CreateTree(T->rchild);
}
}
//法1:后序遍历,结点最大栈长即为树的高度
int BT_high(BiTree T)
{
BiTree p=T,r=NULL;
int max=0; //树高
stack<BiTree> s;
while(p||!s.empty())
{
if(p!=NULL)
{
s.push(p);
p=p->lchild;
}
else
{
p=s.top();
if(p->rchild!=NULL && p->rchild!=r)
p=p->rchild;
else
{
if(s.size()>max) max=s.size();//最大层次即为高度
r=p;
s.pop();
p=NULL;
}
}
}
return max;
}
//法2:层次遍历,层次即为高度
int BT_level_depth(BiTree T)
{
if(!T) return 0;
BiTree p=T,Q[100];
int front=-1,rear=-1,last=0,level=0;
Q[++rear]=p;
while(front<rear)
{
p=Q[++front];
if(p->lchild)
Q[++rear]=p->lchild;
if(p->rchild)
Q[++rear]=p->rchild;
if(front==last)
{
last=rear;
level++; //层次+1
}
}
return level;
}
//法3:递归求树高1
int max1=0;//树高
int BT_depth1(BiTree T,int depth)
{
if(T)
{
if(T->lchild)
BT_depth1(T->lchild,depth+1);
if(T->rchild)
BT_depth1(T->rchild,depth+1);
}
if(depth>max1)
max1=depth;
return depth;
}
//法3:递归求树高2
int Height (BiTree T)
{
if(T==NULL) return 0;
else
{
int m = Height ( T->lchild );
int n = Height(T->rchild);
return (m > n) ? (m+1) : (n+1);
}
}
int main()
{
BiTree T=NULL;
CreateTree(T);
cout<<"后序遍历求树高:"<<endl;
cout<<BT_high(T)<<endl;
cout<<"层次遍历求树高:"<<endl;
cout<<BT_level_depth(T)<<endl;
cout<<"递归求树高1:"<<endl;
BT_depth1(T,1);
cout<<max1<<endl;
cout<<"递归求树高2:"<<endl;
cout<<Height(T)<<endl;
return 0;
}
计算一棵树的宽度(并输出每一层的宽度)
求解思路: 这里需要用到二叉树的层次遍历,即广度优先周游。在层次遍历的过程中,通过读取队列中保留的上一层的节点数来记录每层的节点数,以获取所有层中最大的节点数。
也可以用上面的求解深度时候用的那种队列,性质是一样的,用队列来辅助求解。
具体实现:
//求二叉树的宽度
int treeWidth(BinaryTreeNode *pRoot){
if (pRoot == NULL)
return 0;
int nLastLevelWidth = 0;//记录上一层的宽度
int nCurLevelWidth = 0;//记录当前层的宽度
queue<BinaryTreeNode*> myQueue;
myQueue.push(pRoot);//将根节点入队列
int nWidth = 1;//二叉树的宽度
nLastLevelWidth = 1;
BinaryTreeNode *pCur = NULL;
while (!myQueue.empty())//队列不空
{
while (nLastLevelWidth!= 0){
pCur = myQueue.front();//取出队列头元素
myQueue.pop();//将队列头元素出对队
if (pCur->m_pLeft != NULL)
myQueue.push(pCur->m_pLeft);
if (pCur->m_pRight != NULL)
myQueue.push(pCur->m_pRight);
nLastLevelWidth--;
}
nCurLevelWidth = myQueue.size();
nWidth = nCurLevelWidth > nWidth ? nCurLevelWidth : nWidth;
nLastLevelWidth = nCurLevelWidth;
}
return nWidth;
}
判断一棵树是否是平衡二叉树(至少两种方法)
解题思路一:
根据二叉树的定义,我们可以递归遍历二叉树的每一个节点来,求出每个节点的左右子树的高度,如果每个节点的左右子树的高度相差不超过1,按照定义,它就是一颗平衡二叉树。
参考如下代码:
//求二叉树的高度
int treeDepth(BinaryTreeNode* root){
if(root==NULL){
return 0;
}
int nLeft=treeDepth(root->m_pLeft);
int nRight=treeDepth(root->m_pRight);
return nLeft>nRight?nLeft+1:nRight+1;
}
bool isBalanced(BinaryTreeNode* root){
if(root==NULL)
return true;//空树是平衡二叉树
int left= treeDepth(root->m_pLeft);
int right= treeDepth(root->m_pRight);
int diff=left-right;
if(diff>1||diff<-1)
return false;
return isBalanced(root->m_pLeft)&&isBalanced(root->m_pRight);
}
优点:只要求出给定二叉树的高度,就可以方便的判断出二叉树是平衡二叉树,思路简单,代码简洁。
缺点:由于每个节点都会被重复遍历多次,这造成时间效率不高。
集体思路二:
采用后序遍历的方式遍历二叉树的每一个节点,在遍历到一个节点之前我们就已经遍历了它的左右子树。此时,记录每个节点为根节点的树的高度,就可以一边遍历一边判断每个节点是不是平衡的。
参考代码:
bool IsBalanced(BinaryTreeNode* pRoot, int* depth){
if(pRoot== NULL){
*depth = 0;
return true;
}
int nLeftDepth,nRightDepth;
bool bLeft= IsBalanced(pRoot->m_pLeft, &nLeftDepth);
bool bRight = IsBalanced(pRoot->m_pRight, &nRightDepth);
if (bLeft && bRight){
int diff = nRightDepth-nLeftDepth;
if (diff<=1 && diff>=-1) //左右字树高度差绝对值不超过1
{
*depth = 1+(nLeftDepth > nRightDepth ? nLeftDepth : nRightDepth);
return true;
}
}
return false;
}
bool IsBalanced(BinaryTreeNode* pRoot)
{
int depth = 0;
return IsBalanced(pRoot, &depth);
}
输出一棵树的所有路径(从根节点到叶子结点)
主要的树系列题目,这里有总结,比较不错。https://blog.csdn.net/sunmenggmail/article/details/7466635
解题思路:
路径的定义就是从根节点到叶子节点的点的集合。 还是利用递归:用一个list来保存经过的节点,如果已经是叶子节点了,那么打印list的所有内容;如果不是,那么将节点加入list,然后继续递归调用该函数,只不过,入口的参数变成了该节点的左子树和右子树。
void printPathsRecur(treeNode* node, int path[], int pathLen) {
if (node == NULL)
return;
// append this node to the path array
path[pathLen] = node->data;
pathLen++;
// it's a leaf, so print the path that led to here
if (node->lchild == NULL && node->rchild == NULL)
{
printArray(path, pathLen);
}
else
{
// otherwise try both subtrees
printPathsRecur(node->lchild, path, pathLen); //函数调用栈操作编译器自动实现!!!(自动回调,返回到上一个状态)
printPathsRecur(node->rchild, path, pathLen);
}
}
void printPaths(treeNode* node) {
int path[1000];
printPathsRecur(node, path, 0);
}
//用到的辅助函数:
/**
* 打印数组
*/
void printArray(int ints[], int len) {
int i;
for (i = 0; i < len; i++) {
printf("%d ", ints[i]);
}
printf("\n");
}
一种更加直接的存储形式
//存储所有的路径
vector<vector<char>> allPath;
//某条路径
vector<char> path;
void findAllPath(BinaryTreeNode *root)
{
if (root == NULL)
return;
if (root->left == NULL && root->right == NULL)
{
path.push_back(root->value);
allPath.push_back(path);
}
else
{
path.push_back(root->value);
if (root->left)
findAllPath(root->left);
if (root->right)
findAllPath(root->right);
}
path.pop_back();
}
附加:在二叉树中找出和为某一值的所有路径
有了上面讲的思想基本上解决这个问题就没有什么问题了。
void FindPath(treeNode* root, int path[],int pathLen,int expectedSum, int currentSum)
{
if (root == NULL)
return;
currentSum += root->data;
path[pathLen] = root->data;
pathLen ++;
if (currentSum == expectedSum && root->lchild == NULL && root->rchild ==NULL)
{
printArray(path,pathLen);
}
if (root->lchild != NULL)
{
FindPath(root->lchild,path,pathLen,expectedSum,currentSum);
}
if (root->rchild != NULL)
{
FindPath(root->rchild,path,pathLen,expectedSum,currentSum);
}
currentSum -= root->data;
}
判断两棵树是否相同
解析:
由题中对二叉树相等的定义判断可知,该题的解决思路是采用递归的方法,“而且A和B的左右子树相等或者左右互换相等”这句是最大的提示。因此我们不难写出程序,如下:
具体实现代码:
bool CompTree(TreeNode* tree1,TreeNode* tree2)
{
if(tree1 == NULL && tree2 == NULL)
return true;
if(tree1 != NULL && tree2 != NULL)
{
if(tree1->c == tree2->c)
{
if(CompTree(tree1->lchild, tree2->lchild) &&
CompTree(tree1->rchild, tree2->rchild) ||
CompTree(tree1->rchild, tree2->lchild) &&
CompTree(tree1->lchild, tree2->rchild))
{
return true;
}
}
}
return false;
}
判断一棵树是否是另一棵树的子树
思路
问题分两步: •找值相同的根结点(遍历解决) •判断两结点是否包含(递归:值、左孩子、右孩子分别相同)
//查找到值相同的根节点,进行判断是否相等的操作
bool IsPart(TreeNode *root1, TreeNode *root2)
{
if (root2 == NULL)
return true;
if (root1 == NULL)
return false;
if (root1->val != root2->val)
return false;
return IsPart(root1->left, root2->left) &&
IsPart(root1->right, root2->right);
}
//遍历找值相同的根节点,并判断
bool IsPartTree(TreeNode *root1, TreeNode *root2)
{
bool result = false;
if (root1 != NULL && root2 != NULL)
{
if (root1->val == root2->val)
result = IsPart(root1, root2);
if (!result)
result = IsPartTree(root1->left, root2);
if (!result)
result = IsPartTree(root1->right, root2);
}
return result;
}
判断一棵树是否是对称二叉树(镜像后和自己一样)
分析:
判断一棵二叉树本身是否是镜像对称的,这个问题可以转化为:二叉树的左子树与右子树是否是镜像对称的。 问题一经转化,就有一种似曾相识的感觉,刚分析过判断两棵二叉树是否相等的问题,而这道题只不过是把“相等”换为“对称”,方法其实是一样的!
本题的解法有递归和迭代两种方法: (设二叉树左子树为p,右子树为q,p、q均指向左右子树的根)
递归实现:
递归:p和q的值相等,并且p的左子树与q的右子树对称,p的右子树与q的左子树对称
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*
class Solution {
public:
bool isSymmetric(TreeNode *root)
{
if(!root) return true; //空树是对称的
return symmetric(root->left,root->right);
}
private:
bool symmetric(TreeNode *p,TreeNode *q) /*判断两棵树是否对称*/
{
if(!p && !q) return true; //都是空
if(!p || !q) return false; //只有一个空
return (p->val==q->val)&&symmetric(p->left,q->right) && symmetric(p->right,q->left);
/*树p和树q对称的条件:p和q的值相同,并且p的左子树与q的右子树对称,p的右子树与q的左子树对称*/
}
};
迭代实现
迭代:维护一个栈,我们在将节点入栈的时候,顺序不是 p->left , q->left ,p->right ,q->right(判断两棵树相等的入栈顺序),而是p->left , q->right , p->right ,q->left,为什么?因为我们要判断的是对称,所以p->left 对应q->right,p->right 对应q->left,它们的入栈顺序理应如此。
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isSymmetric(TreeNode *root) {
if(!root) return true; //空树是对称的
stack<TreeNode *> s;
TreeNode *p=root->left,*q=root->right;
s.push(p);
s.push(q); //即使是空节点,也是可以push到栈里的,栈并不为空。
while(!s.empty())
{
p=s.top();s.pop();
q=s.top();s.pop();
if(!p && !q) continue; //p、q都是空节点
if(!p || !q) return false; //有一个为空,不对称
if(p->val!=q->val) return false; //值不相等,不对称
s.push(p->left);s.push(q->right);
s.push(p->right);s.push(q->left);
}
return true;
}
};
将一棵树对称(镜像)后输出
思路:
先序遍历这棵树的每个结点,如果遍历到的结点有子结点,则交换它的两个子结点。当交换完所有非叶子结点的左右子结点之后,就得到了树的镜像。
//递归实现二叉树的镜像,按照先序遍历,如果遇到空的节点或者叶子节点就返回,否则交换两个子树后再改变左右子树
void MirrorBinaryTree1(BinaryTreeNode* root)
{
if (root == NULL || (root->leftchild == NULL && root->rightchild == NULL))
{
return;
}
//交换左右子树
BinaryTreeNode * tmp = root->leftchild;
root->leftchild = root->rightchild;
root->rightchild = tmp;
if (root->leftchild)
{
MirrorBinaryTree1(root->leftchild);
}
if (root->rightchild)
{
MirrorBinaryTree1(root->rightchild);
}
}
明确二叉搜索树的前驱后继关系
二叉查找树按照二叉树进行组织。二叉查找树关键字的存储方式总是瞒住二叉查找树性质:
设x为二查查找树种一个节点。如果y是x的左子树中的一个节点,那么key[x] >= key[y]。如果y是x的右子树的一个节点,那么key[x] <= key[y]。
这样对二叉查找树进行中序遍历就可得到书中所有元素的一个非降序排列。
查找某一个存在节点的前驱和后继。某一个节点x的后继就是大于key[x]的关键字中最小的那个节点,前驱就是小于key[x]的关键字中最大的那个节点。查找二叉前驱和后继节点的算法如下所示:
typedef struct _node {
struct _node *left_child;
struct _node *right_child;
struct _node * parent;
ctype data;
}node; //树节点数据结构定义
typedef node* Tree;
//查找二叉查找树中关键字最小的节点,返回指向该节点的指针
Tree tree_minimum(Tree root)
{
Tree p = root;
while (p->left_child != null)
p = p->left_child;
return p;
}
//查找二叉查找树中关键字最大的节点,返回指向该节点的指针
Tree tree_maxmum(Tree root)
{
Tree p = root;
while (p->right_child != null)
{
p = p->right_child;
}
return p;
}
//查找二叉查找树中节点x的后继节点,返回指向该节点的指针
//在查找过程中,如果节点x右子树不为空,那么返回右子树的最小节点即可
//如果节点x的右子树为空,那么后继节点为x的某一个祖先节点的父节点,而且该祖先节点是作为其父节点的左儿子
Tree tree_successor(Tree x)
{
if (x->right_child != null)
return tree_minimum(x->right_child);
//x用来保存待确定的节点
//y为x的父节点
Tree y = x->parent;
while (y != NULL && x == y->right_child)
{
x = y;
y = y->parent;
}
return y;
}
//查找二叉查找树中节点x的前驱节点,返回指向该节点的指针
//在查找过程中,如果节点x左子树不为空,那么返回左子树的最大节点即可
//如果节点x的左子树为空,那么前驱节点为x的某一个祖先节点的父节点,而且该祖先节点是作为其父节点的右儿子
Tree tree_predecessor(Tree x)
{
if (x->left_child != null)
return tree_maxmum(x->left_child);
Tree y = x->parent;
while (y != NULL && x == y->left_child)
{
x = y;
y = y->parent;
}
return y;
}
给定一棵二叉搜索树,找出其中第K大的结点。
思路:
二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序。 所以,按照中序遍历顺序找到第k个结点就是结果。
代码–递归中序遍历实现
public class Solution
{
int index = 0; //计数器
TreeNode KthNode(TreeNode root, int k)
{
if(root != NULL)//中序遍历寻找第k个
{
TreeNode node = KthNode(root.left,k);
if(node!=NULL)
return node;
index++;
if(index==k)
return root;
node = KthNode(root.right,k);
if(node != NULL)
return node;
}
}
}
代码–非递归中序遍历实现
TreeNode KthNode(TreeNode root, int k){
if(root==NULL||k==0)
return NULL;
Stack<TreeNode> stack = new Stack<TreeNode>();
int count = 0;
TreeNode node = root;
do{
if(node!=NULL){
stack.push(node);
node = node.left;
}else{
node = stack.pop();
count++;
if(count==k)
return node;
node = node.right;
}
}while(node!=NULL||!stack.empty());
return NULL;
}
二叉树的中序遍历程序:
void midOrderStack(BinaryTreeNode* root)
{
if(root==NULL)
return;
stack<BinaryTreeNode*> stack;
BinaryTreeNode* cur=root;
while(!stack.empty()||cur!=NULL){
while(cur)
{
stack.push(cur);
cur=cur->lchild; //出来的时候,cur已经指向空了
}
cur=stack.top();
cout<<" "<<cur->data; //visit(count++,进行判断就好。底子就是二叉树的中序遍历)
stack.pop();
cur=cur->rchild;
}
}