數據結構與算法:圖論——深度優先搜索dfs

深度優先搜索dfs

提到深度優先搜索(dfs),就不得不說和廣度優先搜索(bfs)有什么區別

根據搜索方式的不同,可以將圖的遍歷分為「深度優先搜索」和「廣度優先搜索」。

  • 深度優先搜索:從某一頂點出發,沿著?條路徑?直搜索下去,在?法搜索時,回退到剛剛訪問過的節點。
  • 廣度優先搜索:從某個頂點出發,?次性訪問所有未被訪問的鄰接點,再依次從這些已訪問過的鄰接點出發,?層?層地訪問。

基礎與模板:

不斷深入,更新標記

下圖來自:AlgoNote/docs/06_graph/06_03_graph_dfs.md at main · itcharge/AlgoNote · GitHub

image-20250624101309420

兩種兩種深度優先搜索的實現方式

  • 遞歸實現
  • 基于堆棧實現

首先介紹遞歸實現。與算法本身非常的和諧,和二叉樹的遍歷的兩種方式很像,實現方法也類似,只不過圖的儲存方式跟二叉樹不同而已。

image-20250624110932548

基于堆棧實現:深度優先搜索是使用stack比較合適

image-20250624114822458

題目1、797. 所有可能的路徑

給你一個有 n 個節點的 有向無環圖(DAG),請你找出從節點 0 到節點 n-1 的所有路徑并輸出(不要求按特定順序

graph[i] 是一個從節點 i 可以訪問的所有節點的列表(即從節點 i 到節點 graph[i][j]存在一條有向邊)。

示例 1:

img

輸入:graph = [[1,2],[3],[3],[]]
輸出:[[0,1,3],[0,2,3]]
解釋:有兩條路徑 0 -> 1 -> 3 和 0 -> 2 -> 3

示例 2:

img

輸入:graph = [[4,3,1],[3,2,4],[3],[4],[]]
輸出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]

提示:

  • n == graph.length
  • 2 <= n <= 15
  • 0 <= graph[i][j] < n
  • graph[i][j] != i(即不存在自環)
  • graph[i] 中的所有元素 互不相同
  • 保證輸入為 有向無環圖(DAG)

這個題目使用深度優先搜索,用stack實現感覺代碼量比較多一些。這里使用遞歸實現比較好理解,而且代碼量比較少,需要考慮的細節也比較多。需要改變DFS的遞歸條件

找出從節點 0 到節點 n-1 的所有路徑并輸出,所以遞歸的時候碰見n-1就把這一組加入結果

image-20250624164453567

class Solution {
public:vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {vector<vector<int>> res;vector<int> singleVec{0};   // 這里開頭0先加到中間vec里面int num = graph.size();function<void(vector<int>&)> dfs = [&](vector<int>& vec){if(singleVec.back() == num-1){	// 中間vec最后一個值判斷條件res.push_back(singleVec);return;}for(auto a:vec){singleVec.push_back(a);dfs(graph[a]);singleVec.pop_back();// 這里singleVec在外面有push就需要pop}};dfs(graph[0]);return res;}
};

題目2、200. 島嶼數量

給你一個由 '1'(陸地)和 '0'(水)組成的的二維網格,請你計算網格中島嶼的數量。

島嶼總是被水包圍,并且每座島嶼只能由水平方向和/或豎直方向上相鄰的陸地連接形成。

此外,你可以假設該網格的四條邊均被水包圍。

示例 1:

輸入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
輸出:1

示例 2:

輸入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
輸出:3

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值為 '0''1'

經典題目:往上下左右四個方向DFS,先遍歷,碰到1就開開始DFS把周圍的1都變成0

image-20250624174938394

	int dir[4][2]={0,1,1,0,0,-1,-1,0}; // 上下左右void makezero(vector<vector<char>>& grid,int x,int y){grid[x][y] = '0';    	// 碰見1以后上下左右都置零for(int i = 0;i<4;i++){int nx = x+dir[i][0];int ny = y+dir[i][1];if(nx<0||ny<0||nx>=grid.size()||ny>=grid[0].size()) continue;if(grid[nx][ny]=='1'){makezero(grid,nx,ny);}}}int numIslands(vector<vector<char>>& grid) {int row = grid.size();int col = grid[0].size();int res = 0;for(int i = 0 ;i<row;i++){for(int j =0;j<col;j++ ){if(grid[i][j] == '1' ){ //碰見1之后,周圍置零makezero(grid,i,j);res++;}}}return res;}

題目3、695. 島嶼的最大面積

給你一個大小為 m x n 的二進制矩陣 grid

島嶼 是由一些相鄰的 1 (代表土地) 構成的組合,這里的「相鄰」要求兩個 1 必須在 水平或者豎直的四個方向上 相鄰。你可以假設 grid 的四個邊緣都被 0(代表水)包圍著。

島嶼的面積是島上值為 1 的單元格的數目。

計算并返回 grid 中最大的島嶼面積。如果沒有島嶼,則返回面積為 0

示例 1:

img
輸入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
輸出:6
解釋:答案不應該是 11 ,因為島嶼只能包含水平或垂直這四個方向上的 1 。

示例 2:

輸入:grid = [[0,0,0,0,0,0,0,0]]
輸出:0

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j]01

這個題目只是在外面加一個參數count,遍歷數組,得到count的最大值

image-20250624181738358

題目4、133. 克隆圖

給你無向 連通 圖中一個節點的引用,請你返回該圖的 深拷貝(克隆)。

圖中的每個節點都包含它的值 valint) 和其鄰居的列表(list[Node])。

class Node {public int val;public List<Node> neighbors;
}

測試用例格式:

簡單起見,每個節點的值都和它的索引相同。例如,第一個節點值為 1(val = 1),第二個節點值為 2(val = 2),以此類推。該圖在測試用例中使用鄰接列表表示。

鄰接列表 是用于表示有限圖的無序列表的集合。每個列表都描述了圖中節點的鄰居集。

給定節點將始終是圖中的第一個節點(值為 1)。你必須將 給定節點的拷貝 作為對克隆圖的引用返回。

image-20250624201046963
class Solution {
public:vector<int> flags  = vector<int>(101,1);unordered_map<Node*,Node*>mp;
}
這里的flags  為什么不能 vector<int> flags(101,1);

在 C++ 類的定義里,成員變量的初始化要遵循特定的語法規則。當你在類中聲明非靜態成員變量時,像下面這樣直接初始化是不被允許的:

vector<int> flags(101,1); // 錯誤的類內初始化方式

這是因為這種圓括號初始化的形式,會被編譯器理解成函數聲明。也就是說,編譯器會把它誤認為是一個名為flags,返回類型為vector,并且帶有兩個參數(一個int類型和一個int類型)的函數。

而使用等號加花括號或者直接用花括號的初始化方式,就不會產生歧義,所以是合法的類內初始化形式:

vector<int> flags = vector<int>(101,1); // 正確,使用等號初始化
vector<int> flags{101,1}; // 正確,使用花括號列表初始化(但這種方式是創建包含兩個元素101和1的vector)
vector<int> flags = {101,1}; // 正確,等價于上面的花括號形式

在你的代碼中:

vector<int> flags  = vector<int>(101,1);

這是正確的類內初始化寫法。它借助拷貝初始化,先構造出一個臨時的vector對象,然后再把這個臨時對象拷貝給成員變量flags。

總結一下,類內初始化要避開圓括號語法,防止和函數聲明混淆。建議優先采用花括號初始化或者等號初始化的方式。

這個題目,主要是

  • 首先,dfs時要確定好終止條件,當新節點創立好了之后就不用創立了,返回這個新節點即可
  • 怎么找到創立好的新節點呢,通過哈希表建立起新舊節點的關系,通過舊節點找到新節點
class Node {
public:int val;vector<Node*> neighbors;Node() {val = 0;neighbors = vector<Node*>();}Node(int _val) {val = _val;neighbors = vector<Node*>();}Node(int _val, vector<Node*> _neighbors) {val = _val;neighbors = _neighbors;}
};
class Solution {
public:// 這里為了判斷這個節點有沒有被創建vector<int> flags  = vector<int>(101,1); // 這個建立原始圖節點和新建節點之間映射// 當neighbors中有已經創建的節點之后// 通過這個哈希表就可以找到新建的對應節點unordered_map<Node*,Node*>mp;Node* cloneGraph(Node* node) {if(!node) return nullptr;if(flags[node->val] == 0) return mp[node];// 節點已創建,返回新節點Node* res = new Node(node->val);// 新建節點mp[node] = res;		  // 建立新舊節點哈希表flags[node->val] = 0; // 表示新節點已經創建for(auto a:node->neighbors){res->neighbors.push_back(cloneGraph(a));}					// 新節點neighborsreturn res;}
};

上面是個人寫的代碼,讓deepseek優化之后發現,這個創建標識符flags可以省略,通過哈希表就可以完成這個是否創建的判斷

class Solution {
public:unordered_map<Node*, Node*> visited; // 存儲原節點到克隆節點的映射Node* cloneGraph(Node* node) {if (!node) return nullptr;if (visited.find(node) != visited.end()) return visited[node]; // 已克隆則直接返回Node* cloneNode = new Node(node->val); // 創建新節點visited[node] = cloneNode; // 建立映射關系for (Node* neighbor : node->neighbors) {cloneNode->neighbors.push_back(cloneGraph(neighbor)); // 遞歸克隆鄰居}return cloneNode;}
};

題目5、841. 鑰匙和房間

n 個房間,房間按從 0n - 1 編號。最初,除 0 號房間外的其余所有房間都被鎖住。你的目標是進入所有的房間。然而,你不能在沒有獲得鑰匙的時候進入鎖住的房間。

當你進入一個房間,你可能會在里面找到一套 不同的鑰匙,每把鑰匙上都有對應的房間號,即表示鑰匙可以打開的房間。你可以拿上所有鑰匙去解鎖其他房間。

給你一個數組 rooms 其中 rooms[i] 是你進入 i 號房間可以獲得的鑰匙集合。如果能進入 所有 房間返回 true,否則返回 false

image-20250624204447096


這里就是dfs的模板題。把圖從開頭遍歷,設置flags,表示遍歷到這個節點,,否則返回false

image-20250624213220582

class Solution {
public:bool canVisitAllRooms(vector<vector<int>>& rooms) {vector<int> flags(rooms.size(),1); // 標準位,1表示未遍歷到flags[0] = 0;					   // 0已經遍歷到,0有鑰匙function<void(vector<int>&)>dfs = [&](vector<int>& vec){for(auto a:vec){if(flags[a]){		flags[a] = 0;		// 遍歷到置零dfs(rooms[a]);		// 繼續遍歷到下一個vec}}};dfs(rooms[0]);for(auto b:flags){		// 如果這個flags全部被遍歷到之后,返回tureif(b == 1) return false;}return true;}
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/bicheng/86182.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/86182.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/86182.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數組題解——?合并區間【LeetCode】

56. 合并區間 排序&#xff1a; 將所有區間按起始位置 start 從小到大排序。這樣&#xff0c;重疊的區間會相鄰排列&#xff0c;方便后續合并。 合并&#xff1a; 初始化一個空列表 merged&#xff0c;用于存儲合并后的區間。遍歷排序后的區間列表&#xff1a; 如果 merged 為…

關于高精度和鏈表的詳細講解(從屬于GESP五級)

本章內容 高精度 鏈表 位數再多&#xff0c;只管穩穩進位&#xff0c;終會把答案寫滿。 一、高精度 1. 什么是高精度 ? 定義 “高精度整數”指不受 C 原生整型 (int / long long) 位寬限制&#xff0c;而用數組模擬任意位數的大整數。 ? 必要性 64 位 long long 僅能…

Python自動化框架選型指南:Selenium/Airflow/Celery該選誰?

在Python自動化領域,Selenium、Airflow和Celery是三個高頻出現的工具,但它們的定位和適用場景截然不同。許多開發者在技術選型時容易混淆它們的邊界,導致項目架構臃腫或功能不匹配。本文將通過對比分析,幫你明確不同場景下的最佳選擇。 一、框架定位與核心功能對比 框架核…

50天50個小項目 (Vue3 + Tailwindcss V4) ? | DrinkWater(喝水記錄組件)

&#x1f4c5; 我們繼續 50 個小項目挑戰&#xff01;—— DrinkWater組件 倉庫地址&#xff1a;https://github.com/SunACong/50-vue-projects 項目預覽地址&#xff1a;https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API 和 <script setup> 語法結…

UAVAI-YOLO:無人機航拍圖像的小目標檢測模型

摘要 針對無人機航拍圖像目標檢測效果差的問題&#xff0c;提出改進的UAVAI-YOLO模型。首先&#xff0c;為使模型獲得更加豐富的語義信息&#xff0c;使用改進可變形卷積網絡&#xff08;deformable convolutional networks&#xff0c;DCN&#xff09;替換原骨干&#xff08…

Solidity 入門教程(一):Hello Web3,從一個字符串開始!

學習 Solidity 最好的方式&#xff0c;就是寫出你的第一個合約&#xff01;在本篇文章中&#xff0c;我們將用極簡的代碼&#xff0c;通過 Remix 平臺快速實現并運行一個 “Hello Web3!” 合約&#xff0c;正式邁入智能合約開發的大門。 一、什么是 Solidity&#xff1f; Sol…

串擾與包地

串擾與包地&#xff1a; 串擾與包地一直是業界非常關心的一個問題&#xff0c;圍繞著它們的爭論非常多&#xff0c;那到底是包地好 還是不包地好呢?高速先生嘗試著從理論和實際測試上來給大家做一個分析。 為了驗證它&#xff0c;高速先生做了以下幾種情況&#xff0c;如圖5-…

leetcode hot 100之:二叉樹的最近公共祖先

本來不打算寫的哈哈哈但是發現這一道遞歸我是有思路的&#xff01;&#xff01;自己能想到一些方向&#xff01;我真棒&#xff01;所以記錄一下哈哈哈 我的思路&#xff1a; 1、祖先一定是自身或往上找&#xff0c;所以如何逆著走呢&#xff1f; 2、3種情況&#xff1a; 有…

【VUE】某時間某空間占用情況效果展示,vue2+element ui實現。場景:會議室占用、教室占用等。

某時間某空間占用情況效果展示&#xff0c;vue2element ui實現。場景&#xff1a;會議室占用、教室占用等。 場景說明&#xff1a; 現在需要基于vue2和el-table實現每日會議室個時間點占用情況。 已知數據&#xff1a; 1、會議室數據&#xff08;名稱&#xff0c;id&#xff…

Git更換源方式記錄

本文首發地址&#xff1a;https://www.dawnsite.cn/archives/198.html 該方式前提是本地項目已關聯遠程倉庫&#xff0c;由于業務變更git地址改變 1. 移除本地已有遠程倉庫 git remote remove origin2. 添加新的遠程倉庫源 git remote add origin "clone地址"3.一步…

前端面試專欄-主流框架:12. Vue3響應式原理與API

&#x1f525; 歡迎來到前端面試通關指南專欄&#xff01;從js精講到框架到實戰&#xff0c;漸進系統化學習&#xff0c;堅持解鎖新技能&#xff0c;祝你輕松拿下心儀offer。 前端面試通關指南專欄主頁 前端面試專欄規劃詳情 Vue3響應式原理與API詳解 一、引言 Vue3作為Vue.j…

DAY 37 早停策略和模型權重的保存

早停策略 import torch.nn as nn import torch.optim as optim import time import matplotlib.pyplot as plt from tqdm import tqdm# Define the MLP model class MLP(nn.Module):def __init__(self):super(MLP, self).__init__()self.fc1 nn.Linear(X_train.shape[1], 10)s…

零基礎搭建Spring AI本地開發環境指南

Spring AI 是一個 Spring 官方團隊主導的開源項目&#xff0c;旨在將生成式人工智能&#xff08;Generative AI&#xff09;能力無縫集成到 Spring 應用程序中。它提供了一個統一的、Spring 風格的抽象層&#xff0c;簡化了與各種大型語言模型&#xff08;LLMs&#xff09;、嵌…

windows登錄系統配置雙因子認證的解決方案

在數字化浪潮席卷全球的今天&#xff0c;安全如同氧氣般不可或缺。Verizon《2023年數據泄露調查報告》指出&#xff0c;80%的黑客攻擊與登錄憑證失竊直接相關。當傳統密碼防護變得千瘡百孔&#xff0c;企業如何在身份驗證的戰場上贏得主動權&#xff1f;答案就藏在"雙保險…

Java數據結構——線性表Ⅱ

一、鏈式存儲結構概述 1. 基本概念&#xff08;邏輯分析&#xff09; 核心思想&#xff1a;用指針將離散的存儲單元串聯成邏輯上連續的線性表 設計動機&#xff1a;解決順序表 "預先分配空間" 與 "動態擴展" 的矛盾 關鍵特性&#xff1a; 結點空間動態…

技術基石:SpreadJS 引擎賦能極致體驗

在能源行業數字化轉型的浪潮中&#xff0c;青島國瑞信息技術有限公司始終以技術創新為核心驅動力&#xff0c;不斷探索前沿技術在能源領域的深度應用。其推出的 RCV 行列視生產數據應用系統之所以能夠在行業內脫穎而出&#xff0c;離不開背后強大的技術基石 ——SpreadJS 引擎。…

Typora - Typora 打字機模式

Typora 打字機模式 1、基本介紹 Typora 打字機模式&#xff08;Typewriter Mode&#xff09;是一種專注于當前寫作行的功能 打字機模式會自動將正在編輯的行保持在屏幕中央&#xff0c;讓用戶更集中注意力&#xff0c;類似于傳統打字機的體驗 2、開啟方式 點擊 【視圖】 -…

3.0 compose學習:MVVM框架+Hilt注解調用登錄接口

文章目錄 前言&#xff1a;1、添加依賴1.1 在settings.gradle.kts中添加1.2 在應用級的build.gradle.kts添加插件依賴1.3 在module級的build.gradle.kts添加依賴 2、實體類2.1 request2.2 reponse 3、網絡請求3.1 ApiService3.2 NetworkModule3.3 攔截器 添加token3.4 Hilt 的 …

git學習資源

動畫演示&#xff1a;Learn Git Branching 終極目標&#xff08;能看懂即入門&#xff09;&#xff1a;git 簡明指南 Git 教程 | 菜鳥教程

C++ 第二階段:模板編程 - 第一節:函數模板與類模板

目錄 一、模板編程的核心概念 1.1 什么是模板編程&#xff1f; 二、函數模板詳解 2.1 函數模板的定義與使用 2.1.1 基本語法 2.1.2 示例&#xff1a;通用交換函數 2.1.3 類型推導規則 2.2 函數模板的注意事項 2.2.1 普通函數與函數模板的調用規則 2.2.2 隱式類型轉換…