数据结构:
四种主要的遍历思想为:
深度优先:
前序遍历:根结点 —> 左子树 —> 右子树
中序遍历:左子树—> 根结点 —> 右子树
后序遍历:左子树 —> 右子树 —> 根结点
广度优先:
层次遍历:只需按层次遍历即可
基础题
给定一个二叉树的根结点root,请依次返回二叉树的先序,中序和后续遍历(二维数组的形式)。
递归解法
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class TreeToSequence {
public:
void preOrder(TreeNode* root, vector<int>& v){
if(root == NULL) return;
v.push_back(root->val);
preOrder(root->left, v);
preOrder(root->right, v);
}
void midOrder(TreeNode* root, vector<int>& v){
if(root == NULL) return;
midOrder(root->left, v);
v.push_back(root->val);
midOrder(root->right, v);
}
void afterOrder(TreeNode* root, vector<int>& v){
if(root == NULL) return;
afterOrder(root->left, v);
afterOrder(root->right, v);
v.push_back(root->val);
}
vector<vector<int> > convert(TreeNode* root) {
// write code here
vector<vector<int>> result;
vector<int> pre, mid, after;
result.resize(3);
preOrder(root, pre);
midOrder(root, mid);
afterOrder(root, after);
result = {pre, mid, after};
return result;
}
};
非递归解法
思路:
二叉树的非递归解法需要借助栈来实现,具体三种遍历的方法如下
先序遍历:
s1:创建一个栈为s
s2:将二叉树的头节点head压入栈s中。
s3:将s中的头节点出栈,并且记为cur,如果cur的右节点不为空,将右节点入栈,如果cur的左节点不为空,左节点入栈
s4:重复s3直到栈为空时结束
头节点入栈
1为cur
打印序列:1
2为cur
打印序列:1 2
5为cur
打印序列:1 2 4 5
中序遍历:
s1:创建一个栈为s,head为cur
s2:将cur压入栈中,不断的使cur = cur.left,重复步骤s2。
s3:当cur为空时,弹出头节点记为node并打印,cur = node.right,重复步骤二,若cur的右节点为空,继续出栈记为node并打印
s4:当stack为空,cur为空,结束
cur = 4
cur = 4.left
出栈:4
cur = 4.right
出栈:4 2
cur = 2.right
cur = 5.right
出栈:4 2 5 1
cur = 1
后序遍历:
s1:创建一个栈为s,h为最近一次弹出的节点,c指向栈顶节点,h初始为head,c为null
s2:t指向栈顶节点,此时有三种情况
1.若c的左节点不为空,h不等于c的左节点和右节点,h的左节点入栈
2.若不满足条件1,c的右节点不为空,h不等于c的右孩子,h的右节点入栈
3.若不满足1,2条件,弹出c,h = t
3.重复s2,直到s为空
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
void pre(TreeNode* root, vector<int> &v){
stack<TreeNode*> s;
TreeNode* cur = NULL;
s.push(root);
while(!s.empty()){
cur = s.top();
v.push_back(cur->val);
s.pop();
if(cur->right != NULL) s.push(cur->right);
if(cur->left != NULL) s.push(cur->left);
}
}
void middle(TreeNode* root, vector<int> &v){
stack<TreeNode*> s;
TreeNode* cur = root;
TreeNode* node = NULL;
//s.push(root);
while(!s.empty() || cur != NULL){
if(cur != NULL){
s.push(cur);
cur = cur->left;
}else{
node = s.top();
v.push_back(node->val);
s.pop();
cur = node->right;
}
}
}
void after(TreeNode* root, vector<int> &v){
stack<TreeNode*> s;
TreeNode* c = NULL;//top
TreeNode* h = root;//pop node
s.push(root);
c = s.top();
while(!s.empty()){
if(c->left != NULL && h!=c->left && h!=c->right){
s.push(c->left);
}else if(c->right != NULL && h != c->right){
s.push(c->right);
}else{
h = s.top();
v.push_back(h->val);
s.pop();
}
c = s.top();
}
}
class TreeToSequence {
public:
vector<vector<int> > convert(TreeNode* root) {
vector<vector<int>> res;
vector<int> temp;
// write code here
pre(root, temp);
res.push_back(temp);
temp.clear();
middle(root, temp);
res.push_back(temp);
temp.clear();
after(root, temp);
res.push_back(temp);
temp.clear();
return res;
}
};
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot == NULL) return 0;
int left = TreeDepth(pRoot->left);
int right = TreeDepth(pRoot->right);
return (left > right)? left+1:right+1;
}
};
有一棵二叉树,请设计一个算法判断这棵二叉树是否为平衡二叉树。
基础知识:
平衡二叉树:
任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过1
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int getDepth(TreeNode* root){
if(root == NULL) return 0;
int left = getDepth(root->left);
int right = getDepth(root->right);
return (left>right)? (left+1) : (right+1);
}
bool IsBalanced_Solution(TreeNode* pRoot) {
if(pRoot == NULL) return true;
int left = getDepth(pRoot->left);
int right = getDepth(pRoot->right);
if(abs(left-right) > 1){
return false;
}
return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right);;
}
};
从上往下打印出二叉树的每个节点,同层节点从左至右打印。(广度优先遍历)
思路:
该题采用队列的方式来实现,将根节点0入队列
s1:当打印根节点0时,将0的两个节点1和2入队列,0出队列
s2:将队列最开始的元素1打印,出队列,将1的两个子节点3和4入队列
s3:不断重复s2,直到队列为空
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> res;
queue<TreeNode*> q;
TreeNode* cur;
if(root == NULL) return res;
q.push(root);
while(q.size()){
cur = q.front();
res.push_back(cur->val);
q.pop();
if(cur->left){
q.push(cur->left);
}
if(cur->right){
q.push(cur->right);
}
}
return res;
}
};
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行
思路:
这道题和上一道题类似,也是用队列的方法来实现的,思路在上一题的基础上再添加两个变量,nextLevel用来存储下一层的节点数,toBePrinted用来存储当前层未打印的节点数。
s1:将根节点a入队列,toBePrinted初始值为1,nextLevel为0
s2:当打印根节点a时,a出队列,toBePrinted减1,若根节点的左节点b存在,nextLevel自增1,右节点c同理,b,c入队列,当toBePrinted等于0时,本行打印结束,并且toBePrinted=nextLevel
s3:重复s2直到栈为空
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int>> Print(TreeNode* pRoot) {
vector<vector<int>> res;
vector<int> temp;
if(pRoot == nullptr) return res;
queue<TreeNode*> q;
q.push(pRoot);
int nexlLevel = 0;
int toBePrinted = 1;
TreeNode* node;
while(!q.empty()){
node = q.front();
q.pop();
temp.push_back(node->val);
if(node->left != nullptr){
q.push(node->left);
++nexlLevel;
}
if(node->right != nullptr){
q.push(node->right);
++nexlLevel;
}
toBePrinted--;
if(toBePrinted == 0){
res.push_back(temp);
temp.clear();
toBePrinted = nexlLevel;
nexlLevel = 0;
}
}
return res;
}
};
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
思路:
这一题使用栈的数据结构,从之前的两个问题可以知道,顺序打印每一个节点使用队列的数据结构,那么逆序打印使用的是栈的数据结构。
从图上可以知道:
当行数为奇数行1,3时先入栈左节点再入栈右节点,当行数为偶数时先入栈右节点再入栈左节点,这道题用两个栈分别存储当前层的节点的和下一层的节点
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
vector<int> temp;
if(pRoot == nullptr) return res;
stack<TreeNode*> levels[2];
int current = 0;
int next = 1;
levels[current].push(pRoot);
while(!levels[1].empty() || !levels[0].empty()){
TreeNode* pNode = levels[current].top();
temp.push_back(pNode->val);
levels[current].pop();
if(current == 0){
if(pNode->left != nullptr) levels[next].push(pNode->left);
if(pNode->right != nullptr) levels[next].push(pNode->right);
}else{
if(pNode->right != nullptr) levels[next].push(pNode->right);
if(pNode->left != nullptr) levels[next].push(pNode->left);
}
if(levels[current].empty()){
res.push_back(temp);
temp.clear();
current = 1-current;
next = 1-next;
}
}
return res;
}
};
进阶题
和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
思路:
这道题可以采取二叉树递归的思路来解:二叉树的前序遍历中第一个节点是二叉树的根节点,二叉树的中序遍历中,根节点左边的节点是左子树,根节点右边的节点是右子树。
s1:求出二叉树的根节点
s2:找出二叉树的左子树以及右子树
s3:递归重复以上步骤
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
int len = pre.size();
if (len == 0){
return NULL;
}
int root = 0;
TreeNode* head = new TreeNode(pre[0]);
vector<int> preLeft, inLeft, preRight, inRight;
for (int i = 0; i < len; i ++){
if (pre[0] == vin[i]){
root = i;
break;
}
}
for (int i = 0; i < root; i++){
inLeft.push_back(vin[i]);
preLeft.push_back(pre[i+1]);
}
for (int i = root+1; i < len; i++){
inRight.push_back(vin[i]);
preRight.push_back(pre[i]);
}
head->left = reConstructBinaryTree(preLeft, inLeft);
head->right = reConstructBinaryTree(preRight, inRight);
return head;
}
};
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:
这题采用递归的思路
s1:在a树中找出与b树根节点相等的值
s2:判断a树中与b树根节点相等的子树的左右子树是否相等
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool result;
if(pRoot1!= NULL && pRoot2!=NULL){
if(pRoot1->val == pRoot2->val){
result = HasSubtree2(pRoot1,pRoot2);
}
if(!result){
result = HasSubtree(pRoot1->right, pRoot2);
}
if(!result){
result = HasSubtree(pRoot1->left, pRoot2);
}
}
return result;
}
bool HasSubtree2(TreeNode* pRoot1, TreeNode* pRoot2){
if(pRoot2 == NULL){
return true;
}
if(pRoot1 == NULL){
return false;
}
if(pRoot1->val != pRoot2->val){
return false;
}
return HasSubtree2(pRoot1->left, pRoot2->left) && HasSubtree2(pRoot1->right, pRoot2->right);
}
};
操作给定的二叉树,将其变换为源二叉树的镜像。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot==NULL) return;
if(pRoot->left == NULL && pRoot->right == NULL) return;
TreeNode *temp = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = temp;
if(pRoot->left){
Mirror(pRoot->left);
}
if(pRoot->right){
Mirror(pRoot->right);
}
}
};
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool isSame(TreeNode* r1, TreeNode* r2){
if(r1 == NULL && r2 == NULL) return true;
if(r1 == NULL || r2 == NULL) return false;
if(r1->val != r2->val) return false;
return isSame(r1->left, r2->right) && isSame(r1->right, r2->left);
}
bool isSymmetrical(TreeNode* pRoot)
{
if (pRoot == NULL) return true;
return isSame(pRoot->left, pRoot->right);
}
};
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:
关于二叉树中序遍历的下一个节点有三种情况:
1.如果一个节点有右子树,那么下一个节点为从右子树出发一直指向左节点的指针
例如:b的下一个节点为h
2.如果一个节点没有右子树,并且是父节点的左节点,下一个节点为父节点。例如:h下一个节点为e
3.如果一个节点既没有右子树,并且是父节点的右节点,这时就要沿着父指针一直向上遍历,直到遍历到的当前节点满足条件2,那么下一个节点为该节点的父节点。例如:i的下一个节点为a
/*
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next;
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
*/
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == nullptr){
return nullptr;
}
TreeLinkNode* pNext = nullptr;
if(pNode->right != nullptr){
TreeLinkNode* pRight = pNode->right;;
while(pRight->left != nullptr){
pRight = pRight->left;
}
pNext = pRight;
}
else if(pNode->next != nullptr){
TreeLinkNode* pCurrent = pNode;
TreeLinkNode* pParent = pNode->next;
while(pParent != nullptr && pCurrent == pParent->right){
pCurrent = pParent;
pParent = pParent->next;
}
pNext = pParent;
}
return pNext;
}
};
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
基础知识:
二叉搜索树是一种节点值之间具有一定数量级次序的二叉树,对于树中每个节点:
若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
若其右子树存在,则其右子树中每个节点的值都不小于该节点值。
二叉树的后序遍历结果中,最后一个元素为二叉树的根节点
思路:
根据二叉树和二叉搜索树的基本性质,序列最后一个元素为根节点,之前的序列可以看作两个部分,比根节点小的序列为左子树,比根节点大的序列为右子树。
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
if(sequence.size() == 0) return false;
return func(sequence, 0, sequence.size()-1);
}
bool func(vector<int> array, int begin, int end){
int root = array[end];
int i;
for(i = begin; i < end; i++){
if(array[i]>root) break;
}
int j = i;
for(; j < end; j++){
if(array[j]<root) return false;
}
bool left = true;
if(begin<i-1) left = func(array, begin, i-1);
bool right = true;
if(i<end-1) right = func(array, i, end-1);
return left && right;
}
};
复杂题
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路:
该题使用递归的思想,当访问到某一节点时,若该节点不是叶节点,该节点下面的路径和等于输入的值减去这一节点的值。当该节点访问结束时,递归自动回归到它的父节点。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<vector<int> > buffer;
vector<int> tmp;
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
if(root==NULL)
return buffer;
tmp.push_back(root->val);
if((expectNumber-root->val)==0 && root->left==NULL && root->right==NULL)
{
buffer.push_back(tmp);
}
FindPath(root->left,expectNumber-root->val);
FindPath(root->right,expectNumber-root->val);
if(tmp.size()!=0)
tmp.pop_back();
return buffer;
}
};
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void convert(TreeNode* root, TreeNode** lastNode){
if(root == nullptr) return;
TreeNode* pNode = root;
if(root->left != nullptr){
convert(root->left, lastNode);
}
pNode->left = *lastNode;
if(*lastNode != nullptr){
(*lastNode)->right = pNode;
}
*lastNode = pNode;
if(root->right != nullptr){
convert(root->right, lastNode);
}
}
TreeNode* Convert(TreeNode* pRootOfTree)
{
TreeNode* lastNode = nullptr;
convert(pRootOfTree, &lastNode);
TreeNode* pHead = lastNode;
while(pHead != nullptr && pHead->left != nullptr){
pHead = pHead->left;
}
return pHead;
}
};
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
思路:
二叉搜索树的中序遍历为节点从小到大排序的顺序,第k小的值为中序遍历的第k个节点
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot == nullptr || k == 0) return nullptr;
return findTarget(pRoot, k);
}
TreeNode* findTarget(TreeNode* pRoot, int &k){
TreeNode* target = nullptr;
if(pRoot->left != nullptr){
target = findTarget(pRoot->left, k);
}
if(target == nullptr){
if(k==1)
target = pRoot;
//遍历一次k减1
k--;
}
if(target == nullptr && pRoot->right != nullptr){
target = findTarget(pRoot->right, k);
}
return target;
}
};
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。
序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。