leetcode--二叉树和图

基础知识

二叉树的数据结构

struct TreeNode{
    int val;   //数据域
    TreeNode *left //左右指针
    TreeNode *right
    TreeNode(int x):val(x),left(NULL),right(NULL){}
    //构造函数
}

构造二叉树

构造如下的一棵二叉树:
二叉树

int main(){
    TreeNode a(1);
    TreeNode b(2);
    TreeNode c(3);
    TreeNode d(4);
    TreeNode e(5);
    TreeNode f(6);
    a.left=&b;
    a.right=&c;
    b.left=&d;
    b.right=&e;
    c.right=&f;
}

二叉树的深度遍历

前序遍历访问
traversal(node->left)
中序遍历访问
traversal(node->right)
后序遍历访问

前中后指的是访问根节点的顺序,前序:先根再左再右

二叉树的层次遍历

也就是广度优先搜索,用队列一层一层的访问节点,访问一个节点压入该节点的孩子节点,队列不空,持续该过程。
这里写图片描述

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

leetcode题目

113 Path Sum

题意:

给定一个二叉树与整数sum,找出所有从根节点到叶节点的路径,要求这些路径上节点值的和等于sum。

解题思路:

用深度优先搜索路径。
1 先序遍历时将节点值存储到path栈中,path_value累加节点值
2 当遍历到叶节点时判断path_value是不是等于sum,if等于push进result中。
3 在后序遍历时,将节点从path栈中弹出,path_value减去节点值

代码:

class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        vector<vector<int>> result;
        vector<int> path;
        int 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=path_value+node->val;
        path.push_back(node->val);
        if(!node->left&&!node->right&&path_value==sum){
            result.push_back(path);
        }
        preorder(node->left,path_value,sum,path,result);
        preorder(node->right,path_value,sum,path,result);
        //后序遍历内容:
        path.pop_back();
        path_value=path_value-node->val;
    }
};

236 Lowest Common Ancestor of a Binary Tree

题意:

给出两个节点,求出他们最近的公共祖先。

解题思路:

1 用深度优先搜索找点,找到点后保存路径。
2 同时遍历两个路径,找最后一个相同点。

代码:

void preorder(TreeNode* node,
              TreeNode *search,
              std::vector<TreeNode*> &path,
              std::vector<TreeNode*> &result,
              int &finish){
    if (!node || finish){
        return;      //结束技巧,finish用于标记已找到节点
    }
    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();//后序遍历时根节点要做退出操作,就是左右节点都不是要找的点,根节点弹出
}

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        std::vector<TreeNode*> path;
        std::vector<TreeNode*> node_p_path;
        std::vector<TreeNode*> node_q_path;
        int finish = 0;
        preorder(root, p, path, node_p_path, finish);//找p点的路径并保存于node_p_path
        path.clear();
        finish = 0;
        preorder(root, q, path, node_q_path, finish);//找q点的路径并保存于node_q_path
        int path_len = 0;
        if (node_p_path.size() < node_q_path.size()){
            path_len = node_p_path.size();
        }
        else{
            path_len = node_q_path.size();
        }
        TreeNode *result = 0;
        //找两个路径中最后一个相同点
        for (int i = 0; i < path_len; i++){
            if (node_p_path[i] == node_q_path[i]){
                result = node_p_path[i];//少写一点代码
            }
        }
        return result;
    }
};

114. Flatten Binary Tree to Linked List

题意:

不用大的额外空间,将二叉树转换为链表,left为null,right为链表的next指针。链表的顺序是树的先序遍历。

解题思路:

如果不考虑大的额外空间,最简单的做法是用一个vector存先序遍历的结果,然后遍历这个vector。

代码:

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

解题思路2:

思考过程是:独立开树的一部分,思考前中后遍历时候应该做什么。考虑输入和输出:
* 先序遍历:第一次访问根节点时,我们要保留左右节点,让right指针指向左节点。右指针指向NULL。
* 中序遍历:第二次访问根节点,这时左子树完成了链表变形,要将左子树的最后节点指向,右子树的开始节点。所以要输出一个末尾节点,可以用&引用去维护。
* 后续遍历:维护最末的节点。

代码:

class Solution {
public:
    void flatten(TreeNode* root) {
        TreeNode *last=NULL;
        preorder(root,last);
    }
private:
    void preorder(TreeNode* node,TreeNode* &last){
        //*& TreeNode*的意思是传入TreeNode的指针变量,&是可以在函数中改变这个变量
        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(left_last){
                left_last->right=right;//让左子树的末尾节点指向右子树的开始节点。放在后序遍历执行可以省去判断是否有右子树的语句。
            }
            last=right_last;//维护右子树的末尾节点
        }
    }
};

199.Binary Tree Right Side View

题意:

从右边观察二叉树,输出每一层最右边的节点。

解题思路:

层次遍历二叉树,将节点和层数绑定微pair,压入队列时,将节点和层数同时压入,记录每一层的最后一个节点。

代码:

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> view; //存储每层最后的节点。
        queue<pair<TreeNode*,int>> Q;
        //广度优先搜索,用一个pair存储节点和它对应的层数。
        if(root){
            Q.push(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(make_pair(node->left,depth+1));  //压入左节点,标记为下一层
            }
            if(node->right){
                Q.push(make_pair(node->right,depth+1)); //压入右节点,标记为下一层
            }
        }
        return view;
    }
};

图

图的表示(邻接矩阵):

这里写图片描述

代码:
int main(){
    const int MAX_N = 5;
    int Graph[MAX_N][MAX_N] = {0};
    Graph[0][2] = 1;
    Graph[0][4] = 1;
    Graph[1][0] = 1;
    Graph[1][2] = 1;
    Graph[2][3] = 1;
    Graph[3][4] = 1;
    Graph[4][3] = 1;
    printf("Graph:\n");
    for (int i = 0; i < MAX_N; i++){
        for (int j = 0; j < MAX_N; j++){
            printf("%d ", Graph[i][j]);
        }
        printf("\n");
    }
    return 0;
}

表示稠密图作用比较大,但是表示稀疏图一般用邻接表。

图的表示(邻接表):

这里写图片描述

代码:
#include <stdio.h>
#include <vector>

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

int main(){ 
    const int MAX_N = 5;
    GraphNode *Graph[MAX_N];

    for (int i = 0; i < MAX_N; i++){
        Graph[i] = new GraphNode(i);
    }

    Graph[0]->neighbors.push_back(Graph[2]);
    Graph[0]->neighbors.push_back(Graph[4]);
    Graph[1]->neighbors.push_back(Graph[0]);
    Graph[1]->neighbors.push_back(Graph[2]);
    Graph[2]->neighbors.push_back(Graph[3]);
    Graph[3]->neighbors.push_back(Graph[4]);
    Graph[4]->neighbors.push_back(Graph[3]);

    printf("Graph:\n");
    for (int i = 0; i < MAX_N; i++){
        printf("Label(%d) : ", i);
        for (int j = 0; j < Graph[i]->neighbors.size(); j++){
            printf("%d ", Graph[i]->neighbors[j]->label);
        }
        printf("\n");
    }
    for (int i = 0; i < MAX_N; i++){
        delete Graph[i];
    }
    return 0;
}

深度优先搜索

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);
        }
    }
} 

广度优先搜索

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

void BFS_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;
            }
        }
    }
}

leetcode题目

207. Course Schedule

题意:给出课程之间的依赖关系,求是否可以将所有的课程全部完成。其实就是求有向图是否有环。

解题思路一:

  • 用深度优先搜索,如果递归搜索某一条路径时发现路径中有重复的节点,则有环,不能完成。
  • 这里的visit的数组要设计三种状态:-1是未被搜索,0是正在被搜索的路径上,1是已经完成搜索的节点。
  • 像递归二叉树思考方式一样,思考先序中序后序遍历要进行什么操作:
    • 先序遍历:将该节点标记状态visit为0
    • 中序遍历:如果访问过程中出现访问到visit为0的点则返回false
    • 后续遍历:访问完成将该节点标记状态visit为-1

代码:

struct GraphNode{
    int label;
    std::vector<GraphNode *> neighbors;
    GraphNode(int x) : label(x) {};
};
bool DFS_graph(GraphNode *node, std::vector<int> &visit){
    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,
        std::vector<std::pair<int, 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);
        }
        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;
    }
};

解题思路二:

用广度优先搜索,用入度的概念,每次只将入度为0的点压入队列,它指向的所有节点的入度都-1,-1后入度为0的点可以压入队列,如果能遍历整个图,则可以完成,不能遍历则不能完成。

代码:

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

class Solution {
public:
    bool canFinish(int numCourses,
        std::vector<std::pair<int, 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);
            degree[prerequisites[i].first]++;
        }       
        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++){
                degree[node->neighbors[i]->label]--;
                if (degree[node->neighbors[i]->label] == 0){
                    Q.push(node->neighbors[i]);
                }
            }
        }       
        for (int i = 0; i < graph.size(); i++){
            delete graph[i];
        }       
        for (int i = 0; i < degree.size(); i++){
            if (degree[i]){
                return false;
            }
        }
        return true;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值