logo

Lộ trình

Khóa học

Tài liệu

Mock Interview

Liên hệ

Quay lại
  • Trang chủ

    /

  • Tài liệu

    /

  • DFS & BFS - Khi dữ liệu không được lưu trữ một cách tuyến tính
Tài liệu

DFS & BFS - Khi dữ liệu không được lưu trữ một cách tuyến tính

Ronin Engineer

21 Tháng 7 2025

<p>By <a href="https://www.linkedin.com/in/toan207/?ref=roninhub.com" rel="noreferrer">Bùi Đức Toàn</a></p><blockquote>“Muốn tìm trân châu nơi đáy biển, ắt phải lặn sâu. Muốn nhìn toàn cảnh rừng xanh, phải leo lên đỉnh núi.” - Cổ nhân.</blockquote><p>Từ thuở khai hoang, con người khi thì đào sâu xuống lòng đất để tìm vàng, khi thì lại lần theo dòng sông để biết nó chảy về đâu. Trong thế giới khoa học máy tính, tinh thần khai hoang đó được tái hiện qua hai chiến lược kinh điển trong việc duyệt đồ thị là: </p><ol><li>Tìm kiếm theo chiều sâu (Depth-First Search – DFS).</li><li>Tìm kiếm theo chiều rộng (Breadth-First Search – BFS).</li></ol><p>Mọi người thường rất thân quen với việc <strong>duyệt mảng một cách tuyến tính</strong>, nhưng đôi khi dữ liệu không được lưu trữ như vậy. Việc lần theo dòng chảy của dữ liệu để tìm ra một con đường có thể tìm đến dữ liệu mà ta có chính là mục đích của <strong>DFS và BFS</strong>.</p><p>Giống như người xưa chọn đường mà đi, lúc thì tiến sâu vào từng ngách, từng lối để khám phá tận cùng, khi thì trải rộng tầm mắt, đi khắp nơi để nắm bắt toàn cục. <strong>DFS và BFS </strong>mỗi thuật toán đều mang trong mình một triết lý tìm kiếm riêng biệt.</p><h2 id="%C4%91%E1%BB%93-th%E1%BB%8Bgraph">ĐỒ THỊ - GRAPH</h2> <p>Trước khi đặt chân lên hành trình khám phá với chiến lược <strong>DFS và BFS</strong> thì ta cần biết vùng đất ta sẽ đi, vùng đất này chính là <strong>đồ thị (graph)</strong>.</p> <p><strong>Đồ thị</strong> có hai thành phần chính để tạo nên nó, chính là:</p> <ul> <li>Đỉnh (vertices) - là các thực thể, như thành phố, ngôi làng...</li> <li>Cạnh (edges) - đại diện cho mối liên hệ giữa các đỉnh, như con đường dẫn đến các ngôi làng, thành phố.</li> </ul> <figure class="kg-card kg-image-card"><img src="https://roninhub.com/content/images/2025/07/graph.png" class="kg-image" alt="" loading="lazy" width="695" height="589" srcset="https://roninhub.com/content/images/size/w600/2025/07/graph.png 600w, https://roninhub.com/content/images/2025/07/graph.png 695w"></figure><h2 id="t%C3%ACm-ki%E1%BA%BFm-theo-chi%E1%BB%81u-s%C3%A2udfs">TÌM KIẾM THEO CHIỀU SÂU - DFS</h2> <blockquote> <p>"Kẻ tìm báu vật ẩn trong động sâu, ắt phải dấn thân vào hang tối, đi đến tận cùng, rồi mới quay đầu."</p> </blockquote> <p><strong>Tìm kiếm theo chiều sâu (DFS)</strong> là một phương pháp tìm kiếm <strong>len lỏi qua mọi khe cùng ngõ hẻm</strong> cho đến khi không còn có thể tiến xa hơn, rồi mới quay đầu trở lại để khám phá lối khác.</p> <p>Tư tưởng của <strong>DFS</strong> sẽ sử dụng cấu trúc dữ liệu <strong>Stack - ngăn xếp</strong> để thực hiện.</p> <h4 id="c%C3%A1ch-ho%E1%BA%A1t-%C4%91%E1%BB%99ng">Cách hoạt động</h4> <ol> <li>Bắt đầu từ một <strong>đỉnh (vertice)</strong> gốc đưa vào <strong>Stack</strong>.</li> <li>Lặp cho đến khi <strong>Stack</strong> rỗng: <ol> <li>Lấy đỉnh trên cùng của <strong>Stack</strong>.</li> <li>Đánh dấu đỉnh vừa được lấy ra thành đã được đi đến.</li> <li>Đưa tất cả các đỉnh kề với đỉnh hiện tại vào <strong>Stack</strong>.</li> <li>Đưa đỉnh hiện tại vào mảng kết quả.</li> </ol> </li> </ol> <figure class="kg-card kg-image-card"><img src="https://roninhub.com/content/images/2025/07/dfs.png" class="kg-image" alt="" loading="lazy" width="681" height="626" srcset="https://roninhub.com/content/images/size/w600/2025/07/dfs.png 600w, https://roninhub.com/content/images/2025/07/dfs.png 681w"></figure><h4 id="c%C6%A1-ch%E1%BA%BF-duy%E1%BB%87t-%C4%91%E1%BB%93-th%E1%BB%8B">Cơ chế duyệt đồ thị</h4> <ol> <li>Bắt đầu từ <code>1</code>, stack: <code>[1]</code></li> <li>Pop <code>1</code>, đánh dấu <code>1</code>, push <code>3</code>, <code>2</code> vào stack: <code>[3, 2]</code></li> <li>Pop <code>2</code>, đánh dấu <code>2</code>, push <code>5</code>, <code>4</code> vào stack: <code>[3, 5, 4]</code></li> <li>Pop <code>4</code>, đánh dấu <code>4</code>, stack: <code>[3, 5]</code></li> <li>Pop <code>5</code>, đánh dấu <code>5</code>, stack: <code>[3]</code></li> <li>Pop <code>3</code>, đánh dấu <code>3</code>, push <code>7</code>, <code>6</code> vào stack: <code>[7, 6]</code></li> <li>Pop <code>6</code>, đánh dấu <code>6</code>, stack: <code>[7]</code></li> <li>Pop <code>7</code>, đánh dấu <code>7</code>, stack: <code>[]</code></li> </ol> <p>Kết quả duyệt: <code>1 2 4 5 3 6 7</code> (có thể khác thứ tự nếu thứ tự các đỉnh đưa vào <strong>stack</strong> khác nhau).</p> <h2 id="t%C3%ACm-ki%E1%BA%BFm-theo-chi%E1%BB%81u-r%E1%BB%99ngbfs">TÌM KIẾM THEO CHIỀU RỘNG - BFS</h2> <blockquote> <p>"Muốn nhìn toàn cánh đồng, hãy đứng trên gò cao. Muốn hiểu cả ngôi làng, hãy đi qua từng nhà."</p> </blockquote> <p>Khác với <strong>DFS</strong> - kẻ lữ hành thích lặng lẽ dấn bước với mọi khe cùng ngõ hẻm, <strong>BFS</strong> lại giống với một người đi tuần, chậm rãi nhưng toàn diện.Hắn là kẻ đi từng vòng, từng lớp, bắt đầu từ gốc rồi lan dần ra xa, như gợn sóng loang trên mặt hồ.</p> <p><strong>BFS</strong> là chiến lược duyệt đồ thị theo từng <strong>"lớp khoảng cách"</strong>. Bắt đầu từ đỉnh gốc, nó lần lượt thăm tất cả các đỉnh kề (láng giềng gần nhất), sau đó mới đến các đỉnh xa hơn, rồi xa hơn nữa...</p> <p>Tư tưởng của <strong>BFS</strong> sẽ sử dụng cấu trúc dữ liệu <strong>Queue - hàng đợi</strong> để thực hiện.</p> <h4 id="c%C3%A1ch-ho%E1%BA%A1t-%C4%91%E1%BB%99ng">Cách hoạt động</h4> <ol> <li>Bắt đầu từ một đỉnh gốc đưa vào <strong>Queue</strong>.</li> <li>Lặp cho đến khi <strong>Queue</strong> rỗng: <ol> <li>Lấy đỉnh đầu tiên của <strong>Queue</strong>.</li> <li>Đánh dấu đỉnh vừa được lấy ra thành đã được đi đến.</li> <li>Đưa tất cả các đỉnh kề với đỉnh hiện tại chưa được đánh dấu vào <strong>Queue</strong>.</li> <li>Đưa đỉnh hiện tại vào mảng kết quả.</li> </ol> </li> </ol> <figure class="kg-card kg-image-card"><img src="https://roninhub.com/content/images/2025/07/bfs.png" class="kg-image" alt="" loading="lazy" width="681" height="626" srcset="https://roninhub.com/content/images/size/w600/2025/07/bfs.png 600w, https://roninhub.com/content/images/2025/07/bfs.png 681w"></figure><h4 id="c%C6%A1-ch%E1%BA%BF-duy%E1%BB%87t-%C4%91%E1%BB%93-th%E1%BB%8B">Cơ chế duyệt đồ thị</h4> <ol> <li>Bắt đầu từ <code>1</code>, queue: <code>[1]</code></li> <li>Dequeue <code>1</code>, đánh dấu <code>1</code>, push <code>2</code>, <code>3</code> vào queue: <code>[2, 3]</code></li> <li>Dequeue <code>2</code>, đánh dấu <code>2</code>, push <code>4</code>, <code>5</code> vào queue: <code>[3, 4, 5]</code></li> <li>Dequeue <code>3</code>, đánh dấu <code>3</code>, push <code>6</code>, <code>7</code> vào queue: <code>[4, 5, 6, 7]</code></li> <li>Dequeue <code>4</code>, đánh dấu <code>4</code>, queue: <code>[5, 6, 7]</code></li> <li>Dequeue <code>5</code>, đánh dấu <code>5</code>, queue: <code>[6, 7]</code></li> <li>Dequeue <code>6</code>, đánh dấu <code>6</code>, queue: <code>[7]</code></li> <li>Dequeue <code>7</code>, đánh dấu <code>7</code>, queue: <code>[]</code></li> </ol> <p>Kết quả duyệt: <code>1 2 3 4 5 6 7</code> (có thể khác thứ tự nếu thứ tự các đỉnh đưa vào <strong>queue</strong> khác nhau).</p> <h2 id="%E1%BB%A9ng-d%E1%BB%A5ng-trong-tr%C3%B2-ch%C6%A1i-%C4%91%E1%BA%A5u-tr%C6%B0%E1%BB%9Dng-ch%C3%A2n-l%C3%BDtft">ỨNG DỤNG TRONG TRÒ CHƠI ĐẤU TRƯỜNG CHÂN LÝ - TFT</h2> <h4 id="%C4%91%E1%BB%93-th%E1%BB%8B-kh%C3%B4ng-ch%E1%BB%89-bi%E1%BB%83u-th%E1%BB%8B-d%C6%B0%E1%BB%9Bi-d%E1%BA%A1ng-c%C3%A2y-tree">Đồ thị không chỉ biểu thị dưới dạng cây (Tree)</h4> <blockquote> <p>"Đừng nghĩ chỉ khi bản đồ hiện rõ đỉnh và cạnh thì mới là đồ thị. Đôi khi, một bảng số cũng ẩn chứa mê cung."</p> </blockquote> <p>Nhiều người thường nhầm lẫn rằng, chỉ khi ở dạng <strong>cây (Tree)</strong> mới là một đồ thị, nhưng không phải như thế tất cả các cấu trúc dữ liệu có đường đi, liên kết thì có thể gọi là một đồ thị. Ví dụ như <strong>array</strong>, các phần tử trong array đều liên kết với phần tử đằng sau và đằng trước nó, từ <code>2</code> có thể đi về <code>1</code> mà cũng có thể đi lên <code>3</code>.</p> <figure class="kg-card kg-image-card"><img src="https://roninhub.com/content/images/2025/07/array-graph-1.png" class="kg-image" alt="" loading="lazy" width="897" height="588" srcset="https://roninhub.com/content/images/size/w600/2025/07/array-graph-1.png 600w, https://roninhub.com/content/images/2025/07/array-graph-1.png 897w" sizes="(min-width: 720px) 720px"></figure><h4 id="v%E1%BA%ADy-c%C3%A2u-h%E1%BB%8Fi-%C4%91%E1%BA%B7t-ra-l%C3%A0-t%E1%BA%A1i-sao-ch%C3%BAng-ta-c%E1%BA%A7n-s%E1%BB%AD-d%E1%BB%A5ng-dfs-tr%C3%AAn-array-v%C3%A0-ma-tr%E1%BA%ADn">Vậy câu hỏi đặt ra là, tại sao chúng ta cần sử dụng <strong>DFS</strong> trên <strong>array</strong> và <strong>ma trận</strong>?</h4> <p>Tương tự với <strong>array</strong>, <strong>ma trận</strong> cũng là một đồ thị, vì vậy việc sử dụng <strong>DFS</strong> hay <strong>BFS</strong> ở đây hoàn toàn có thể, để ngắn gọn, ở đây mình dùng <strong>DFS</strong>.</p> <figure class="kg-card kg-image-card"><img src="https://roninhub.com/content/images/2025/07/hexagonal-index.png" class="kg-image" alt="" loading="lazy" width="651" height="395" srcset="https://roninhub.com/content/images/size/w600/2025/07/hexagonal-index.png 600w, https://roninhub.com/content/images/2025/07/hexagonal-index.png 651w"></figure><p>Mục đích chính khi ta sử dụng <strong>DFS</strong> trên một <strong>ma trận </strong>chính là để xác định khoảng cách từ một phần tử đến một phần tử khác. Mình có một ví dụ lấy cảm hứng từ trò chơi <strong>Đấu trường Chân Lý (TFT)</strong>, khi <strong>Seraphine (đứng tại ô 3)</strong> có tầm tấn công là 3, muốn kiểm tra xem khoảng cách từ cô đến <strong>Sylas (đứng tại ô 25)</strong> có nằm trong tầm tấn công không.</p><h4 id="c%C3%A1c-b%C6%B0%E1%BB%9Bc-duy%E1%BB%87t-ma-tr%E1%BA%ADn">Các bước duyệt ma trận</h4> <p><strong>B1.</strong> Bắt đầu từ <code>3</code> và khoảng cách từ <code>3</code> tới <code>3</code> là <code>0</code>, stack:</p> <pre><code>[{position: 3, length: 0}] </code></pre> <p><strong>B2.</strong> Pop <code>{position: 3, length: 0}</code>, đánh dấu <code>3</code>, push <code>2</code>, <code>10</code>, <code>11</code>, <code>4</code> khoảng cách so với <code>3</code> là <code>0 + 1</code> vào stack:</p> <pre><code>[{position: 2, length: 1}, {position: 10, length: 1}, {position: 11, length: 1}, {position: 4, length: 1}] </code></pre> <p><strong>B3.</strong> Pop <code>{position: 4, length: 1}</code>, đánh dấu <code>4</code>, push <code>12</code>, <code>5</code> khoảng cách so với <code>3</code> là <code>1 + 1</code> vào stack:</p> <pre><code>[{position: 2, length: 1}, {position: 10, length: 1}, {position: 11, length: 1}, {position: 12, length: 2}, {position: 5, length: 2}] </code></pre> <p><strong>B4.</strong> Pop <code>{position: 5, length: 2}</code>, đánh dấu <code>5</code>, push <code>13</code>, <code>6</code> khoảng cách so với <code>3</code> là <code>2 + 1</code> vào stack:</p> <pre><code>[{position: 2, length: 1}, {position: 10, length: 1}, {position: 11, length: 1}, {position: 12, length: 2}, {position: 13, length: 3}, {position: 6, length: 3}] </code></pre> <p><strong>B5.</strong> Pop <code>{position: 6, length: 3}</code>, đánh dấu <code>6</code>, push <code>14</code>, <code>7</code> khoảng cách so với <code>3</code> là <code>3 + 1</code> vào stack:</p> <pre><code>[{position: 2, length: 1}, {position: 10, length: 1}, {position: 11, length: 1}, {position: 12, length: 2}, {position: 13, length: 3}, {position: 14, length: 4}, {position: 7, length: 4}] </code></pre> <p><strong>B6.</strong> Pop <code>{position: 7, length: 4}</code>, đánh dấu <code>7</code>, stack:</p> <pre><code>[{position: 2, length: 1}, {position: 10, length: 1}, {position: 11, length: 1}, {position: 12, length: 2}, {position: 13, length: 3}, {position: 14, length: 4}] </code></pre> <p>...</p> <p>Khi duyệt qua hết tất cả ma trận, toàn bộ các đỉnh sẽ được đánh dấu, nhưng <strong>stack</strong> vẫn chưa rỗng, ta chỉ cần lấy <strong>khoảng cách nhỏ nhất</strong> giữa <strong>khoảng cách so với 3 của đỉnh mới</strong> và <strong>khoảng cách so với 3 của đỉnh cũ</strong> tức là lúc này, từ <strong>đỉnh 3 ban đầu</strong> có <strong>khoảng cách ngắn hơn</strong> nếu đi từ <strong>đỉnh mới</strong>.</p> <p>Giả sử khi bạn ở <strong>Hà Nội</strong>, bạn muốn đi đến <strong>Hà Giang</strong>. Bạn có thể chọn cách đi vào <strong>TP. Hồ Chí Minh</strong>, xong rồi lại trở lại <strong>Hà Giang</strong>. Một cách khác là đi thẳng từ <strong>Hà Nội</strong> tới <strong>Hà Giang</strong>. Đường đi giữa hai cách sẽ khác nhau hoàn toàn, dễ thấy, ta sẽ chỉ lấy kết quả tối ưu nhất tức là từ <strong>Hà Nội</strong> tới <strong>Hà Giang</strong> để tiết kiệm chi phí.</p> <p>Và sau khi duyệt xong chúng ta sẽ có kết quả như bên dưới.</p> <figure class="kg-card kg-image-card"><img src="https://roninhub.com/content/images/2025/07/hexagonal-distance.png" class="kg-image" alt="" loading="lazy" width="651" height="395" srcset="https://roninhub.com/content/images/size/w600/2025/07/hexagonal-distance.png 600w, https://roninhub.com/content/images/2025/07/hexagonal-distance.png 651w"></figure><p>Vậy lúc này với việc duyệt từ vị trí của <strong>Seraphine (ô số 3)</strong> ta chỉ cần kiểm tra <strong>ma trận khoảng cách </strong>của <strong>Seraphine</strong> tại vị trí của <strong>Sylas (ô số 25) </strong>đứng với kết quả là <strong>3.</strong> Điều này đã trả lời được bài toán rằng <strong>Sylas</strong> đang trong tầm tấn công <strong>3</strong> của <strong>Seraphine</strong> và có thể gọi đến hàm tấn công để <strong>Seraphine </strong>tấn công <strong>Sylas</strong>.</p><h2 id="k%E1%BA%BFt-lu%E1%BA%ADn">KẾT LUẬN</h2> <p><strong>DFS, BFS</strong> không chỉ là một thuật toán mang tính học thuật, nó là một trong nhiều cách để mọi người thấy các thế giới số kết nối. Mỗi cái mang một triết lý tìm kiếm riêng:</p> <ul> <li>Khi cần khám phá đến tận cùng, hãy nghĩ như DFS.</li> <li>Khi cần bao quát và công bằng, hãy hành xử như BFS.</li> </ul> <p>Và cuối cùng, mọi cấu trúc dữ liệu đều có thể có <strong>hình bóng của đồ thị</strong>, điều quan trọng không phải nó <strong>"trông như đồ thị"</strong> mà là ta nhìn thấy <strong>đồ thị trong nó</strong>.</p> <hr><p>✏️ System Design VN: <a href="https://fb.com/groups/systemdesign.vn%5C?ref=roninhub.com">https://fb.com/groups/systemdesign.vn</a><br>📚 Đọc thêm tài liệu khác: <a href="https://roninhub.com/tai-lieu%5C">https://roninhub.com/tai-lieu</a><br>🎬 Youtube: <a href="https://youtube.com/@ronin-engineer?ref=roninhub.com">https://youtube.com/@ronin-engineer</a></p>

By Bùi Đức Toàn

“Muốn tìm trân châu nơi đáy biển, ắt phải lặn sâu. Muốn nhìn toàn cảnh rừng xanh, phải leo lên đỉnh núi.” - Cổ nhân.

Từ thuở khai hoang, con người khi thì đào sâu xuống lòng đất để tìm vàng, khi thì lại lần theo dòng sông để biết nó chảy về đâu. Trong thế giới khoa học máy tính, tinh thần khai hoang đó được tái hiện qua hai chiến lược kinh điển trong việc duyệt đồ thị là:

  1. Tìm kiếm theo chiều sâu (Depth-First Search – DFS).
  2. Tìm kiếm theo chiều rộng (Breadth-First Search – BFS).

Mọi người thường rất thân quen với việc duyệt mảng một cách tuyến tính, nhưng đôi khi dữ liệu không được lưu trữ như vậy. Việc lần theo dòng chảy của dữ liệu để tìm ra một con đường có thể tìm đến dữ liệu mà ta có chính là mục đích của DFS và BFS.

Giống như người xưa chọn đường mà đi, lúc thì tiến sâu vào từng ngách, từng lối để khám phá tận cùng, khi thì trải rộng tầm mắt, đi khắp nơi để nắm bắt toàn cục. DFS và BFS mỗi thuật toán đều mang trong mình một triết lý tìm kiếm riêng biệt.

ĐỒ THỊ - GRAPH

Trước khi đặt chân lên hành trình khám phá với chiến lược DFS và BFS thì ta cần biết vùng đất ta sẽ đi, vùng đất này chính là đồ thị (graph).

Đồ thị có hai thành phần chính để tạo nên nó, chính là:

  • Đỉnh (vertices) - là các thực thể, như thành phố, ngôi làng...
  • Cạnh (edges) - đại diện cho mối liên hệ giữa các đỉnh, như con đường dẫn đến các ngôi làng, thành phố.

TÌM KIẾM THEO CHIỀU SÂU - DFS

"Kẻ tìm báu vật ẩn trong động sâu, ắt phải dấn thân vào hang tối, đi đến tận cùng, rồi mới quay đầu."

Tìm kiếm theo chiều sâu (DFS) là một phương pháp tìm kiếm len lỏi qua mọi khe cùng ngõ hẻm cho đến khi không còn có thể tiến xa hơn, rồi mới quay đầu trở lại để khám phá lối khác.

Tư tưởng của DFS sẽ sử dụng cấu trúc dữ liệu Stack - ngăn xếp để thực hiện.

Cách hoạt động

  1. Bắt đầu từ một đỉnh (vertice) gốc đưa vào Stack.
  2. Lặp cho đến khi Stack rỗng:
    1. Lấy đỉnh trên cùng của Stack.
    2. Đánh dấu đỉnh vừa được lấy ra thành đã được đi đến.
    3. Đưa tất cả các đỉnh kề với đỉnh hiện tại vào Stack.
    4. Đưa đỉnh hiện tại vào mảng kết quả.

Cơ chế duyệt đồ thị

  1. Bắt đầu từ 1, stack: [1]
  2. Pop 1, đánh dấu 1, push 3, 2 vào stack: [3, 2]
  3. Pop 2, đánh dấu 2, push 5, 4 vào stack: [3, 5, 4]
  4. Pop 4, đánh dấu 4, stack: [3, 5]
  5. Pop 5, đánh dấu 5, stack: [3]
  6. Pop 3, đánh dấu 3, push 7, 6 vào stack: [7, 6]
  7. Pop 6, đánh dấu 6, stack: [7]
  8. Pop 7, đánh dấu 7, stack: []

Kết quả duyệt: 1 2 4 5 3 6 7 (có thể khác thứ tự nếu thứ tự các đỉnh đưa vào stack khác nhau).

TÌM KIẾM THEO CHIỀU RỘNG - BFS

"Muốn nhìn toàn cánh đồng, hãy đứng trên gò cao. Muốn hiểu cả ngôi làng, hãy đi qua từng nhà."

Khác với DFS - kẻ lữ hành thích lặng lẽ dấn bước với mọi khe cùng ngõ hẻm, BFS lại giống với một người đi tuần, chậm rãi nhưng toàn diện.Hắn là kẻ đi từng vòng, từng lớp, bắt đầu từ gốc rồi lan dần ra xa, như gợn sóng loang trên mặt hồ.

BFS là chiến lược duyệt đồ thị theo từng "lớp khoảng cách". Bắt đầu từ đỉnh gốc, nó lần lượt thăm tất cả các đỉnh kề (láng giềng gần nhất), sau đó mới đến các đỉnh xa hơn, rồi xa hơn nữa...

Tư tưởng của BFS sẽ sử dụng cấu trúc dữ liệu Queue - hàng đợi để thực hiện.

Cách hoạt động

  1. Bắt đầu từ một đỉnh gốc đưa vào Queue.
  2. Lặp cho đến khi Queue rỗng:
    1. Lấy đỉnh đầu tiên của Queue.
    2. Đánh dấu đỉnh vừa được lấy ra thành đã được đi đến.
    3. Đưa tất cả các đỉnh kề với đỉnh hiện tại chưa được đánh dấu vào Queue.
    4. Đưa đỉnh hiện tại vào mảng kết quả.

Cơ chế duyệt đồ thị

  1. Bắt đầu từ 1, queue: [1]
  2. Dequeue 1, đánh dấu 1, push 2, 3 vào queue: [2, 3]
  3. Dequeue 2, đánh dấu 2, push 4, 5 vào queue: [3, 4, 5]
  4. Dequeue 3, đánh dấu 3, push 6, 7 vào queue: [4, 5, 6, 7]
  5. Dequeue 4, đánh dấu 4, queue: [5, 6, 7]
  6. Dequeue 5, đánh dấu 5, queue: [6, 7]
  7. Dequeue 6, đánh dấu 6, queue: [7]
  8. Dequeue 7, đánh dấu 7, queue: []

Kết quả duyệt: 1 2 3 4 5 6 7 (có thể khác thứ tự nếu thứ tự các đỉnh đưa vào queue khác nhau).

ỨNG DỤNG TRONG TRÒ CHƠI ĐẤU TRƯỜNG CHÂN LÝ - TFT

Đồ thị không chỉ biểu thị dưới dạng cây (Tree)

"Đừng nghĩ chỉ khi bản đồ hiện rõ đỉnh và cạnh thì mới là đồ thị. Đôi khi, một bảng số cũng ẩn chứa mê cung."

Nhiều người thường nhầm lẫn rằng, chỉ khi ở dạng cây (Tree) mới là một đồ thị, nhưng không phải như thế tất cả các cấu trúc dữ liệu có đường đi, liên kết thì có thể gọi là một đồ thị. Ví dụ như array, các phần tử trong array đều liên kết với phần tử đằng sau và đằng trước nó, từ 2 có thể đi về 1 mà cũng có thể đi lên 3.

Vậy câu hỏi đặt ra là, tại sao chúng ta cần sử dụng DFS trên array và ma trận?

Tương tự với array, ma trận cũng là một đồ thị, vì vậy việc sử dụng DFS hay BFS ở đây hoàn toàn có thể, để ngắn gọn, ở đây mình dùng DFS.

Mục đích chính khi ta sử dụng DFS trên một ma trận chính là để xác định khoảng cách từ một phần tử đến một phần tử khác. Mình có một ví dụ lấy cảm hứng từ trò chơi Đấu trường Chân Lý (TFT), khi Seraphine (đứng tại ô 3) có tầm tấn công là 3, muốn kiểm tra xem khoảng cách từ cô đến Sylas (đứng tại ô 25) có nằm trong tầm tấn công không.

Các bước duyệt ma trận

B1. Bắt đầu từ 3 và khoảng cách từ 3 tới 3 là 0, stack:

[{position: 3, length: 0}]

B2. Pop {position: 3, length: 0}, đánh dấu 3, push 2, 10, 11, 4 khoảng cách so với 3 là 0 + 1 vào stack:

[{position: 2, length: 1}, {position: 10, length: 1}, 
{position: 11, length: 1}, {position: 4, length: 1}]

B3. Pop {position: 4, length: 1}, đánh dấu 4, push 12, 5 khoảng cách so với 3 là 1 + 1 vào stack:

[{position: 2, length: 1}, {position: 10, length: 1}, 
{position: 11, length: 1}, {position: 12, length: 2}, 
{position: 5, length: 2}]

B4. Pop {position: 5, length: 2}, đánh dấu 5, push 13, 6 khoảng cách so với 3 là 2 + 1 vào stack:

[{position: 2, length: 1}, {position: 10, length: 1}, 
{position: 11, length: 1}, {position: 12, length: 2},
{position: 13, length: 3}, {position: 6, length: 3}]

B5. Pop {position: 6, length: 3}, đánh dấu 6, push 14, 7 khoảng cách so với 3 là 3 + 1 vào stack:

[{position: 2, length: 1}, {position: 10, length: 1}, 
{position: 11, length: 1}, {position: 12, length: 2}, 
{position: 13, length: 3}, {position: 14, length: 4}, 
{position: 7, length: 4}]

B6. Pop {position: 7, length: 4}, đánh dấu 7, stack:

[{position: 2, length: 1}, {position: 10, length: 1}, 
{position: 11, length: 1}, {position: 12, length: 2},  
{position: 13, length: 3}, {position: 14, length: 4}]

...

Khi duyệt qua hết tất cả ma trận, toàn bộ các đỉnh sẽ được đánh dấu, nhưng stack vẫn chưa rỗng, ta chỉ cần lấy khoảng cách nhỏ nhất giữa khoảng cách so với 3 của đỉnh mới và khoảng cách so với 3 của đỉnh cũ tức là lúc này, từ đỉnh 3 ban đầu có khoảng cách ngắn hơn nếu đi từ đỉnh mới.

Giả sử khi bạn ở Hà Nội, bạn muốn đi đến Hà Giang. Bạn có thể chọn cách đi vào TP. Hồ Chí Minh, xong rồi lại trở lại Hà Giang. Một cách khác là đi thẳng từ Hà Nội tới Hà Giang. Đường đi giữa hai cách sẽ khác nhau hoàn toàn, dễ thấy, ta sẽ chỉ lấy kết quả tối ưu nhất tức là từ Hà Nội tới Hà Giang để tiết kiệm chi phí.

Và sau khi duyệt xong chúng ta sẽ có kết quả như bên dưới.

Vậy lúc này với việc duyệt từ vị trí của Seraphine (ô số 3) ta chỉ cần kiểm tra ma trận khoảng cách của Seraphine tại vị trí của Sylas (ô số 25) đứng với kết quả là 3. Điều này đã trả lời được bài toán rằng Sylas đang trong tầm tấn công 3 của Seraphine và có thể gọi đến hàm tấn công để Seraphine tấn công Sylas.

KẾT LUẬN

DFS, BFS không chỉ là một thuật toán mang tính học thuật, nó là một trong nhiều cách để mọi người thấy các thế giới số kết nối. Mỗi cái mang một triết lý tìm kiếm riêng:

  • Khi cần khám phá đến tận cùng, hãy nghĩ như DFS.
  • Khi cần bao quát và công bằng, hãy hành xử như BFS.

Và cuối cùng, mọi cấu trúc dữ liệu đều có thể có hình bóng của đồ thị, điều quan trọng không phải nó "trông như đồ thị" mà là ta nhìn thấy đồ thị trong nó.


✏️ System Design VN: https://fb.com/groups/systemdesign.vn
📚 Đọc thêm tài liệu khác: https://roninhub.com/tai-lieu
🎬 Youtube: https://youtube.com/@ronin-engineer

middle
dsa
buiductoan

Bài viết liên quan

Materialized view với PostgreSQL

Khái niệm view trong relational database chắc hẳn đã quá quen thuộc với anh em dev. View chỉ lưu trữ câu lệnh truy vấn, không lưu kết quả truy vấn và thực hiện truy vấn trên (các) bảng gốc mỗi khi view được truy cập. Vậy nếu truy vấn của view là một truy vấn phức tạp cho thống kế, cần JOIN nhiều bảng, nhiều điều kiện FILTER, tập dữ liệu ở bảng gốc lớn, có nhiều phép tổng hợp như SUM, AVG… Thì các bạn có đoán được vấn đề gì sẽ xuất hiện không? Nếu câu trả lời của các bạn là “hiệu suất của truy

Stack & Queue - Khi array không chỉ để lưu trữ

Stack & Queue là hai cấu trúc dữ liệu cơ bản và phổ biến trong con đường của một lập trình viên, hẳn rằng những lập trình viên có kinh nghiệm đều đã nghe, đã biết đến cấu trúc dữ liệu này, vậy cuối cùng thì, Stack & Queue là gì và nó có tác dụng thế nào? QUEUE - HÀNG ĐỢI Để bắt đầu nhẹ nhàng, chúng ta bắt đầu với Queue trước. Trước tiên thì Queue là một cấu trúc dữ liệu có dạng FIFO (First In Frist Out) tức là vào trước thì ra trước. Một ví dụ dễ thấy trong cuộc sống hàng ngày là việc xếp

Connection Pool: Tối ưu hiệu suất kết nối Database trong hệ thống Backend

By Đức Hiếu 1. Connection Pool là gì? Connection Pool là một cơ chế quản lý kết nối giữa các ứng dụng đến cơ sở dữ liệu (Database Server), trong đó một tập hợp các kết nối được khởi tạo trước và duy trì sẵn sàng để sử dụng. Thay vì phải thiết lập kết nối mới mỗi khi cần truy vấn dữ liệu. Database Client (thường là Backend App) có thể "mượn" một kết nối có sẵn từ pool, sử dụng xong và trả lại pool để tái sử dụng. 1.1. Vấn đề với phương pháp kết nối truyền thống Trong mô hình kết nối truyền

HTTP/1.1 vs HTTP/2 : Tại sao HTTP/2 lại nhanh hơn?

By @VietDuc HTTP (Hypertext Transfer Protocol) là giao thức truyền tải tài nguyên cho Web theo mô hình Client-Server (hoặc Request-Response). Ví dụ khi bạn truy cập https://roninhub.com/ . Trình duyệt sẽ tạo các HTTP request đến Server lấy các file HTML, Ảnh, Text… hiển thị lên như ảnh bên dưới. HTTP hoạt động thế nào? Khi một Client (thường là trình duyệt) gửi một HTTP request, quá trình giao tiếp giữa Client và Server thường diễn ra qua các bước sau: * Thiết lập kết nối TCP: Client

Transaction trong Store Procedure: Vấn đề gì xảy ra khi quên ROLLBACK?

By Đức Hiếu Store Procedure: Vũ khí hai lưỡi trong tối ưu hóa performance Trong quá trình phát triển phần mềm, chúng ta thường tin rằng Store Procedure là công cụ mạnh mẽ để tối ưu hiệu suất hệ thống. Tuy nhiên, qua trải nghiệm thực tế, mình nhận ra rằng nếu không sử dụng đúng cách, chúng có thể gây ra những vấn đề nghiêm trọng về hiệu năng. Bài viết này chia sẻ một bài học quý giá về trường hợp Store Procedure có thể trở thành nguyên nhân làm chậm hệ thống nếu không được xử lý lỗi đúng cách.

Tất cả bài viết
logo

HỘ KINH DOANH LẬP VƯƠNG

Giấy chứng nhận đăng ký doanh nghiệp số: 8656162915-001. Cấp ngày 21/02/2024. Nơi cấp: Sở Kế hoạch và Đầu tư TP. Hà Nội

PHƯƠNG THỨC THANH TOÁN

vnpay

LIÊN HỆ

roninengineer88@gmail.com

0362228388

26 ngõ 156 Hồng Mai, Hai Bà Trưng, Hà Nội

THEO DÕI CHÚNG TÔI

Facebook

Youtube

Tiktok

CHÍNH SÁCH

Chính sách bảo mật

Chính sách thanh toán

Đổi trả/Hoàn tiền

Hướng dẫn thanh toán VNPAY

PHƯƠNG THỨC THANH TOÁN

vnpay

Ronin Engineer 2024