TÌM KIẾM THEO CHIỀU SÂU TRÊN ĐỒ THỊ CÓ HƯỚNG ĐƯỢC BIỂU DIỄN BỞI DANH SÁCH KỀ VÀ ỨNG DỤNG VÀO SẮP XẾP TÔPÔ
Trang 1Viện Công nghệ Thông tin và Truyền thông
BÀI TẬP LỚN CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
ĐỀ TÀI: TÌM KIẾM THEO CHIỀU SÂU TRÊN ĐỒ THỊ CÓ HƯỚNG ĐƯỢC BIỂU DIỄN BỞI DANH SÁCH KỀ VÀ ỨNG DỤNG VÀO SẮP XẾP TÔPÔ.
NHÓM 5
Giáo viên hướng dẫn: PGS Nguyễn Đức Nghĩa
Trang 2NỘI DUNG
A-Định nghĩa ADT
I- Đồ thị
II-Danh sách kề
B- Bài toán tìm kiếm theo chiều sâu trên đồ thị có hướng biểu diễn bởi danh sách kề
I-Phát biểu bài toán
II-Ứng dụng bài toán tìm kiếm theo chiều sâu
C- Ứng dụng bài toán tìm kiếm theo chiều sâu trên đồ thị có hướng được biểu diễn bởi danh sách kề vào bài toán sắp xếp tôpô
Trang 3A-Định nghĩa ADT
I-Đồ thị
1.Khái niệm đồ thị
Đồ thị G là cấu trúc rời rạc bao gồm hai tập
- Tập đỉnh V(G) là tập hữu hạn khác rỗng
- Tập cạnh E(G) là tập hữu hạn có thể là tập rỗng các cặp (u,v) trong đó u,v V
Kí hiệu G=(V,E)
2 Các loại đồ thị
Phụ thuộc vào kiểu của cạnh nối và số lượng cạnh nối giữa hai đỉnh mà ta phân biệt các loại đồ thị khác nhau
2.1 Đồ thị vô hướng
Đơn (đa) đồ thị vô hướng G = (V,E) là cặp gồm:
- Tập đỉnh V là tập hữu hạn phần tử, các phần tử gọi là các đỉnh
- Tập cạnh E là tập (họ) các bộ không có thứ tự dạng
(u, v), u, v V, u≠v
2.2 Đồ thị có hướng
Đơn (đa) đồ thị có hướng G = (V,E) là cặp gồm:
Tập đỉnh V là tập hữu hạn phần tử, các phần tử gọi là các đỉnh
Tập cung E là tập (họ) các bộ có thứ tự dạng
(u, v), u, v V, u≠v
3.Biểu diễn đồ thị
3.1.Biểu diễn đồ thị bằng ma trận kề
3.2.Biểu diễn đồ thị bằng danh sách kề
3.3.Biểu diễn đồ thị bằng danh sách cạnh
4.Các thao tác cơ bản thường gặp khi xử lý đồ thị
- incidentEdge(v)- duyệt các đỉnh kề của đỉnh v
- areAdjacent(v,w)- trả lại giá trị true khi và chỉ khi v và w là kề nhau
- insertVertex(z)-bổ sung đỉnh z
- insertEdge(v)-bổ sung cạnh e=(v,w)
- removeVertex(v)-loại bỏ đỉnh v
- removeEdge(e)-loại bỏ cạnh e
5 Các thuật toán duyệt đồ thị
5.1.Thuật toán tìm kiếm theo chiều rộng (BFS)
5.2.Thuật toán tìm kiếm theo chiều sâu (DFS)
Trang 4II-Danh sách kề
Với mỗi đỉnh v cất giữ danh sách các đỉnh kề với nó
-Là mảng Ke gồm có | V| danh sách
-Mỗi đỉnh có một danh sách
-Với mỗi u V, Ke[u] bao gồm tất cả các đỉnh kề của u
Ví dụ
a) Biểu diễn đồ thị vô hướng G=(V,E) sử dung danh sách kề
v u u z v x
w w v
y
u v w x y z t
Bộ nhớ đòi hỏi = a|V|+2b|E|
b) Biểu diễn đồ thị có hướng G=(V,E) sử dụng danh sách kề
b e b
b f
c a
b c d e
Bộ nhớ đòi hỏi = a|V|+b|E|
Có thể sử dụng mô tả trên C sau đây để biểu diễn danh sách kề
#define MAX_ VERTICES 500
Typedef struct node*node_ptr;
Typedef struct node
{
int vertex;
node_ptr link;
} node;
node_ptr graph[MAX_VERTICES];
Trang 5B-Bài toán tìm kiếm theo chiều sâu trên đồ thị có hướng biểu diễn bởi danh sách kề
I-Phát biểu bài toán
1.Ý tưởng chung cho các thuật toán tìm kiếm
-Trong quá trình thực hịện thuật toán , ở mỗi đỉnh có một trong ba
trạng thái sau:
+ Chưa thăm thì thể hiện bằng màu trắng
+ Đã thăm (nhưng chưa duyệt xong) thì thể hiện bằng màu xám
+ Đã duyệt xong thể hiện bằng màu đen
2 Thuật toán tìm kiếm theo chiều sâu (depth first search)
Input: G=(V,E) – đồ thị vô hướng hoặc có hướng
Output: Với mỗi v V
d[v]= thời điểm bắt đầu thăm(v chuyển từ màu trắng sang xám)
f[v]= thời điểm kết thúc thăm(v chuyển từ màu xám sang đen)
π[v]: từ đỉnh đó ta đến thăm đỉnh v
Rừng tìm kiếm theo chiều sâu(gọi tắt là rừng DFS – Forest of depth-first trees):
Gπ=(V,Eπ),
Eπ={(π[v] , v): vV và π[v] ≠ null}
2.1.thuật toán tìm kiếm theo chiều sâu bắt đầu từ đỉnh u
DFS-Visit(u)
1.color[u] ← GRAY
2.time ← time+1
3.d[u] ← time
4.for v Adj[u]
5 do if color[v]=WHITE
6 then π [v] ←u
7 DFS-Visit(v)
8.color[u]← BLACK
9.f[u] ← time ← time+1
2.2.thuật toán theo chiều sâu trên đồ thị G
DFS(G)
1 for u V[G]
2 do color[u] ← white
3 π[u] ← NULL
4 Time ← 0
5 for u V[G]
Trang 66 do if color[u] = white
7 then DFS-Visit(u)
Quá trình thực hiện thuật toán được trình diễn trong các hình minh hoạ sau
u v w
1/ / /
/ / /
x y z DFS(u): Thăm đỉnh u DFS( n) u v w 1/ 2/ /
/ / /
x y z
DFS(v): thăm đỉnh v DFS(u) DFS(v) u v w 1/ 2/ /
/ 3/ /
x y z
DFS(y): thăm đỉnh y DFS( u)
DFS(v)
DFS(y)
u v w
DFS(y): thăm đỉnh y DFS( u)
Trang 71/ 2/ /
4/ 3/ /
x y z
DFS(v)
DFS(y)
DFS(x)
u v w
1/ 2/ /
4/5 3/ /
x y z
Kết thúc thăm đỉnh x DFS( u)
DFS(v)
DFS(y)
DFS(x)
Trang 8u v w
1/ 2/ /
4/5 3/6 /
x y z
Kết thúc thăm đỉnh y DFS( u)
DFS(v)
DFS(y)
DFS(x)
u v w
1/ 2/7 /
4/5 3/6 /
x y z
Kết thúc thăm đỉnh v DFS( u)
DFS(v)
DFS(y)
DFS(x)
u v w
1/8 2/7 /
4/5 3/6 /
Kết thúc thăm đỉnh u DFS( u)
DFS(v)
Trang 9
x y z DFS(y)
DFS(x)
u v w
1/8 2/7 9/
4/5 3/6 /
x y z
DFS(w): Thăm đỉnh w
u v w
1/8 2/7 9/
4/5 3/6 10/
x y z
DFS(z): Thăm đỉnh z
u v w
1/8 2/7 9/
4/5 3/6 10/11
x y z
Kết thúc thăm đỉnh z
u v w
Trang 101/8 2/7 9/12
4/5 3/6 10/11
x y z
Kết thúc thăm đỉnh w
Kết thúc DFS(G)
Rừng tìm kiếm gồm 2 cây: cây DFS(u) và cây DFS(w)
u v w
1/8 2/7 9/12
4/5 3/6 10/11
x y z
cây DFS(u) cây DFS(w)
3.Các tính chất của DFS
- Rừng DFS là phụ thuộc vào thứ tự các đỉnh được duỵêt trong vòng lặp for duyệt
đỉnh trong DFS(G) và DFS_Visit(u)
- Để gỡ đệ quy có thể sử dụng ngăn xếp Và có thể nói, điểm khác biệt cơ bản của DFS với BFS là các đỉnh đang được thăm trong DFS được cất giữ vào ngăn xếp thay vì hàng đợi trong BFS
- Các khoảng thời gian thăm [d[v],f[v]] của các đỉnh có cấu trúc lồng nhau
(parenthesis structure)
4.Cấu trúc lồng nhau (parenthesis structure)
Định lý: Với mọi u,v chỉ có thể xảy ra một trong các tình huống sau:
Trang 111.d[u] < f[u] < d[v] < f[v] hoặc d[v] < f[v] < d[u] < f[u] ( nghĩa là hai khoảng thời gian thăm của u và v là dời nhau) và khi đó u và v là không có quan hệ tổ
tiên-hậu duệ
2.d[u] < d[v] < f[v] < f[u] ( nghĩa là khoảng thời gian thăm của v là lồng trong khoảng thời gian thăm của u) và khi đó v là hậu duệ của u.
3.d[v] < d[u] < f[u] < f[v] (nghĩa là khoảng thời gian thăm của u là lồng trong khoảng thời gian thăm của v) và khi đó u là hậu duệ của v.
Ví dụ: Xét việc thực hiện thuật toán tìm kiếm theo chiều sâu trên đồ thị cho trong hình vẽ dưới đây
2/15 3/14 4/5
a b c DFS(s) Tree
1/16
s
8/9 Time stamps:
t d[]/t[]
d e f
11/12 6/13 7/10
Hình vẽ sau đây minh hoạ cho định lý về cấu trúc lồng nhau:
s
c
a
b e f t
d
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Trang 12(s (a (b (c c) (e (f (t t) f ) (d d) e) b) a) s)
5.Độ phức tạp của DFS
-Thuật toán thăm mỗi đỉnh v V đúng một lần (|V|)
- Với mỗi đỉnh v duyệt qua tất cả các đỉnh kề, với mỗi đỉnh kề thực hiện thao tác
với thời gian hằng số Do đó việc duyệt qua tất cả các đỉnh mất thời gian
vV |neighbors[v]| = (|E|)
- Tổng cộng (|V|)+ (|E|) = (|V|+|E|), hay (|V|2)
Như vậy, DFS có cùng độ phức tạp như BFS
6.Phân loại cạnh
DFS tạo ra một cách phân loại các cạnh của đồ thị đã cho:
- Cạnh của cây(Tree edge): là cạnh mà theo đó từ một đỉnh ta đến thăm một đỉnh mới
- Cạnh ngược (Back edge): đi từ con cháu (descendent) đến tổ tiên(ancestor)
- Cạnh tới (Forward edge): đi từ tổ tiên đến hậu duệ
- Cạnh vòng (Cross edge): cạnh nối hai đỉnh ko có quan hệ họ hàng
Để nhận biết cạnh (u,v) thuộc loại cạnh nào, ta dựa vào màu của đỉnh v khi lần đầu trên cạnh (u,v) được khảo sát Cụ thể, nếu màu của đỉnh v làp
- Trắng thì (u,v) là cạnh của cây
- Xám thì (u,v) là cạnh ngược
- Đen thì (u,v) là cạnh tới hoặc vòng Trong trường hợp này để phân biệt cạnh tới và cạnh vòng ta cần xét xem hai đỉnh u và v có quan hệ họ hàng hay
không nhờ sử dụng kết quả của định lý về cấu trúc lồng nhau
Nhiều ứng dụng của DFS chỉ đòi hỏi nhận biết cạnh của cây và cạnh ngược, Hơn nữa, đối với đồ thị vô hướng, DFS chỉ sản sinh ra hai loại cạnh này như chỉ ra trong khẳng định của định lý sau đây
Định lý: nếu G là đồ thị vô hướng, thì DFS chỉ sản sinh ra cạnh của cây và cạnh
ngược
II-Ứng dụng của thuật toán tìm kiếm theo chiều sâu (DFS)
Trang 13 Tính liên thông của đồ thị
Tìm đường đi từ s đến t
Phát hiện chu trình
Kiểm tra tính liên thông mạnh
Định hướng đồ thị
C- Ứng dụng thuật toán tìm kiếm theo chiều sâu trên đồ thị
có hướng sử dụng danh sách kề vào bài toán sắp xếp tôpô I- Phát biểu bài toán
Bài toán đặt ra là : Cho đồ thị có hướng không có chu trình G=(V,E) hãy tìm cách sắp xếp các đỉnh sao cho nếu có cạnh (u,v) thì u phải đi trước v trong thứ tự đó (nói
cách khác, cần tìm cách đánh số các đỉnh của đồ thị sao cho mỗi cung của đồ thị luôn hướng từ đỉnh có chỉ số nhỏ hơn đến đỉnh có chỉ số lớn hơn)
II-Thuật toán sắp xếp tôpô
Thuật toán có thể viết vắn tắt như sau: Thực hiện DFS(G), khi mỗi đỉnh được duyệt
xong ta đưa nó vào đầu danh sách lien kết (điều đó có nghĩa là những đỉnh kết thúc thăm càng muộn sẽ càng ở đầu danh sách hơn) Danh sách lien kết thu được khi kết
thúc DFS(G) sẽ cho ta thứ tự cần tìm.
TopoSort(G)
1 for u V color[u]=white; //khởi tạo
2 L = new(linked_list); //khởi tạo danh sách liên kết rỗng
3 for u V
4 if (color[u] == white) TopVisit(u);
5 return L // L cho thứ tự cần tìm
TopVisit(u){
1 color[u] = gray; //bắt đầu tìm kiếm từ u
2 for v Adj(u) //đánh dấu u đã thăm
3 if (color[v]==white)TopVisit(v);
4 Nạp u vào đầu danh sách L // u đã duyệt xong
Cài đặt trên C
Trang 14void DFS(int vertex)
{
//printf(" %d ", vertex);
pListIt p;
color[vertex] = GRAY; // visited
for (p = adjacencyList[vertex]; p != NULL; p = p -> p_next)
if (!color[p -> value]) {
ancient[p->value] = vertex;
distance[p->value] = distance[vertex] +1;
p->type = TREE_EDGE;
DFS(p -> value);
} else {
if( color[p->value] == GRAY) {
p->type = BACK_EDGE;
isCircle = true;
// print the circle printf(" \n Circle: ");
int l = vertex;
while(l != p->value) {
printf( " %d ", l);
l = ancient[l];
} printf( " %d ", l);
} else
if (distance[p->value] > distance[vertex]) {
p->type = AHEAD_EDGE ; }
else
p->type = CROSS_EDGE ;
} // add to the topological order ++atPos;
Trang 15topological_order[n-atPos] = vertex;
color[vertex] = BLACK;
}
A B D
1/
C E
Linked list:
A B D
1/
2/
C E Linked list:
A B D
1/
2 /3
C E
Linked list:
2/3
E
A B D
1/4
2/3
C E
Linked list:
1/4 2/3
D E
Trang 16A B D
5/ 1/4
2 /3
C E
Linked list:
1/4 2/3
D E
A B D
5/ 1/4
6/ 2/3
C E
Linked list:
1/4 2/3
D E
A B D
5/ 1/4
6/7 2 /3
C E
Linked list:
6/7 1/4 2/3
C D E
A B D
5/8 1/4
6/7 2/3
C E Linked list:
5/8 6/7 1/4 2/3
B C D E
Trang 17A B D
9/ 5/8 1/4
6/7 2 /3
C E
Linked list:
5/8 6/7 1/4 2/3
B C D E
A B D 9/10 5/8 1/4
6/7 2/3
C E Linked list:
9/10 5/8 6/7 1/4 2/3
A B C D E
III-Đánh giá độ phức tạp của thuật toán
Thời gian tính của TopoSort(G) là O(|V|+|E|)