二叉树

1 二叉树与分治递归

几乎所有的二叉树问题都可以通过分治解决,包括二叉树的遍历、二叉树中求最大分支长度、最大深度任意两个结点间的最大距离,搜索二叉树,平衡二叉树 等等,只要题目中没有严格的时间性能的要求,使用递归的方法时都可以优先考虑分治递归的方法,除了二叉树的层序遍历以外。但然很多题目虽然也可以用变通递归来实现,但解题过程更难想到。

1.1 分治递归与普通递归的模板

Template 1: Traverse

public class Solution {
    public void traverse(TreeNode root) {
        if (root == null) {
            return;
        }
        // do something with root
        traverse(root.left);
        // do something with root
        traverse(root.right);
        // do something with root
    }
}


Tempate 2: Divide & Conquer

public class Solution {
    public ResultType traversal(TreeNode root) {
        // null or leaf
        if (root == null) {
            // do something and return;
        }
        
        // Divide
        ResultType left = traversal(root.left);
        ResultType right = traversal(root.right);
        
        // Conquer
        ResultType result = Merge from left and right.
        return result;
    }
}

1.2 经典的二叉树前中后遍历也可以用分治递归方法

    /*method2 division and conquer*/
    vector<int> preorderTraversal(TreeNode *root) {
        vector<int> result;
        if (root == NULL) {
            return result;
        }
        //division
        vector<int> left, right;
        left = preorderTraversal(root->left);
        right = preorderTraversal(root->right);
        //conquer
        result.push_back(root->val);
        result.insert(result.end(), left.begin(), left.end());
        result.insert(result.end(), right.begin(), right.end());
        
        return result;
    }

2 二叉树问题用非递归方法解决

二叉树问题用非递归方法解决一般是题目要求时间有限,不能使用递归方式,或者直接要求使用非递归方法。

2.1 二叉树前序遍历非递归

void preorder(TreeNode *root) {
    if (root == NULL) {
        return;
    }
    stack<TreeNode*> stk;
    stk.push(root);
    while (!stk.empty()) {
        TreeNode *curt = stk.top();
        cout<<curt->val<<" ";
        stk.pop();
        if (curt->right != NULL) {
            stk.push(curt->right);
        }
        if (curt->left != NULL) {
            stk.push(curt->left);
        }
    }
}

2.2 二叉树中序遍历非递归

二叉树中序遍历的非递归考的比较少,后序遍历的非递归考得就更少了。中序遍历的非递归除了使用栈以外,还需要维护一个当前指针root,具体思路如下:

  1. 首先使用root找到左下方第一个没有左子树的结点并且入栈,并把沿途结点也全部入栈;
  2. 访问当前栈顶元素,并把对当前结点的右子树进行步骤 1);
  3. 如果栈空结束结束该过程;
  4. 因为刚开始进行循环与最终结束循环时,栈都为空,为了使用开始状态时,能够进入循环,需要将循环判断改为(!stk.empty() || root != NULL),使用root != NULL进入循环;
void inorder(TreeNode *root) {
    if (root == NULL) {
        return;
    }
    stack<TreeNode*> stk;
    while (!stk.empty() || root != NULL) {
        while (root != NULL) {
            stk.push(root);
            root = root->left;
        }
        root = stk.top();
        cout<<root->val<<" ";
        stk.pop();
        root = root->right;
    }
}

2.3 二叉树的层序遍历

使用队列遍历,注意应该先将右子树放入,再将左子树放入,这样才能每层从左向右访问。

void levelorder(TreeNode *root) {
    if (root == NULL) {
        return NULL;
    } 
    queue<TreeNode*> que;
    que.push(root);
    while (!que.empty()) {
        TreeNode *node = que.front();
        que.pop();
        cout<<node->val<<" ";
        if (node->left != NULL) {
            que.push(node->left);
        }
        if (node->right != NULL) {
            que.push(node->right);
        }
    }
}

2.4 图的层序遍历

说到二叉树层序遍历,那自然也不得不讨论一下逻辑完全相同的图的BFS,图的大部分问题都需要遍历来解决,而且优先也考虑使用BFS而非DFS,因为BFS一般找到访问过的点的情况要比DFS少。

void searchNode(vector<UndirectedGraphNode*>& graph, map<UndirectedGraphNode*, int>& values, UndirectedGraphNode* node, int target) {
    if (node == NULL) {
        return ;
    } 
    queue<UndirectedGraphNode*> que;
    que.push(node);
    unordered_set<UndirectedGraphNode*> hash;
    hash.insert(node);
    while(!que.empty()) {
        UndirectedGraphNode *cur = que.front();
        que.pop();
        cout<<cur->val; 
        for (int i = 0; i < cur->neighbors.size(); i++) {
            if (hash.find(cur->neighbors[i]) == hash.end()) {
                hash.insert(cur);
                que.push(cur->neighbors[i]);
            }
       }
    }
}

从上面的代码可以看出,图的BFS遍历与二叉树的遍历过程相比:

  1. 需要维护一个hash表来判断该邻居是否遍历过,只有没有遍历过的邻居才会加入队列进行之后的访问。
  2. 因为二叉树只有两个孩子,但是图的每个结点的邻居个数不确定,因此需要循环判断是否要将图的邻居是否入队。

3 本章遇到的值得回味题目

Lowest Common Ancestor
Binary Tree Maximum Path Sum
Validate Binary Search Tree
本题技巧:

  1. 为了简化代码,空节点可以初始化为最大值,与最小值 来方便后续的比较;
  2. 本题给出的测试样本中有些就是INT_MAX或者INT_MIN,因此我们空节点初始化时应该初始为LONG_MAX与LONG_MIN;
posted @ 2017-07-12 10:19  fariver  阅读(314)  评论(0编辑  收藏  举报