- vừa được xem lúc

Lý thyết đồ thị trong Thuật Toán (P2)

0 0 3

Người đăng: Toàn Vũ Đức

Theo Viblo Asia

Duyệt đồ thị (Graph Traversal)

Phần II này sẽ tập trung thảo luận về 2 thuật toán đồ thị cơ bản: Tím kiếm theo chiều sâu (Depth-First Search) hay thường gọi là DFS và Tìm kiếm theo chiều rộng (Breadth-First Search) viết tắt là BFS (còn tên gọi khác là thuật toán Loang).

Cả 2 thuật toán này đều được cung cấp 1 nút bắt đầu trong đồ thị và chúng ta sẽ truy cập tất cả các nút có thể truy cập được từ nút bắt đầu. Sự khác biệt giữa 2 thuật toán này là thứ tự truy cập các nút của chúng.

Depth-First Search (DFS)

Thuật toán DFS (tìm kiếm theo chiều sâu) là một kỹ thuật duyệt đồ thị đơn giản. Thuật toán bắt đầu với 1 nút nguồn và tiến tới các nút khác có thể tiếp cận được từ nút nguồn bằng cách sử dụng các cạnh của đồ thị.

Tìm kiếm theo chiều sâu luôn theo một đường dẫn (path) duy nhất trong đồ thị miễn là nó tìm thấy các nút mới. Sau đó, nó quay lại các nút trước đó và bắt đầu khám phá các phần khác của đồ thị. Thuật toán này có đánh dấu lại các nút đã thăm, do đó nó chỉ đi qua mỗi nút một lần.

Tôi sẽ lấy ví dụ để chúng ta xem cách thức mà DFS hoạt động một cách trực quan hơn với đồ thị dưới đây:

image.png

Chúng ta sẽ duyệt toàn bộ các nút của đồ thị trên với DFS. Ta có thể chọn nút nguồn là bất kì nút nào của đồ thị; Tại đây tôi sẽ chọn tạm nút 1 là nút nguồn. Đầu tiên ta di chuyển đến nút 2 thông qua cạnh (như vậy ta đã thăm được nút 2) image.png

Sau đó, ta tiếp tục thăm nút 3 và nút 5 theo đường dẫn mà ở phần lý thuyết bên trên ta đã nhắc.

image.png

Ta có thể thấy hàng xóm của nút 5 hay nút kề với nút 5 là 2 và 3, nhưng trong quá trình duyệt vừa rồi ta đã thăm cả 2 nút này rồi nên đã đến lúc quay lại các nút trước đó. Ta quay lại nút 2 và nút 3 thì đều thấy các hàng xóm (hay nút kề) của các nút này đều đã được thăm nên ta quay lại nút 1 và tiến hành thăm nốt nút 4 là hàng xóm của nút 1.

image.png

Như vậy là ta đã truy cập tất cả các nút của đồ thị.

Độ phức tạp thời gian của DFS là O(n + m) trong đó n là số nút và m là số cạnh, vì thuật toán xử lý với mỗi nút và mỗi cạnh chỉ 1 lần.

Tiếp theo là phần Implement Code của DFS với C++:

DFS có thể được triển khai thuật tiện bằng cách sử dụng đệ quy hoặc stack. Tùy mỗi người, hầu như thấy đệ quy dùng tiện hơn nhưng mình lại quen code với stack.

Đầu tiên ta sử dụng danh sách kề để lưu trữ đồ thị

vector<int> graph(n);

và sử dụng một vector<bool> vis để đánh dẫu các nút đã thăm.

vector<bool> visited(n, false)

Ban đầu, mỗi giá trị của visited được đánh dấu là false và khi duyệt đến nút s chẳng hạn thì ta gán visited[s] = true. Điều này tránh việc ta thăm 1 nút 2 lần.

Với việc triển khai DFS bằng đệ quy, ta triển khai như sau:

void dfs(int s) { if(visited[s]) return; visited[s] = true; // process node s for(auto u : graph[s]) { dfs(u); }
}

Với cách triển khai bằng Stack

void dfs(int s) { stack<int> st; st.push(s); vis[s] = true; while(!st.empty()) { auto cur = st.top(); st.pop(); for(auto u : graph[cur]) { if(!vis[u]) { st.push(u); vis[u] = true; } } }
}

Với dạng lưu trữ đồ thị ở dạng ma trận như ta đã nói ở P1. Thì ta triển khai theo cả 2 cách như sau:

vector<vector<int>> grid(r, vector<int> c);

Vector 2 chiều để lưu trữ đồ thị với r hàng, c cột. image.png

Tôi xin nhắc lại 1 chút: với ma trận ta có thể di chuyển theo 4 hướng: lên trên, xuống dưới, sang trái, sang phải. Với 1 là đường có thể đi, 0 là vật cản hay nước không thể đi.

vector<vector<bool>> vis;

Tương tự như trên ta cũng sử dụng vis để đánh dấu tọa độ các nút đã thăm.

vector<pair<int, int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

Cái này dùng để di chuyển theo 4 hướng lên trên, xuống dưới, sang trái, sang phải

Với cách sử dụng đệ quy:

void dfs(pair<int, int> s, vector<vector<int>>& grid, vector<vector<bool>>& vis) { int x = s.first; int y = s.second; if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size() || vis[x][y] || grid[x][y] != 1) { return; } vis[x][y] = true; vector<pair<int, int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; for (const auto& dir : directions) { int newX = x + dir.first; int newY = y + dir.second; dfs(make_pair(newX, newY), grid, vis); }
}

Với cách sử dụng stack

void dfs(pair<int, int> start, vector<vector<int>>& grid, vector<vector<bool>>& vis) { vector<pair<int, int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; stack<pair<int, int>> s; s.push(start); while (!s.empty()) { pair<int, int> current = s.top(); s.pop(); int x = current.first; int y = current.second; if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size() || vis[x][y] || grid[x][y] != 1) { continue; } vis[x][y] = true; for (const auto& dir : directions) { int newX = x + dir.first; int newY = y + dir.second; s.push(make_pair(newX, newY)); } }
}
Mai viết nốt BFS HAHA

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học lập trình game cần những gì?

Lập trình game là làm gì. Các ngôn ngữ các bạn có thể sử dụng để lập trình game : C, C++, C#, Java, Python,... Các bước cơ bản để lập trình game. . Hiển thị: Đã là game thì hiển thị không thể thiếu, lúc đầu các bạn chỉ làm cho phần hiển thị thật đơn giản, các bạn đừng quá chú tâm vào việc làm sao ch

0 0 40

- vừa được xem lúc

[MFC] Http request with winsock2.h

Giới thiệu. Xin chào, trong bài này mình sẽ giới thiệu 1 số lưu ý khi sử dụng thư viện winsock2.h (thư viện window socket) sử dụng trong window app. Đầu tiên, bạn sẽ dễ dàng search được 1 ví dụ cụ thể trên document của winsock2.

0 0 32

- vừa được xem lúc

Design parttern

Builder. 1. Ý tưởng. Builder là một mẫu thiết kế sáng tạo cho phép bạn xây dựng các đối tượng phức tạp theo từng bước.

0 0 29

- vừa được xem lúc

Kỹ thuật viết mã nguồn hiệu quả

Kỹ thuật viết mã nguồn hiệu quả? Hôm nay bài viết này mình không đề cập tới thuật toán, hãy coi như rằng chúng ta đã có thuật toán tốt nhất có thể và bây giờ chúng ta phải làm gì để có thể tăng tính hiệu quả của code. Bài viết này mình sẽ lấy ngôn ngữ lập trình C/C++ để minh họa về các hàm, các thao

0 0 34

- vừa được xem lúc

Singleton Design pattern

Singleton Design pattern. 1. Vấn đề. - Ý tưởng:.

0 0 31

- vừa được xem lúc

So sánh Python và C++

Cuộc tranh luận giữa Python và C ++ là một chủ đề hấp dẫn vì cả hai ngôn ngữ lập trình đều rất khác nhau về cú pháp, tính đơn giản, cách sử dụng và cách tiếp cận tổng thể để lập trình. Vì vậy, mọi người cảm thấy khó khăn khi lựa chọn ngôn ngữ lập trình nào để học.

0 0 34