C++

数据结构与算法

二叉树、图

Posted by Bob on September 15, 2019

以下题目均来自leetcode、poj

树的定义

树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。

树具有的特点有:

(1)每个结点有零个或多个子结点

(2)没有父节点的结点称为根节点

(3)每一个非根结点有且只有一个父节点

(4)除了根结点外,每个子结点可以分为多个不相交的子树。

二叉树的定义

二叉树是每个结点最多有两个子树的树结构。它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;或者左、右子树皆为空。

性质1:二叉树第i层上的结点数目最多为2的i-1次方(i>=1)

性质2:深度为k的二叉树至多有2的k次方-1个结点(k>=1)

性质3:包含n个结点的二叉树的高度至少为:$ log2^{n}+1 $

性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1

深度遍历

前序遍历:先范文父节点,再遍历左子树和右子树。

中序遍历:首先遍历左子树,然后再访问父节点,最后遍历右子树。

后序遍历:首先遍历左子树,然后遍历右子树,最后访问父节点。

image


struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x):val(x),left(NULL),right(NULL){}
};

void traversal(TreeNode *node){
    if(!node){
        return;
    }
    //此时访问node称为前序遍历,(完成一次递归前)

    traversal(node->left);
    //此时访问node称为中序遍历 (完成一次递归后)

    traversal(node->right);
    //此时访问node称为后序遍历 (完成二次递归后)
}

//前 a1,b2,d3,e4,c5,f6

//中 d3,b2,e4,a1,c5,f6

//后 d3,e4,b2,f6,c5,a1


路径总和 II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例: 给定如下二叉树,以及目标和 sum = 22,

image

返回:

[ [5,4,11,2], [5,8,4,5] ]


/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        vector<vector<int>> result;
        vector<int> path;
        path_value = 0;
        preOrder(root,path_value,sum,path,result);
        return result;
    }
private:
    void preOrder(TreeNode *node,
        int &path_value,
        int sum,
        vector<int> &path,
        vector<vector<int>>& result){
        if(!node){
            return;
        }
        //前序放入栈

        path_value +=node->val;
        path.push_back(node->val);
        //是叶节点(没有左右子节点)

        if(sum == path_value&&
         !node->left&&
         !node->right){
            reuslt.push_back(path);
        }
        preOrder(node->left,path_value,sum,path,result);
        preOrder(node->right,path_value,sum,path,result);
        //遍历完,扔掉

        path_value -=node->val;
        path.pop_back();
    }

};


最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

image

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出: 3


/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> path;
        vector<TreeNode*> nodePPath;
        vector<TreeNode*> nodeQPath;
        int finish = 0;
        preOrder(root,p,path,nodePPath,finish);
        path.clear();
        finish = 0;
        preOrder(root,q,path,nodeQPath,finish);
        int path_len = 0;
        pathLen = nodePPath.size() < nodeQPath.size() ? nodePPath.size() : nodeQPath.size();
        TreeNode* result = 0;
        for(int i = 0;i < path_len;i++){
            if(nodePPath[i] == nodeQPath[i]){
                result = nodePPath[i];
            }
        }
        return result;
    }

private:
    void preOrder(TreeNode* node,
        TreeNode* search,
        vector<TreeNode*>& path,
        vector<TreeNode*>& result,
        int &finish
        ){
        if(!node || finish){
            return;
        }
        path.push_back(node);
        if(node == search){
            finish = 1;
            result = path;
        }
        preOrder(node->left,search,path,result,finish);
        preOrder(node->right,search,path,result,finish);
        path.pop_back();
    }
};


二叉树展开为链表

给定一个二叉树,原地将它展开为链表。

例如,给定二叉树

image

将其展开为:

1-2-3-4-5-6


/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

//借助

class Solution {
public:
    void flatten(TreeNode* root) {
        std::vector<TreeNode*> nodeVec;
        preOrde(root,nodeVec);
        for(int i = 1;i < nodeVec.size();i++){
            nodeVec[i-1].left = NULL;
            nodeVec[i-1].right = nodeVec[i];
        }
    }
private:
    void preOrder(TreeNode* node,std::vector<TreeNode*> &nodeVec){
        if(!node){
            return;
        }
        nodeVec.push_back(node);
        preOrder(node->left,nodeVec);
        preOrder(node->right,nodeVec);
    }
};

//就地

class Solution {
public:
    void flatten(TreeNode* root) {
        TreeNode *last = NULL;
        preOrder(root,last);
    }

private:
    void preOrder(TreeNode* node,TreeNode &last){
        if(!node){
            return;
        }
        if(!node->left && !node->right){
            last = node;
            return;
        }
        TreeNode *left = node->left;
        TreeNode *right = node->right;
        TreeNode *left_last = NULL;
        TreeNode *right_last = NULL;
        if(left){
            //先处理左子树为链表

            preOrder(left,left_last);
            node->left = NULL;
            node->right = left;
            last = left_last;//左子树最后一个

        }
        if(right){
            preOrder(right,right_last);
            if(leftLast){
                //左子树最后一个节点接上右子树

                left_last->right = right;
            }
            last = right_last;
        }
    }
};


宽度遍历

image

层次遍历: a1,b2,c5,d3,e4,f6


void BFS_print(TreeNode* root){
    std::queue<TreeNode*> q;
    q.push_back(root);
    while(!q.empty()){
        TreeNode* node = q.front();
        q.pop();
        printf("%d",node->val);
        if(node->left){
            q.push(node->left);
        }
        if(node->right){
            q.push(node->right);
        }
    }
}

二叉树的右视图

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例:

输入: [1,2,3,null,5,null,4] 输出: [1, 3, 4] 解释:

image


/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

//层次遍历二叉树,每层中的最后一个节点。

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        std::vector<int> view;
        std::queue<std::pair<TreeNode*,int>> q;
        if(root){
            q.push(std::make_pair(root,0));
        }
        while(!q.empty()){
            TreeNode* node = q.front().first;
            int depth = q.front().second;
            q.pop();
            if(view.size() == depth){
                //第一次放入

                view.push_back(node->val);
            }else{
                //存在则更新

                view[depth] = node->val;
            }
            if(node->left){
                q.push(std::make_pair(node->left,depth+1));
            }
            if(node->right){
                q.push(std::make_pair(node->right,depth+1));
            }
        }
    }
};



无向图和有向图、带权图和不带权图

image

一般用邻接矩阵、邻接表来表示。

深度搜索


struct GraphNode{
    int label;
    std::vector<GraphNode*> neighbors;
    GraphNode(int x):label(x){};
};

void DFS_graph(GraphNode* node,int visit[]){
    visit[node->label] = 1;
    printf("%d",node->label);
    for(int i = 0;i < node->neighbors.size();i++){
        if(visit[node->neighbors[i]->label] == 0){
            DFS_graph(node->neighbors[i],visit);
        }
    }
}

宽度搜索


void DFS_graph(GraphNode* node,int visit[]){
    std::queue<GraphNode*> q;
    q.push(node);
    visit[node->label] = 1;
    while(!q.empty()){
        GraphNode *node = q.front();
        q.pop();
        printf("%d",node->label);
        for(int i = 0;i < node->neighbors.size();i++){
            if(visit[node->neighbors[i]->label] == 0){
                q.push(node->neighbors[i]);
                visit[node->neighbors[i]->label] = 1;
            }
        }
    }
}

课程表

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 输出: true 解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。 示例 2:

输入: 2, [[1,0],[0,1]] 输出: false 解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。 说明:

输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。 你可以假定输入的先决条件中没有重复的边。 提示:

这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。 通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。 拓扑排序也可以通过 BFS 完成。


//判断有向图是否有环

struct GraphNode{
    int label;
    std::vector<GraphNode*> neighbors;
    GraphNode(int x):label(x){};
};


//深度

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
       std::vector<GraphNode*> graph;
       std::vector<int> visit;
       for(int i = 0;i < numCourses;i++){
           graph.push_back(new GraphNode(i));
           visit.push_back(-1);
       }
       for(int i = 0;i < prerequisites.size();i++){
           GraphNode *begin = graph[prerequisites[i].second];
           GraphNode *end = graph[prerequisites[i].first];
           begin->neighbors.push_back(end);//课程2指向课程1

       }
       for(int i = 0;i < graph.size();i++){
           //是否有环

           if(visit[i] == -1 && !DFS_graph(graph[i],visit)){
               return false;
           }
       }
       for(int i = 0;i < numCourses;i++){
          delete graph[i];
       }
       return true;
    }
private:
    bool DFS_graph(GraphNode* node,std::vector<int> &visit){
        //-1没有访问过,0正在访问,1代表已完成访问

        visit[node->label] = 0;
        for(int i = 0;i < node->neighbors.size();i++){
            if(visit[node->neighbors[i]->label] == -1){
                //有环

                if(DFS_graph(node->neighbors[i],visit) == 0){
                    return false;
                }
            }
            else if(visit[node->neighbors[i]->label] == 0){
                //指向自己

                return false;
            }
        }
        visit[node->label] = 1;
        return true;
    }
};

//宽度

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        std::vector<GraphNode*> graph;
        std::vector<int> degree;
        for(int i = 0;i < numCourses;i++){
            degree.push_back(0);
            graph.push_back(new GraphNode(i));
        }
        for(int i = 0;i < prerequisites.size();i++){
           GraphNode *begin = graph[prerequisites[i].second];
           GraphNode *end = graph[prerequisites[i].first];
           begin->neighbors.push_back(end);//<课程1,课程2>

           degree[prerequisites[i].first]++;//课程1入度

        }
        std::queue<GraphNode*> q;
        for(int i = 0;i < numCourses;i++){
            if(degree[i] == 0){
                q.push(graph[i]);
            }
        }
        while(!q.empty()){
            //完成一次搜索从队列中取出

            GraphNode* node = q.front();
            q.pop();
            for(int i = 0;i < node->neighbors.size();i++){
                //指向的所有节点入度减1
                
                degree[node->neighbors[i].label]--;
                //入度为0的添加到队列

                if(degree[node->neighbors[i]->label] == 0){
                    q.push(node->neighbors[i]);
                }
            }
        }
        for(int i = 0;i < numCourses;i++){
          delete graph[i];
        }
        //所有入度为0,则图无环

        for(int i = 0;i < degree.size();i++){
            if(degree[i]){
                return false;
            }
        }
        return true;
    }
};