SHORTEST PATH: Thuật toán tìm đường đi ngắn nhất

Ngày đăng: 12/09/2020   -    Cập nhật: 23/10/2020


Trong thế giới thực, có rất nhiều bài toán được mô phỏng và giải bằng các chương trình máy tính. Và một trong những bài toán phổ biến mà bất kỳ lập trình viên nào cũng phải học đó là: Tìm đường đi ngắn nhất (Shortest path).

1. Thuật toán SHORTEST PATH là gì?


Thuật toán Shortest Path - Tìm đường đi ngắn nhất

Thuật toán Shortest Path - Tìm đường đi ngắn nhất
 
Nhắc đến giải thuật duyệt đồ thị, chắc ai học công nghệ thông tin cũng biết đến 2 thuật toán cơ bản: Depth-First Search (DFS) , Breadth-First Search (BFS).

Về mặt ý nghĩa, cả 2 thuật toán đều thực hiện trên đồ thị không có trọng số.

Thuật toán DFS cho ta tìm đường đi đến đỉnh ta muốn (có thể chưa ngắn nhất).

Còn thuật toán BFS cũng tìm đường đi đến đỉnh ta muốn nhưng là đường ngắn nhất có thể.

Trong đồ thị có trọng số, vấn đề sẽ khó hơn, nó nảy sinh ra 1 bài toán, đó là bài toán tìm đường đi ngắn nhất giữa 2 đỉnh.

Nó là bài toán rất quen thuộc với những người bắt đầu học Graph.

Để giải quyết bài toán này, đến giờ có nhiều thuật toán và các biến thể. Trong bài viết này mình sẽ chắc lọc và lựa chọn sử dụng Moore – Dijkstra.

Thuật toán Dijkstra cho phép tìm đường đi ngắn nhất từ một đỉnh s đến các đỉnh còn lại của đồ thị và chiều dài (trọng số) tương ứng.

Phương pháp của thuật toán là xác định tuần tự đỉnh có chiều dài đến s theo thứ tự tăng dần.

Thuật toán được xây dựng trên cơ sở gán cho mỗi đỉnh các nhãn tạm thời.

Nhãn tạm thời của các đỉnh cho biết cận trên của chiều dài đường đi ngắn nhất từ s đến đỉnh đó.

Nhãn của các đỉnh sẽ biến đổi trong các bước lặp, mà ở mỗi bước lặp sẽ có một nhãn tạm thời trở thành chính thức.

Nếu nhãn của một đỉnh nào đó trở thành chính thức thì đó cũng chính là chiều dài ngắn nhất của đường đi từ s đến đỉnh đó.


2. Ý tưởng và Mô tả thuật toán Dijkstra để tìm đường đi ngắn nhất


Trong phần này, chúng ta sẽ phân tích từng bước Thuật toán Dijkstra. Ở đây ta sẽ sử dụng đồ thị bên dưới để làm ví dụ để giúp bạn hiểu rõ hơn về thuật toán này.

Ví dụ minh họa thuật toán tìm đường đi ngắn nhất (1)

Như bạn đã biết, thuật toán của Dijkstra là một thuật toán Greedy (Thuật toán tham lam)

Điều này có nghĩa là chúng ta sẽ đi một con đường ngắn hơn từ đỉnh này đến đỉnh kia. Thuật toán hoàn tất khi chúng ta truy cập tất cả các đỉnh của đồ thị.

Tuy nhiên, hãy cẩn thận - đôi khi khi chúng ta tìm thấy một đỉnh mới, có thể có các đường đi ngắn hơn qua nó từ một đỉnh đã truy cập đến một đỉnh đã được truy cập khác.

Chúng ta có thể xem bên dưới các bước để hoàn thành thuật toán Dijkstra.


Ví dụ minh họa thuật toán tìm đường đi ngắn nhất (2)

Chúng ta có thể bắt đầu với nút A và chúng ta có 2 con đường.

  • Đầu tiên là từ A đến B với độ dài 5
  • Và từ A đến C với độ dài 3.

Vì vậy, chúng ta có thể viết trong danh sách kế bên với các đỉnh đã truy cập là 2 đỉnh mới (B, C) và trọng số để đến đó.

Sau đó, như đã nói trước đó - chúng ta sẽ chọn con đường từ A -> C.


Ví dụ minh họa thuật toán tìm đường đi ngắn nhất (3)

Khi truy cập vào đỉnh C, chúng ta có thể thấy rằng có 3 đường đi khác nhau:

  • Con đường đầu tiên là C đến B
  • Con đường thứ hai là C đến D
  • Con đường thứ ba là C đến E

Vì vậy, hãy ghi vào danh sách hai đỉnh mới và chọn con đường ngắn nhất là C đến B.

Ví dụ minh họa thuật toán tìm đường đi ngắn nhất (4)


Bây giờ tại B, chúng ta có 3 đường:

  • B đến D
  • B đến E
  • B quay lại C

Chúng ta chọn con đường ngắn nhất là B đến D và chúng ta cập nhật vào danh sách trọng số mới của các đường đi từ A đến các đỉnh khác.

Ví dụ minh họa thuật toán tìm đường đi ngắn nhất (5)

Bây giờ như chúng ta có thể thấy không có đường đi mới nào từ D đến E.

Trong trường hợp đó, chúng ta quay lại đỉnh trước đó để kiểm tra đường đi ngắn nhất.

Bây giờ có một đường với độ dài 4 đi đến E và một đường đi đến C.

Trong trường hợp này, chúng ta chọn bất kỳ đường nào chúng ta thích.

Cuối cùng, chúng ta có thể thấy rằng bất kỳ phương án nào chúng ta đi trên đường từ A đến E đều có trọng số như nhau vì các đường đi ngắn nhất được ghi trong danh sách.

Cuối cùng, chúng ta có thể thấy tất cả các đường đi mà chúng ta đã sử dụng.


Ví dụ minh họa thuật toán tìm đường đi ngắn nhất (6)

3. Triển khai thuật toán tìm đường đi ngắn nhất trong Java


Để triển khai thuật toán tìm đường đi ngắn nhất Dijkstra, trước hết - chúng ta phải tạo các cạnh và đỉnh của biểu đồ:

File Vert.java



package dijkstra.algo;

import java.util.ArrayList;
import java.util.List;

public class Vert implements Comparable<Vert> {

    private boolean visited;
    private String name;
    private List<EdgeList;
    private double dist = Double.MAX_VALUE;
    private Vert pr;

    public Vert(String name) {
        this.name = name;
        this.List = new ArrayList<>();
    }

    public List<EdgegetList() {
        return List;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setList(List<EdgeList) {
        this.List = List;
    }

    public void addNeighbour(Edge edge) {
        this.List.add(edge);
    }

    public boolean Visited() {
        return visited;
    }

    public void setVisited(boolean visited) {
        this.visited = visited;
    }

    public Vert getPr() {
        return pr;
    }

    public void setPr(Vert pr) {
        this.pr = pr;
    }

    public double getDist() {
        return dist;
    }

    public void setDist(double dist) {
        this.dist = dist;
    }

    @Override
    public String toString() {
        return this.name;
    }

    @Override
    public int compareTo(Vert otherV) {
        return Double.compare(this.distotherV.getDist());
    }
}
 

File Edge.java


package dijkstra.algo;

public class Edge {
    private double weight;
    private Vert startVert;
    private Vert targetVert;

    public Edge(double weightVert startVertVert targetVert) {
        this.weight = weight;
        this.startVert = startVert;
        this.targetVert = targetVert;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public Vert getStartVert() {
        return startVert;
    }

    public void setStartVert(Vert startVert) {
        this.startVert = startVert;
    }

    public Vert getTargetVert() {
        return targetVert;
    }

    public void setTargetVert(Vert targetVert) {
        this.targetVert = targetVert;
    }
}
 

File PathFinder.java


package dijkstra.algo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;

public class PathFinder {
    public void ShortestP(Vert sourceV) {
        sourceV.setDist(0);
        PriorityQueue<VertpriorityQueue = new PriorityQueue<>();
        priorityQueue.add(sourceV);
        sourceV.setVisited(true);

        while (!priorityQueue.isEmpty()) {
            Vert actualVertex = priorityQueue.poll();
            for (Edge edge : actualVertex.getList()) {
                Vert v = edge.getTargetVert();

                if (!v.Visited()) {
                    double newDistance = actualVertex.getDist()
edge.getWeight();
                    if (newDistance < v.getDist()) {
                        priorityQueue.remove(v);
                        v.setDist(newDistance);
                        v.setPr(actualVertex);
                        priorityQueue.add(v);
                    }
                }
            }
            actualVertex.setVisited(true);
        }
    }

    public List<VertgetShortestP(Vert targetVertex) {
        List<Vertpath = new ArrayList<>();
        for (Vert vertex = targetVertexvertex != nullvertex = vertex.getPr()) {
            path.add(vertex);
        }
        Collections.reverse(path);
        return path;
    }

}
 

File Dijkstra.java


package dijkstra.algo;

public class Dijkstra {
    public static void main(String[] args) {

        Vert vA = new Vert("A");
        Vert vB = new Vert("B");
        Vert vC = new Vert("C");
        Vert vD = new Vert("D");
        Vert vE = new Vert("E");

        vA.addNeighbour(new Edge(3vAvC));
        vA.addNeighbour(new Edge(5vAvB));
        vC.addNeighbour(new Edge(2vCvB));
        vC.addNeighbour(new Edge(6vCvE));
        vC.addNeighbour(new Edge(5vCvD));
        vB.addNeighbour(new Edge(4vBvC));
        vB.addNeighbour(new Edge(3vBvD));
        vB.addNeighbour(new Edge(4vBvE));
        vE.addNeighbour(new Edge(2vEvD));

        PathFinder shortestPath = new PathFinder();
        shortestPath.ShortestP(vA);
        System.out.println("Khoảng cách tối thiểu từ:");
        System.out.println("A đến B: " + vB.getDist());
        System.out.println("A đến C: " + vC.getDist());
        System.out.println("A đến D: " + vD.getDist());
        System.out.println("A đến E: " + vE.getDist());
        System.out.println("Đường đi ngắn nhất từ:");
        System.out.println("A đến B: " + shortestPath.getShortestP(vB));
        System.out.println("A đến C: " + shortestPath.getShortestP(vC));
        System.out.println("A đến D: " + shortestPath.getShortestP(vD));
        System.out.println("A đến E: " + shortestPath.getShortestP(vE));

    }
}
 


Khi chạy chương trình, kết quả ta nhận được là:


Khoảng cách tối thiểu từ:
A đến B: 5.0
A đến C: 3.0
A đến D: 8.0
A đến E: 9.0
Đương đingắn nhất từ:
A đến B: [A, B]
A đến C: [A, C]
A đến D: [A, C, D]
A đến E: [A, C, E]
 

Dijkstra là thuật toán tìm đường đi có thể nói là đơn giản nhất.

Trong đồ thị, còn nhiều giải thuật tìm đường đi khác, tuỳ vào trường hợp và mục đích sử dụng mà bạn có thể dành thời gian tìm hiểu để nâng cao trình độ của mình như:

Giải bài toán đường đi ngắn nhất nguồn đơn (simple-source): Ta sẽ tìm đường đi ngắn nhất từ đỉnh nguồn v đến tất cả các đỉnh khác. Có 2 thuật toán quan trọng: Thuật toán Dijkstra (Đối với trọng số dương) và Thuật toán Bellman-Ford (Đối với trọng số bất kỳ).

Giải bài toán đường đi ngắn nhất nguồn đích (simple-destination): Ta sẽ tìm đường đi ngắn nhất trong đồ thị có hướng từ các đỉnh đến đỉnh đích v. Ta có thể giải quyết bằng cách đảo ngược hướng của đồ thị và trở thành bài toán nguồn đơn.

Giải bài toán đường đi ngắn nhất cho mọi cặp đỉnh: Ta sẽ tìm đường đi ngắn nhất giữa 2 đỉnh bất kỳ. Có 2 thuật toán quan trọng: Thuật toán Floyd-Warshall và Thuật toán Johnson.

Như vậy, qua bài hướng dẫn này bạn đã tìm hiểu được một cách để giải bài toán tìm đường đi ngắn nhất (Shortest path).

Đến đây, bạn cũng đã được học về các thuật toán Bubble sort, Selection sort, Insertion sort, Quick sort, Merge sort, Heap sort, Binary search và cả Shortest path nữa.

Cũng đã kha khá để bạn có một nền tảng thuật toán cơ bản rồi đó.

Hãy tự luyện tập thêm để thực sự hiểu rõ và vận dụng được những thuật toán này trong các tình huống khác nhau bạn nhé.

> Đừng quên, nếu bạn muốn chinh phục ngôn ngữ Java, đặc biệt là Java Web thì đừng bỏ qua KHÓA HỌC JAVA (Full Stack) của NIIT - ICT Hà Nội nhé.

> Hoặc, nếu bạn chưa có nền tảng về lập trình mà muốn học đầy đủ hơn thì KHÓA HỌC LẬP TRÌNH VIÊN FULL STACK sẽ giúp bạn trở thành lập trình viên vững vàng trong vòng 12 tháng.


---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Học Lập trình chất lượng cao (Since 2002). Học làm Lập trình viên. Hành động ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0914939543
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
 
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp #java #php #python
Bình luận Facebook
Đăng ký tư vấn
Nhân viên gọi điện tư vấn miễn phí sau khi đăng ký
Được cập nhật các ưu đãi sớm nhất
Hotline: 0383180086
Tên không được để trống
Số điện thoại không được để trống
Email không được để trống
Hãy đăng ký để nhận những thông tin mới nhất về học bổng mới nhất tại NIIT - ICT Hà Nội
top
Đóng lại Đăng ký học tại NIIT - ICT Hà Nội
6260+ học viên đã theo học tại NIIT - ICT Hà Nội và có việc làm tốt trong ngành lập trình. Nắm lấy cơ hội ngay hôm nay!
Chọn khóa học
  • KHÓA HỌC LẬP TRÌNH FRONT END VỚI REACT.JS
  • KHÓA HỌC LẬP TRÌNH PHP WEB
  • Khóa học PHP Full stack [2023] cho người mới bắt đầu
  • Khóa học BIG DATA với Hadoop và Spark
  • Khóa học Lập trình Android tại Hà Nội
  • [Tuyển sinh 2023] Lập trình viên Quốc tế DigiNxt
  • Khóa học Tiền lương & Phúc lợi (C&B Excel) tại Hà Nội
  • LẬP TRÌNH GAME
    • Khóa học Lập trình Game Unity
  • LẬP TRÌNH WEB FRONT END
    • KHÓA HỌC PYTHON HƯỚNG ĐỐI TƯỢNG
    • KHÓA HỌC ANGULAR & TYPESCRIPT (FRONT END)
  • LẬP TRÌNH WEB BACK END
    • LẬP TRÌNH JAVA WEB VỚI FRAME WORK
    • Lập trình Web với Django
    • Lập trình PHP với Laravel Framework
  • CHƯƠNG TRÌNH ĐÀO TẠO ỨNG DỤNG CÔNG NGHỆ
    • Khóa học Tiền lương & Phúc lợi (C&B Excel) tại TP HCM
  • LẬP TRÌNH WEB FULL STACK
    • Khóa học Java Full stack (IJFD)
  • LẬP TRÌNH MOBILE
    • FRONT-END VỚI REACTJS VÀ REACT NATIVE
    • Lập trình Android Nâng cao
  • ĐÀO TẠO CHO DOANH NGHIỆP
    • KHÓA HỌC BUSINESS ANALYSIC TỪ CƠ BẢN ĐẾN NÂNG CAO 2023
    • Khóa học Magento: Làm chủ CMS TMĐT lớn nhất
    • Khóa học IOT: Xây dựng Sản phẩm IOT với Raspberry Pi
    • Khóa học Automation Testing Chuyên nghiệp
  • KHÓA HỌC DỰ ÁN
    • Học sử dụng bộ Office: Word, Excel, Power Point, Mail chuyên nghiệp
  • KHÓA HỌC KHÁC
    • VBA Excel Toàn Tập (Cơ Bản - Nâng Cao)
    • VBA Excel Nâng cao
    • Khóa học JMeter: Performance Testing
    • Khóa học Tester đạt chuẩn Quốc tế ISTQB Foundation Level
    • Khoá Học Tester đạt chuẩn quốc tế ISTQB Advanced Level
Bạn chưa chọn khóa học cần đăng ký
Tên không được để trống
Số điện thoại không được để trống
Email không được để trống
Đăng ký học thành công!
Cảm ơn bạn đã đăng ký học tại NIIT - ICT HÀ NỘI!