Các phương pháp biểu diễn đồ thị

Một phần của tài liệu [Giáo trình] Phân tích thiết kế thuật toán và đánh giá độ phức tạp của giải thuật - ĐH Sư phạm Hà Nội (Trang 78 - 81)

1.1. Biểu diễn đồ thị bằng ma trận kề

Cho đơn đồ thị đồ thị G = (V, E), trong đó |V| =n, có thể đánh số thứ tự các đỉnh từ 1 đến n và đồng nhất mỗi đỉnh với số thứ tự của nó. Ma trận kề của G là ma trận vuông A = (aij)n x n trong đó: ⎩ ⎨ ⎧ ∉ ∈ = E j i if E j i if aij ) , ( 0 ) , ( 1 Nhận xét

- Ma trận kề của đồ thị yêu cầu O(V2) bộ nhớ, không phụ thuộc vào số cạnh trong đồ thị.

- Ma trận kề của đồ thị vô hướng thì đối xứng, do đó chỉ cần lưu phần tam giác trên (hoặc dưới) của ma trận, do đó bộ nhớ được giảm một nửa.

- Ưu điểm của ma trận kề là trực quan, dễ cài đặt trên máy, dễ kiểm tra xem hai đỉnh u, v của đồ thị có kề nhau hay không

- Hạn chế của ma trận kề là tốn bộ nhớ, luôn tốn một ma trận n x n cho dù chỉ có rất ít cạnh.

Biểu diễn ma trận kề trên Pascal

var a : array[1..n, 1..n] of boolean

1.2. Biểu diễn đồ thị bằng danh sách cạnh

Cho đồ thị G = (V, E), giả sử có n đỉnh và m cạnh, tức n = |V|, m = |E|. Khi đó G có thể cho bởi danh sách m cạnh trong E.

Nhận xét:

- Ưu điểm của biểu diễn đồ thị bằng danh sách cạnh là thích hợp đối với đồ thị thưa, thích hợp đối với các thuật toán đồ thị mà cần duyệt trên các cạnh (ví dụ thuật toán Kruscal)

- Hạn chế của danh sách cạnh là không thích hợp khi cần duyệt đồ thị theo đỉnh.

Biểu diễn danh sách cạnh trên Pascal

type Canh = record x, y : integer; end; var e : array[1..m] of Canh;

1.3. Biểu diễn đồ thị bằng danh sách kề

Biểu diễn đồ thị bằng danh sách kề (adjacency list) khắc phục được những hạn chế của ma trận kề và danh sách cạnh; tuy nhiên nó có hạn chế trong việc kiểm tra một cặp đỉnh có phải là một cạnh hay không (vì phải duyệt trên tập các đỉnh kề). Trong cách biểu diễn này, với mỗi đỉnh v của đồ thị, ta cho tương ứng với nó một danh sách các đỉnh kề với đỉnh v.

Cho đồ thị có hướng G = (V, E), V gồm n đỉnh và E gồm cung. Có hai cách cài đặt danh sách kề:

Danh sách trỏ trước: Với mỗi đỉnh u, lưu trữ một danh sách adj[u] chứa các đỉnh kề với đỉnh u: adj[u] = {v : (u, v) ∈ E}

Danh sách trỏ sau: Với mỗi đỉnh u, lưu trữ một danh sách adj[u] chứa các đỉnh v mà u kề với v: adj[u] = {v : (v, u) ∈ E}

a) Biu din danh sách k bng mng

Dùng mảng adj[1..n] chứa các đỉnh, mảng được chia thành n đoạn, đoạn thứ u trong mảng lưu các đỉnh kề với đỉnh u.

Để biết một đoạn nằm từ chỉ số nào đến chỉ số nào, dùng thêm một mảng head[1...n + 1] để đánh dấu vị trí phân đoạn: head[u] bằng chỉ số (vị trí) cuối cùng của đoạn ngay trước đoạn u, qui ước head[n+1] = 2m, với m là số cung của đồ thị (lưu ý việc biểu diễn danh sách kề bằng mảng sẽ nhân đôi số cung của đồ thị).

Vậy đoạn thứ u bắt đầu từ chỉ số head[u] + 1 cho đến head[u+1]. Nói cách khác, các đỉnh kề của u thuộc phân đoạn từ adj[head[u]+1] đến adj[head[u+1]]. Ví dụ như hình dưới đây, các đỉnh kề của u = 2 thuộc phân đoạn từ adj[head[2]+1] đến adj[head[3]], cụ thể là từ adj[3] đến adj[6], phân đoạn này tương ứng với các đỉnh 1, 3, 4, 5.

Biểu diễn danh sách kề bằng mảng trên Pascal

var adj : array[1..2*m] of integer; head : array[1..n + 1 ] of integer;

Đoạn trình chuyển đổi từ ma trận kề sang danh sách kề

1. head[n +1] := 2*m;

2. for i := n downto 1 do begin 3. head[i] := head[i+1]; 1 2 3 4 5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 adj 2 4 1 3 4 5 2 5 1 2 5 2 3 4 1 2 3 4 5 1 2 3 4 5 6 head 0 2 6 8 11 14

4. for j := n downto 1 do 5. if a[i, j] then begin

6. adj[head[i]] := j; 7. head[i] := head[i] - 1; (adsbygoogle = window.adsbygoogle || []).push({});

8. end;

9. end;

Để hiểu được thuật toán trên, ta mô phỏng vài thao tác ban đầu dựa vào ví dụ đã nêu. Ta có head[6] = 14. Với i = 5, ban đầu head[5] = 14. Ta thấy câu lệnh 7 sẽ được thực hiện 3 lần, bằng độ dài của phân đoạn 5, tức là cuối cùng head[5] = 12. Cùng với câu lệnh 7, câu lệnh 6 sẽ lần lượt gán adj[14] = 4 (vì tồn tại a[5,4]), adj[13] = 3 (vì tồn tại a[5, 3]), adj[12] = 2 (vì tồn tại a[5,2]). Vậy là xong phân đoạn 5.

Với i = 4 tiếp theo, câu lệnh 3 khởi gán head[4] = 11, rồi việc tính lại head và tính mảng adj thuộc phân đoạn 4 lại diễn ra tương tự. Cuối cùng head[4] giảm 3 lần còn 8, phân đoạn 4 được tính với adj[11, 10, 9] = [5, 2, 1].

Đoạn trình chuyển đổi từ danh sách kề sang ma trận kề

for i := 1 to n do

for j :=1 to n do a[i,j] := false; for u :=1 to n do

for k := head[u] + 1 to head[u+1] do a[u, adj[k]] := true;

b) Biu din danh sách k bng danh sách móc ni

Trong biểu diễn này, ta cho mỗi đỉnh u của đồ thị tương ứng với list[u] là chốt của một danh sách móc nối (hay danh sách liên kết) gồm các đỉnh kề u.

Nếu G là đồ thị có hướng, tổng chiều dài của tất cả các danh sách kề là |E|, do có cạnh nối (u,v) chỉ khi đỉnh v xuất hiện trong adj[u]. Nếu G là một đồ thị vô hướng, tổng độ dài của tất cả các danh sách kề là 2|E|, do tồn tại cạnh vô hướng (u,v) chỉ khi đỉnh u xuất hiện trong danh sách các đỉnh kề của v và đỉnh v xuất hiện trong danh sách các đỉnh kề của u. Dù đồ thị có hướng hay vô

1 2 34 5 4 5 list[1] 2 4 list[2] 1 3 4 list[3] 2 5 list[4] 1 2 list[5] 2 3 5 5 4

hướng, việc biểu diễn qua danh sách kề chiếm một vùng bộ nhớ có kích thước là O(max(V,E)) = O(V + E).

Các danh sách các đỉnh kề có thể dễ dàng được sử dụng để biểu diễn các đồ thị có trọng số. Đó là đồ thị mà mỗi cạnh đều có một trọng số riêng, được tính bằng hàm trọng số w: E → R.

1.4. Biểu diễn đồ thị bằng danh sách liên thuộc

Cho đồ thị G = (V, E). Với cạnh e = (u, v) ∈ E, ta nói u và v là hai đỉnh kề nhau; cạnh e là cạnh liên thuộc (incdent) với đỉnh u và đỉnh v.

Danh sách liên thuộc là mở rộng của danh sách kề. Nếu như trong danh sách kề, mỗi đỉnh được cho tương ứng với một danh sách các đỉnh kề, thì trong danh sách liên thuộc, mỗi đỉnh được cho bởi danh sách các cạnh liên thuộc.

Một phần của tài liệu [Giáo trình] Phân tích thiết kế thuật toán và đánh giá độ phức tạp của giải thuật - ĐH Sư phạm Hà Nội (Trang 78 - 81)