LeetCodeHot100|图、994.腐烂的橘子、207.课程表、208.实现Trie前缀树

一种由定点和边组成的非线性数据结构,从链表扩展而来,用于表示复杂的网络关系

1从链表树再到图

图是从链表扩展而来的数据结构。相较于线性关系的链表和分治关系数,网络关系的图的自由度更高,因而会更加的复杂。

![[PixPin_2026-04-27_17-27-35.gif]]

2图的基本概念

图的两个关键的概念是什么

3图的常见类型

图的常见类型有哪些,有哪六种类型

4 图的常见术语

领接、路径、和度是描述图结构的三个核心的术语。依次

![[Pasted image 20260427173817.png]]这里图的有向图和无向图是比较重要的地方,对于无相图,领接就是与顶点相邻的的所有顶点。度就是这个定点拥有的边的个数。比如当前4的领接以及顶点如下图所示:

![[Pasted image 20260427175551.png]]

对于无相图,里面的度是有说法的,分为入度以及出度,下图顶点5的信息就是没有领接顶点同时入度为2,出度为0![[Pasted image 20260427175704.png]]当前1的领接顶点就为{2,3,5},而入度为0,出度为3.

5 图的表示:领接矩阵

领接矩阵使用n×n大小的矩阵来表示图。元素为1表示两顶点之间存在变,为0表示不存在。对于无向图来说。

这里有张图来方便理解这个问题,对于相连的两个元素,比如第一个顶点链接着第二个顶点,那么就表示为第一行第二列的这个数。

还有两种情况也需要注意:分有向图以及无向图,带权边和不带权边都是不相同的。

![[Pasted image 20260429103827.png]]

6.图的表示:领接表

领接表使用n个链表来表示图,链表的节点存储该顶点的所有领接顶点。相比于领接矩阵更省空间,但是查找效率低。

![[Pasted image 20260429102318.png]]

图的基础操作

  • 分别写出领接矩阵和领接表在无向图中添加一条边的实现思路,并说明各自的时间复杂度
  • 对比删除顶点操作:1.在领接矩阵中时间复杂度是多少?为什么? 2.在领接表中时间复杂度又是多少?主要耗时在哪
  • 现在有一个顶点很多,但是边很少的稀疏图,应选领接矩阵还是领接表?从空间占用、遍历领接点效率、增删边际效率三个方面说明。

Q1

首先看领接矩阵在无向图中添加一条边,在看领接表如何在无向图中添加一条边,然后会再给出时间复杂度。

200.岛屿数量

给你一个二维网格1陆地以及0水组成的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和竖直方向上相邻的陆地连接形成。可以假设该网格的四条边都被水包围。

题目解析

题目是求岛屿的数量,有岛屿可以分为这几种情况:1、也就是每一个数的上下和左右都没有它就岛屿 2.有一个方向有其他方向没有...

需要解决的问题:

  1. 怎么判断连续

将二维网格看成一个无向图,竖直或者水平相邻的1之间有边相连。如果需要求岛屿的数量,可以扫码整个二维网格。如果一个位置为1,就以其为起始节点开始深度优先搜索

cpp 复制代码
  

class Solution {

public:

    // 主函数:计算岛屿数量

    int numIslands(vector<vector<char>>& grid) {

        // 边界判断:如果网格为空,直接返回0个岛屿

        if (grid.empty() || grid[0].empty()) {

            return 0;

        }

  

        // 获取网格的 总行数 和 总列数

        int row = grid.size();

        int col = grid[0].size();

        // 记录最终的岛屿数量,初始为0

        int res = 0;

  

        // 1. 遍历整个二维网格的每一个格子

        for (int i = 0; i < row; i++) {

            for (int j = 0; j < col; j++) {

                // 2. 如果当前格子是陆地 '1'

                if (grid[i][j] == '1') {

                    // 发现一座新岛屿,数量 +1

                    res++;

                    // 3. 调用DFS:把这座岛屿的所有陆地都变成水 '0'(沉岛)

                    dfs(grid, i, j);

                }

            }

        }

  

        // 遍历完成,返回岛屿总数

        return res;

    }

  

private:

    // DFS 深度优先搜索:淹没当前岛屿,将相连的陆地全部置为0

    // grid:二维网格(引用传递,直接修改原数据)

    // r:当前所在行

    // c:当前所在列

    void dfs(vector<vector<char>>& grid, int r, int c) {

        // 获取网格总行数、总列数

        int nr = grid.size();

        int nc = grid[0].size();

  

        // 核心:把当前陆地标记为水,避免重复访问

        grid[r][c] = '0';

  

        // 【向上】搜索:上面不越界 + 是陆地 → 递归DFS

        if (r - 1 >= 0 && grid[r-1][c] == '1') {

            dfs(grid, r - 1, c);

        }

        // 【向下】搜索:下面不越界 + 是陆地 → 递归DFS

        if (r + 1 < nr && grid[r+1][c] == '1') {

            dfs(grid, r + 1, c);

        }

        // 【向左】搜索:左边不越界 + 是陆地 → 递归DFS

        if (c - 1 >= 0 && grid[r][c-1] == '1') {

            dfs(grid, r, c - 1);

        }

        // 【向右】搜索:右边不越界 + 是陆地 → 递归DFS

        if (c + 1 < nc && grid[r][c+1] == '1') {

            dfs(grid, r, c + 1);

        }

    }

};
关键问题
  1. dfs是如何实现的。
  2. 是如何识别多个岛屿的
q1

在这个题目中只有一个作用:就是报道一个陆地'1',一直往深处走,把所有连在一起的陆地全部变成水'0',就像你站在一个小岛上吧整个岛全部淹掉。

cpp 复制代码
void dfs(vector<vector<char>&grid, int r, int c ) { 
	// 1.获取网格的大小行以及列
	int nr = grid.size();
	int nc = grid[0].size

	// 2.把当前陆地变成水
	grid[r][c] = '0';
	
	// 3.上下左右四个方向递归搜索
	if(r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r-1,c); // 上
	if(r+1 < nr && grid[r +1][c] == '1')dfs(grid, r+1,c);
	
	
}

994.腐烂的橘子

给定的 m * n的网格中,每个单元格有三个值之一:

值0代表空单元格;

值1代表新鲜橘子

值2代表腐烂的橘子

每分钟,腐烂的橘子周围4个方向上相邻的新鲜橘子都会腐烂。

返回最小分钟数:直接单元格没有新鲜橘子为止所需比经过的最小分钟数。如果不可能,返回01

问题
  1. 题目需要求最小分钟数,从一个感染到其他怎么用代码表示?

优先搜索

cpp 复制代码
while (fresh && !q.empty()) {
复制代码
        ans++; // 经过一分钟
        vector<pair<int, int>> nxt;
        for (auto& [x, y] : q) { // 已经腐烂的橘子
            for (auto d : DIRECTIONS) { // 四方向
                int i = x + d[0], j = y + d[1];
                if (0 <= i && i < m && 0 <= j && j < n && grid[i][j] == 1) { // 新鲜橘子
                    fresh--;
                    grid[i][j] = 2; // 变成腐烂橘子
                    nxt.emplace_back(i, j);
                }
            }
        }
        q = move(nxt);
    }
复制代码
  1. 周围四个相邻的怎么表示?
  2. 怎么使用BFS以及层序遍历
  3. 什么是广度优先搜索,具体的算法如何实现 从起点出发,每次的尝试访问同一层的节点,如果同一层访问完成再继续访问下一层,最后广度优先搜索找到的路径就是从起点开始的最短合法路径

207.课程表

你这个学期必须选修numCourses门课程,记为0numCourses - 1在选修某些课程之前需要一些先修课程。先修课程按数组prerequisites给出,其中 prerequisites[i] = [ai, bi],表示如果要学习课程 ai必须 先学习课程 bi

请你判断是否可能完成所有课程的学习?如果可以返回true;否则返回false。

思路分析
  1. 题目要求是判断是否可能完成所有课程的学习,就是需要一些先修课程,这里的规则是什么?
  2. 题目中给的参数各个之间关系是为什么?
  3. 这里的可能和不可能指的是什么?
  4. 怎么判断有像以及无向,如何判断是否是一个拓扑图?
  5. 整个算法的流程是什么样的?
代码
cpp 复制代码
class Solution {

public:

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {

        vector<vector<int>> g(numCourses); // ?语法

        for(auto& p : prerequisites) {

            g[p[1]].push_back(p[0]);

        }

  

        vector<int> colors(numCourses);

        // 返回true表示找到了环

        auto dfs = [&](this auto&& dfs, int x) ->bool {

            colors[x] = 1; // x 正在访问中

            for (int y : g[x]) {

                if(colors[y] == 1 || colors[y] == 0 && dfs(y)) {

                    return true; // 找到了环

  

                }

            }

            colors[x] = 2; // x完全访问完毕,从x出发无法找到环

            return false;

        };

        for(int i = 0 ;i < numCourses; i++) {

            if(colors[i] ==0 && dfs(i)) {

                return false; // 有环

            }

        }

        return true; // 没有环

    }

};

208.实现Trie前缀树

Trie或者说是前缀树是一种树形数据结构,是用来高效存储和检索字符串数据集中的键。

常用于自动补全和拼写检查。

实现Trie()类:

  • Trie()初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串word.
  • boolean search(String word)如果字符串word在前缀树中,返回true;否则返回false
  • boolean startsWith(String prefix)如果之前已经插入的字符串word的前缀之一为prefix,返回true否则返回false.
    示例:
    示例:

输入

"Trie", "insert", "search", "search", "startsWith", "insert", "search"

\[\], \["apple"\], \["apple"\], \["app"\], \["app"\], \["app"\], \["app"\]

输出

null, null, true, false, true, null, true

解释

Trie trie = new Trie();

trie.insert("apple");

trie.search("apple"); // 返回 True

trie.search("app"); // 返回 False

trie.startsWith("app"); // 返回 True

trie.insert("app");

trie.search("app"); // 返回 True

思路分析
  1. 题目是需要你去实现一个前缀树的
  2. 需要你实现的是三个功能+一个构造
  3. 示例中的案例是在干嘛?

这里的核心思路是需要去实现一个前缀树,这个树就是专门用来存放字符串的树,它的特点是。按字母一层层的存,共享前缀不重复,能想到这一点就很好了,所有它查单词、查前缀特别快。

然后接下来你必须实现3个功能和一个构造:1.需要去写一个Trie的类,里面有这四个东西1️⃣Trie,创建一颗空的前缀树,啥都不用做,初始化就可以

代码解析
cpp 复制代码
class Trie {

private:

    bool isEnd; // 标记当前的节点是否是某个单词的结束位置

    Trie* next[26];

public:

    Trie() {

        isEnd = false;

        memset(next, 0, sizeof(next));

    }

    void insert(string word) {

        Trie* node = this;

        for(char c: word) {

            if(node->next[c - 'a'] == NULL) { // c-'a' 是转为0 ~ 25的索引

                node->next[c-'a' ]= new Trie(); // 如果当前字符路径不存在,新建节点

            }

            node = node->next[c-'a']; // 移动到子节点

        }

        node -> isEnd = true;

    }

    // 查询单词是否存在于字典树中

    bool search(string word) {

        Trie* node = this; //就是从根节点出发开始遍历。

        for(char c : word) {

            node = node->next[c - 'a'];

            if(node == NULL) {

                return false;

            }

        }

        return node ->isEnd;

    }

    bool startsWith(string prefix) {

        Trie* node = this;

        for(char c : prefix) {

            node = node -> next[c - 'a'];

            if(node == NULL) {

                return false;

            }

        }

        return true;

    }

};

  

/**

 * Your Trie object will be instantiated and called as such:

 * Trie* obj = new Trie();

 * obj->insert(word);

 * bool param_2 = obj->search(word);

 * bool param_3 = obj->startsWith(prefix);

 */
遇到的问题
  1. 如何去初始化一个前缀树?
  2. 为什么使用this
  3. 分步骤如何去写插入,查找,前缀匹配。

图总结

刷完图之后需要对整个章节有一个简单的回顾,比如图的一些术语以及基本概念。还有图的便利以及小结。

图常见的算法深度优先和广度有限搜索

相关推荐
许长安1 小时前
gRPC 数据包传输格式解析:从 Protobuf 到 HTTP/2
c++·经验分享·笔记·http·rpc
渡我白衣1 小时前
定时器与时间轮思想
linux·开发语言·前端·c++·人工智能·深度学习·神经网络
luyun0202021 小时前
实用小工具,吾爱出品
开发语言·c++·算法
蜡笔小马1 小时前
06.C++设计模式-装饰模式
c++·设计模式·装饰器模式
宏笋1 小时前
C++11使用chrono获取当前时间戳
c++
Shadow(⊙o⊙)1 小时前
硬核手搓解析!进程-内核分析:命令行参数及环境变量,重构main()
linux·运维·服务器·开发语言·c++·后端·学习
YYYing.1 小时前
【C++项目之高并发内存池 (五)】一些小细节和性能优化及整体测试
c++·性能优化·高并发·内存池·基数树
2301_789015622 小时前
Linux:基础指令(二)
linux·运维·服务器·c语言·开发语言·c++·算法
闻缺陷则喜何志丹2 小时前
【区间合并】P7912 [CSP-J 2021] 小熊的果篮|普及+
c++·算法·洛谷·区间合并