Tổ chức dữ liệu cho thuật toán

Một phần của tài liệu Các thuật toán về đường đi và chu trình euler và ứng dụng (Trang 39 - 43)

Theo phương án cài đặt trên, mỗi cạnh được duyệt 1 lần vào lúc tiến và có thể duyệt thêm 1 lần vào lúc lùi, như vậy sẽ duyệt tối đa 2m lần. Nhận thấy hai thủ tục Tìm đỉnh tiếp đòi hỏi duyệt một dòng của ma trận kề do đó có độ phức tạp O(n). Hơn thế nữa,

Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/

thủ tục Lui còn duyệt gần như toàn bộ ma trận kề, nghĩa là có độ phức tạp O(n2). Do thủ tục này nằm trong vòng lặp của thủ tục chính Euler, nên tổng hợp lại, độ phức tạp của phương án cài đặt này là O(m.n2).

Chúng ta sẽ tập trung cải tiến thủ tục Tiến và Lui. Muốn vậy ta cố gắng vận dụng các phép truy nhập trực tiếp tới các đỉnh liên thông, tức là đỉnh kề với một đỉnh cho trước. Như trên đã nói, muốn tìm đỉnh j nào kề với đỉnh i cho trước ta phải duyệt dòng c[i] trong ma trận kề c để xác định chỉ số j thoả điều kiện c[i][j] > 0, tức là ta phải bỏ qua tối đa n-1 giá trị c[i][j] = 0 trong quá trình duyệt từ 1 đến n. Nếu với mỗi đỉnh i ta có cách nào nhận được ngay đỉnh j kề với i mà không cần duyệt tuần tự thì độ phức tạp cho thủ tục tìm đỉnh j kề với đỉnh i sẽ giảm còn O(1). Giải pháp ở đây là: ta đặt tất cả đỉnh kề với đỉnh i lên đầu dòng cần duyệt. Khi đó, chỉ cần lấy phần tử đầu tiên là được.

Tuy nhiên ta sẽ gặp 2 khó khăn. Thứ nhất, sau khi chọn được đỉnh j kề với đỉnh i ta đưa cạnh (i,j) vào đường đi thì ta cần xoá cạnh đó khỏi đồ thị để các bước sau ta không lấy lại. Thứ hai, vì đồ thị là vô hướng nên ta phải xoá đồng thời hai cạnh (i,j) và (j,i), tức là xoá trên 2 dòng, dòng i và dòng j. Tổ chức danh sách (DS) sẽ giải quyết trọn vẹn hai khó khăn này. Ta sẽ thay mỗi dòng c[i] của ma trận kề c bằng một DS cc[i] chứa các đỉnh kề với đỉnh i. Khi đọc dữ liệu, gặp cạnh (i,j) ta sẽ nạp đồng thời đỉnh j vào DS cc[i] và đỉnh i vào DS c[j]. Thí dụ, cạnh (5, 1) hai dòng c[1] và c[5]:

c[1] = (*, *, *, *, 1, *); c[5] = (1, *, *, *, *, *); dấu * biểu diễn một giá trị nào đó.

Nếu dùng DS thì ta có biểu diễn sau:

cc[1] (5, u) ; cc[5] (1, v),

trong đó cho biết DS là hai chiều: mỗi phần tử có hai con trỏ, một trỏ về phần tử kề trước và một trỏ về phần tử kề sau; giá trị 5 tại DS cc[1] cho biết đỉnh kề với đỉnh 1 chính là đỉnh 5, tương tự giá trị 1 tại DS cc[5] cho biết đỉnh kề với đỉnh 5 chính là đỉnh 1; u là con trỏ tới phần tử (1,v) và v là con trỏ tới phần tử (5,u). [3]

Tại sao cần các con trỏ u và v? Khi cần xoá cạnh (i,j) ta phải xoá đồng thời 2 cạnh (i,j) và (j,i), do đó ta phải có cầu nối từ đỉnh i tới đỉnh j và ngược lại. Tổ chức này cho

Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/

phép ta "nhảy" trực tiếp từ một nút biểu diễn đỉnh i sang nút biểu diễn đỉnh j và ngược lại.

Tại sao cần dùng danh sách 2 chiều? Khi cần xoá một phần tử nào đó của DS ta cần tháo phần tử đó khỏi DS rồi mới xoá. Muốn tháo một phần tử t khỏi DS ta cần truy nhập tới phần tử sát trước và sát sau của t. DS tuyến tính 1 chiều đòi hỏi ta phải duyệt từ đầu DS mới tìm được phần tử sát trước t. Tổ chức 2 chiều cho phép ta đứng tại t

"nhảy" ngay đến phần tử sát trước.

Tóm lại, với DS 2 chiều ta quản lí được các đỉnh kề và như vậy là ta quản lí được các cạnh.

Mỗi phần tử của DS cc[i] được khai báo với 4 trường dữ liệu:

Trường Vert: chứa đỉnh kề j với đỉnh i;

Trường Pred: trỏ tới phần tử sát trước trong cùng DS;

Trường Next: trỏ tới phần tử sát sau trong cùng DS;

Trường Adj: trỏ tới phần tử chứa đỉnh i trong danh sách cc[j].

typedef struct Elem {

int Vert; // vertex (dinh) Elem * Pred; // tro truoc Elem * Next; // Tro sau

Elem * Adj; // Tro toi dinh thuoc cung canh };

Mỗi khi đọc cạnh (i,j) ta sinh ra 2 phần tử: phần tử chứa đỉnh j được gắn vào đầu DS cc[i], phần tử chứa đỉnh i được gắn vào đầu DS cc[j].

void Add(int i, int j) {

Elem *p = NewElem(j,NULL,cc[i]);

Elem *q = NewElem(i,p,cc[j]);

cc[i]->Pred = p;

cc[j]->Pred = q;

cc[i] = p; cc[j] = q;

p->Adj = q;

}

Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/

Mỗi khi chấp nhận cạnh (i,j) vào chu trình Euler ta xoá đồng thời hai phần tử: phần tử chứa đỉnh j trong DS cc[i] và phần tử chứa đỉnh i trong DS cc[j]. Chú ý rằng, theo tiến độ của thuật toán, phần tử chứa đỉnh j trong DS cc[i] luôn luôn là phần tử đầu của cc[i], nhưng phần tử chứa đỉnh i trong DS cc[j] có thể nằm giữa DS. Ngoài con trỏ sau, con trỏ trước được sử dụng trong tình huống này.

// Xoa phan tu dau danh sach i // va phan tu ke Adj

void Del(int i){

Elem *p = cc[i]; // dau trai Elem *q = p->Adj; // dau phai int j = p->Vert;

if (j == 0) return; /* Gap phan tu dem, danh sach cc[i] rong */

cc[i] = p->Next;

cc[i]->Pred = cc[i];

delete p;

if (q->Pred == q) { // q la dau danh sach j cc[j] = q->Next;

cc[j]->Pred = cc[j];

delete q;

return;

}

// q o giua danh sach p = q->Pred; // p truoc q

p->Next = q->Next; // thao q khoi danh sach cc[j]

(q->Next)->Pred = p;

delete q;

}

Số hóa bởi Trung tâm Học liệu http://www.lrc-tnu.edu.vn/

Một phần của tài liệu Các thuật toán về đường đi và chu trình euler và ứng dụng (Trang 39 - 43)

Tải bản đầy đủ (PDF)

(65 trang)