d) Cài đặt thuật toán
6.4.4. Thuật toán tìm một đường đi Euler trên đồ thị có hướng
Tìm một đường đi Euler cũng giống như tìm một chu trình Euler trên đồ thị có hướng. Điểm khác biệt duy nhất giữa hai thuật toán này là đỉnh xuất phát để tìm đường đi hay chu trình Euler. Đối với đồ thị Euler ta có thể xây dựng chu trình Euler tại bất kỳ đỉnh nào thuộc tập đỉnh. Đối với đồ thị là nửa Euler nhưng không là Euler, đỉnh để xây dựng đường đi Euler là m đỉnh u có deg+(u)-deg-(u) =1. Thuật toán kiểm tra một đồ thị có hướng liên thông yếu là nửa Euler nhưng không là Euler được thể hiện như Hình 5.13.
a) Biểu diễn thuật toán
Hình 5.13. Thuật toán kiểm tra đồ thị có hướng là nửa Euler.
Thuật toán Check_Semi_Euler(G=<V, E>): Begin
int s, t, dem1 = 0, dem2 = 0;
for each uV do //duyệt trên tập các đỉnh trong V
if ( deg+(u)- deg-(u)==1){ //tìm hiệu bán bậc của đỉnh u
dem1++; s =u; //ghi nhận đỉnh u có deg+(u)- deg-(u)==1
endif;
else if (deg-(u)- deg+(u)==1){
dem2++; t =u; //ghi nhận đỉnh u có deg-(u)- deg+(u)==1
endesle; endfor;
if (dem1==1 && dem2==1) //nếu điều này xảy ra
return true;//kết luận đồ thị là nửa euler
endif;
return false;//kết luận đồ thị không là nửa Euler
NGUYỄN DUY PHƯƠNG 198
b) Độ phức tạp thuật toán
Bạn đọc tự tìm hiểu và chứng minh độ phức tạp thuật toán trong các tài liệu tham khảo liên quan.
c) Kiểm nghiệm thuật toán
Bạn đọc tự tìm hiểu và kiểm nghiệm thuật toán trong các tài liệu tham khảo liên quan.
d) Cài đặt thuật toán
Thuật toán được cài đặt cho đồ thị vô hướng liên thông yếu được biểu diễn dưới dạng danh sách cạnh sau đó chuyển đổi biểu diễn thành dánh sách kề như dưới đây.
#include<iostream> #include <list> #include <fstream> #include <iomanip> #include <stack> using namespace std;
class Graph{ //định nghĩa lớp Graph
private:
int V; // tập đỉnh của đồ thị
list <int> *adj; // con trỏ đến mảng các danh sách kề
public:
Graph(int V); // constructor của lớp
void addEdge(int v, int w); // thêm một cạnh của đồ thị vào danh sách kề
int NegativeDeg(int u); //tìm bán đỉnh bậc vào của đỉnh u
void SemiEuler(void);//thuật toán Hình 5.13
void EulerCycle(int u); //thuật toán Hình 5.10
};
Graph::Graph(int V){//Constructor: khởi tạo đồ thị ban đầu
this->V = V;//thiết lập tập đỉnh
adj = new list<int>[V];//thiết lập V danh sách kề
}
void Graph::addEdge(int v, int w){
adj[v].push_back(w); //thêm đỉnh w vào danh sách kề đỉnh v. }
int Graph::NegativeDeg(int u){
int dem =0; list <int>::iterator v; for(int s=1; s<V; s++){
NGUYỄN DUY PHƯƠNG 199 for(v=adj[s].begin(); v != adj[s].end(); ++v){ for(v=adj[s].begin(); v != adj[s].end(); ++v){ if(u==(*v)) dem++; } } return dem; } void Graph::SemiEuler(){
int deg_u, deg_u1; //bán bậc ra và bán bậc vào của đỉnh
int dem1=0, dem2=0;//số lượng định thỏa mãn đồ thị semi-Euler
int s, t; //ghi nhận đỉnh có deg+(u)-deg-(u) =1 hoặc deg-(u)-deg+(u) =1 for(int u=1; u<V; u++){ //duyệt trên tập đỉnh V
deg_u = adj[u].size(); //tìm bán bậc ra của đỉnh
deg_u1 = NegativeDeg(u);//tìm bán bậc vào của đỉnh
if (deg_u - deg_u1 ==1) { //nếu deg+(u)-deg-(u) =1 dem1 ++; s = u; //ghi nhận số đỉnh và s = u
}
else if (deg_u1 - deg_u ==1){ //nếu deg-(u)-deg+(u) =1 dem2++; t = u; //ghi nhận số đỉnh và t =u
} }
if (dem1==1 && dem2==1) //nếu điều này xảy ra
EulerCycle(s); //ta làm giống như chu trình Euler
else cout<<"\n Đồ thị không là nửa Euler"; }
void Graph::EulerCycle(int u){//tìm chu trình Euler bắt đầu tại đỉnh u
//Bước 1 (Khởi tạo):
stack <int> stack; //tạo stack rỗng
int *CE = new int[V], k=0;//tạo mảng CE rỗng
stack.push(u); //đưa u vào ngăn xếp
//Bước 2 (Lặp):
while(!stack.empty()){//lặp đến khi stack rỗng
int s = stack.top(); //lấy s là đỉnh đầu ngăn xếp if(!adj[s].empty()){ //nếu ke(s) khác rỗng
int t= adj[s].front();//t là đỉnh đầu tiên trong ke(s) stack.push(t);//đưa t vào ngăn xếp
adj[s].remove(t);//loại t khỏi ke(s) adj[t].remove(s); //loại s khỏi ke(t)
NGUYỄN DUY PHƯƠNG 200 } }
else {//nếu ke(s) là rỗng
stack.pop();//loại s khỏi stack
CE[k]= s; k++;//đưa s sang CE
} }
//Bước 3 (Trả lại kết quả): cout<<"\n Kết quả:";
for(int t=k-1;t>=0; t--){//lật ngược lại CE
cout<<EC[t]<<"-"; }
cout<<endl; }
6.5. Bài toán xây dựng cây khung của đồ thị
Trong mục này ta đề cập đến một loại đồ thị đơn giản nhất đó là cây. Cây được ứng dụng rộng rãi trong nhiều lĩnh vực khác nhau của tin học như tổ chức các thư mục, lưu trữ dữ liệu, biểu diễn tính toán, biểu diễn quyết định và tổ chức truyền tin. Ta có thể tiếp cận cây bằng lý thuyết đồ thị như dưới đây.
Định nghĩa 1. Ta gọi cây là đồ thị vô hướng liên thông không có chu trình. Đồ thị không liên thông được gọi là rừng. Như vậy, rừng là đồ thị mà mỗi thành phần liên thông của nó là một cây.
Định lý 1. Giả sử T= <V, E> là đồ thị vô hướng n đỉnh. Khi đó những khẳng định sau là tương đương
a) T là một cây.
b) T không có chu trình và có n-1 cạnh. c) T liên thông và có đúng n-1 cạnh.
d) T liên thông và mỗi cạnh của nó đều là cầu.
e) Giữa hai đỉnh bất kỳ của T được nối với nhau bởi đúng một đường đi đơn. f) T không chứa chu trình nhưng hễ cứ thêm vào nó một cạnh ta thu được đúng
một chu trình.
Định nghĩa 2. Cho G =<V, E> là đồ thị vô hướng liên thông. Ta gọi đồ thị con H=<V,T> là một cây khung của G nếu H là một cây và TE.
Tiếp cận cây bằng lý thuyết đồ thị, người ta qua tâm đến hai bài toán cơ bản về cây:
Bài toán 1. Cho đồ thị vô hướng G =<V,E>. Hãy xây dựng một cây khung của đồ thị bắt đầu tại đỉnh uV.
Bài toán 2. Cho đồ thị vô hướng G =<V,E> có trọng số. Hãy xây dựng cây khung có độ dài nhỏ nhất.