HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỄN THÔNG CƠ SỞ THÀNH PHỐ HỒ CHÍ MINH BÀI TẬP ĐIỀU

KIỆN Hệ đào tạo từ xa Học Kỳ 2

MÔN: TOÁN RỜI RẠC

GIÁO VIÊN: TÊN HỌC VIÊN: KHÓA: LỚP: MSV:

Ths LÊ THỊ CẨM TÚ NGUYỄN PHẠM TRUNG TUẤN VI.1 CN210B1 210200302

NỘI DUNG CÂU HỎI:
Câu 1 : Anh/chị hãy trình bày thuật toán tìm chu trình Euler, đường đi Euler. Viết chương trình cài đặt hai thuật toán trên. Áp dụng : Tìm chu trình Euler hoặc đường đi Euler (nếu có) của đồ thị có hướng với ma trận kề sau

Câu 2 : Anh/chị hãy trình bày thuật toán Kruskal và thuật toán Prim để tìm cây bao trùm nhỏ nhất. Viết chương trình cài đặt. Áp dụng : Tìm cây khung nhỏ nhất cho đồ thị sau theo thuật toán Prim và thuật toán Kruskal
A 5 B 6 4 3 7 F 9 4 E 8 G 1 C 5 5 2 4 D

Câu 3 : Anh/chị hãy trình bày thuật toán tìm đường đi ngắn nhất : Dijkstra. Áp dụng : Xét đồ thị sau :

B 3 A 1 E 2 0 3 2 8 5 F 4 13 C 6 D 8 Dùng giải thuật Dijkstra tìm đường đi ngắn nhất từ đỉnh A đến các đỉnh còn lại. .

Nếu v là liên thông với đỉnh x thì xếp x vào ngăn xếp sau đó xoá bỏ cạnh (v. và thực hiện: .Nếu v là đỉnh cô lập thì lấy v khỏi ngăn xếp và đưa vào CE. Viết chương trình cài đặt hai thuật toán trên. /* x là phần tử đầu stack */ if (ke(x) ≠ φ) ) . ta thực hiện theo thuật toán sau: * Tạo một mảng CE để ghi đường đi và một stack để xếp các đỉnh ta sẽ xét. Chọn u là đỉnh nào đó của đồ thị.BÀI LÀM Câu 1 : Anh/chị hãy trình bày thuật toán tìm chu trình Euler. giả sử đỉnh đó là đỉnh v. đường đi Euler. Thủ tục Euler sau sẽ cho phép ta tìm chu trình Euler. * Xét đỉnh trên cùng của ngăn xếp. x). CE:=φ. /* nạp u vào stack*/ while (Stack≠φ ) { /* duyệt cho đến khi stack rỗng*/ x= top(Stack). . u=>Stack. Kết quả chu trình Euler được chứa trong CE theo thứ tự ngược lại. * Quay lại bước 2 cho tới khi ngăn xếp rỗng. Áp dụng : Tìm chu trình Euler hoặc đường đi Euler (nếu có) của đồ thị có hướng với ma trận kề sau Để tìm một chu trình Euler. nghĩa là đỉnh u sẽ được xét đầu tiên. void Euler(void) { Stack:=φ. Xếp vào đó một đỉnh tuỳ ý u nào đó của đồ thị.

y) khỏi đồ thị}*/ } else { x<= Stack.*/ } } Chương trình cài đặt thuật toán trên : #include <stdio. j.h> #include <dos.FILE *fp. /* nạp x vào CE.h> #include <stdlib. fp = fopen("CTEULER. n. void Init(void) { int i.h> #define MAX 50 #define TRUE #define FALSE 1 0 int A[MAX][MAX]."%d". u=1. Stack<=y. /* nạp y vào Stack*/ Ke(x) = Ke(x) \{y}. CE <=x. .IN". Ke(y) = Ke(y)\{x}. &n). "r"). /*lấy x ra khỏi stack*/.h> #include <math.{ y = Đỉnh đầu trong danh sách ke(x). /*loại cạnh (x.h> #include <conio. fscanf(fp.

for(i=1. j<=n. i<=n. return(TRUE). } } fclose(fp).j++) s+=A[i][j].i++) { printf("\n"). d. for(i=1. s. j<=n. for(j=1. A[i][j]). &A[i][j]). d=0.n). printf("\n Ma tran ke:"). } if(d>0) return(FALSE). j. } int Kiemtra(void) { int i. printf("%3d". i<=n. } void Tim(void) .i++) { s=0.printf("\n So dinh do thi:%d". for(j=1. if(s%2) d++."%d".j++) { fscanf(fp.

top. . A[v][x]=0.x=1. } } while(top!=0). dCE. for(x=dCE. stack[top]=x. x>0. top=1. if (x>n) { dCE++. stack[top]=u.dCE=0. CE[x]). } void main(void) { clrscr(). x. top--. A[x][v]=0. do { v = stack[top]. } else { top++. CE[dCE]=v. x--) printf("%3d". CE[MAX]. int stack[MAX]. printf("\n Co chu trinh Euler:").{ int v. while (x<=n && A[v][x]==0) x++.

h> #define MAX 50 #define TRUE 1 #define FALSE 0 int m. void Init(int A[][MAX]. i=1 (xét đỉnh thứ nhất của đường đi). d.Init(). Nếu i==m thì dãy b chính là một đường đi Euler. ta có thể dùng kỹ thuật đệ quy như sau: Bước 1. } Để tìm tất cả các đường đi Euler của một đồ thị n đỉnh.h> #include <conio. Lần lượt cho b[i] các giá trị là đỉnh kề với b[i-1] mà cạnh (b[i-1]. . else printf("\n Khong co chu trinh Euler"). m cạnh. Chương trình liệt kê tất cả đường đi Euler được thể hiện như sau: #include <stdio. ta kiểm tra: Nếu i<m thì tăng i lên 1 đơn vị (xét đỉnh tiếp theo) và quay lại bước 2. int *n) { int i. Đặt b[0]=1. b[MAX]. i. j.h> #include <dos. u.h> #include <math. Tạo mảng b có độ dài m + 1 như một ngăn xếp chứa đường đi. s. OK. getch(). Bước 2.h> #include <stdlib.FILE *fp. if(Kiemtra()) Tim().b[i]) không trùng với những cạnh đã dùng từ b[0] đến b[i-1]. Với mỗi giá trị của b[i].

j++) { fscanf(fp. n).*n). if (d!=2) OK=FALSE. for(j=1. "r"). } m=m+s.IN". s+=A[i][j]. &A[i][j]). printf("\n Ma tran ke:").i++) { printf("\n").s=0. i<=*n. for(i=1. } void Result(void) . m=0. else OK=TRUE."%d". printf("\n So dinh do thi:%d". fscanf(fp. u=i. A[i][j]).fp = fopen("DDEULER. d=0. } if (s%2) { d++. fclose(fp). printf("%3d". u=1. } m=m /2. j<=*n."%d".

int i) { int j. int A[][MAX]. i+1). A. else DDEULER(b. printf("\n Co duong di Euler:"). A[b[i-1]][j]=1. b[i]=j. i<=m. n.{ int i. n. if(i==m) Result(). i++) printf("%3d". b[i]). A[j][b[i-1]]=1. k. for(j=1. for(i=0. . j<=n. } } } void main(void) { int A[MAX][MAX]. A[j][b[i-1]]=0.j++) { if (A[b[i-1]][j]==1) { A[b[i-1]][j]=0. int n. } void DDEULER(int *b.

EA.FH.FC.CB.GD .GD.DB.AF.FH.FC.HD.BE.BA. HD.HE.CF.E.B A.EH.DF . getch().BE.E.EA. HD.EA.GD.EA.BE. else printf("\n Khong co duong di Euler").FH.HH.DF AA. E A F H G C B D i 1 2 3 4 5 6 7 8 A A.GD.FH. Init(A.GH.DF AA.HH.AF. HD.CF.EH.FH.AF.GH.clrscr().EA.DF A.CF.HE.DF AA. &n).EH.FC.EH.DB.HE.CF.FC.FC.BE.E.FG.C.EH.F A.C A.HH.GH.C. HD.B.A.FG.AE.E.CF.HH.BE. b[0]=u.A.F.CB.FG.GD.E Giá trị trong Stack CE Cạnh còn lại AA.i=1.A.AF.FG.F C AA.DB.EH.GH.B.C.DB.BE.EA.A A.GH.FG.C.DF AA.HH.DB.HE.FG.DB.DB. HD.FH.DF AA.C A.GH.GH.FG. A.HH. HD.GH.EA. n.CB.FH.BA.FC. } Áp dụng tìm chu trình và đường đi Euler của đồ thị.EA.CF.HH.C.EC.CF.EH.HE.GD.CF.BA.B. HD.BE. if(OK) DDEULER(b.EH.E.BE.FG.HE.B.EC.GD.HE.DF AA.AF.FH.DB.HH. i).GD.BA.E.HE.

GH AA.B.F.A Như vậy không tồn tại đường đi Euler của đồ thị và chu trình Euler củng không tồn tại tương ứng.F.G.A.D.HE.CF.EA.EA.D.HH.D.H.D 15 16 17 18 19 A. HD.G.EA.A A.E.C.BE.D.GH.FH.EA.H.BE.H.E A.A.E.H A.H.H.HE.FH.HE.B.G.E. F.CF.B 12 A.E.B.EH.GH AA.CF.GH.B.D.A.C.E.DF AA.HH.C.DF AA.DF AA.E. Áp dụng : Tìm cây khung nhỏ nhất cho đồ thị sau theo thuật toán Prim và thuật toán Kruskal A 5 B 6 4 3 7 F 9 4 E 8 G 1 C 5 5 2 4 D .CF.E.G.B.H.EA.A.HH.CF. Câu 2 : Anh/chị hãy trình bày thuật toán Kruskal và thuật toán Prim để tìm cây bao trùm nhỏ nhất. F.B.GD.DB.E.GH 10 A.E.CF.E.A.HE. HD.C.C.F.DF AA.HE.E.D. HD.G C C C C C C C C C C C AA.F.G.HE.FH.B.C.D 11 A.EA.B.HH. HD.HH.EA.GH.E.HH.E.B.D.B.D.B.BE.H 14 A.B.E.HH.E.C.D.CF.FH.F.H.G.D.A.B.G.FH.F.E.A.CF. Viết chương trình cài đặt.HH.HE.B.E.G.F.E 13 A.FH.EH.CF.E.DF AA.F.C.DB.FH.D.GH.HH.A.B.GH.HH.9 A.B.GH CF.D.GH AA.EA.A.D.F. F.H. F.A.GH. F A.HE.E.C.E.F.B.H. HD.HH.G.F.B.C.A.EH.EH.C.CF.A.DF AA.D.B.EA.G.

DG.CD. ở mỗi bước. Sắp xếp các cạnh của đồ thị G theo thứ tự tăng dần của trọng số cạnh. đỉnh C ta thược hiện như sau : STT 1 Cạnh Được Duyệt CA Danh sách cạnh kề được sắp xếp CE. T> theo từng bước như sau: a. While ( | T | < (n-1) and (E≠ φ ) ) { Chọn cạnh e ∈E là cạnh có độ dài nhỏ nhất.BF. E:= E\ {e}. if (T ∪ {e}: không tạo nên chu trình ) T = T ∪ {e}.BA.CB Danh Sánh còn lại EB. Thuật toán được mô tả thông qua thủ tục Kruskal như sau: void Kruskal(void) { T = φ. Xuất phát từ tập cạnh T=φ.ED. ta sẽ lần lượt duyệt trong danh sách các cạnh đã được sắp xếp. } if ( | T | <n-1) Đồ thị không liên thông.DA.GF . Thuật toán sẽ kết thúc khi ta thu được tập T gồm n-1 cạnh.Thuật toán Kruskal : Thuật toán sẽ xây dựng tập cạnh T của cây khung nhỏ nhất H=<V. b. c. từ cạnh có trọng số nhỏ đến cạnh có trọng số lớn để tìm ra cạnh mà khi bổ sung nó vào T không tạo thành chu trình trong tập các cạnh đã được bổ sung vào T trước đó.EF.EG. } Áp Dụng : Xuất phát từ cạnh 1 tức CA.BE.

h> #define MAX 50 .DG CA.BE.CE CA.CE.CE.EG EG EB.BE.GF GF GF CA.BF.BF.BA.EF.CE.EG CA.h> #include <stdlib.DA.BF.D A CD.BA.BA BF.BE.CB.DG.GF DG.DG.BA.EG.h> #include <conio.DA.DG.h> #include <dos. A 1 B 6 4 3 F E 2 C 5 D G Chương trình tìm cây khung nhỏ nhất theo thuật toán Kruskal được thể hiện như sau: #include <stdio.CD.CD.EG ED.CB.CB.CD.BA Thỏa điều kiện số cạnh n = n – 1.EB CA.CD.CB.EG.EG EF.BF BE.BF.BA.CD CA.CB CA.ED.CE.DA.EF.DA.GF DG.DG.BA.EF CA.BE.CE.BE.DA.GF EB.CB CB EB.BE.CD.CD.DA.DA.BA.GF DG.CE.BF.GF DA BF.EF.CE.CD.CB.BF.CE.BF.CB.BA.BA.ED CA.CD.ED.h> #include <math.CB.GF DG.CE. BE.BE.BE.2 3 4 5 6 7 8 9 10 11 CA.GF DG.EF.ED.

#define TRUE 1 #define FALSE 0 int n.cuoi[500]. k.in". &n. void Init(void) { int i.getch(). i. w[500]. fp=fopen("baotrum1. int Last) { int j. j=First. printf("\n So dinh do thi:%d". t3. t2."r"). &dau[i]. dau[i].&m). } void Heap(int First. cuoit[50]. &cuoi[i]. fscanf(fp. minl. while(j<=(Last/2)) { . printf("\n Canh %d: %5d%5d%5d". cuoi[i]. } fclose(fp). FILE *fp.i++) { fscanf(fp. for(i=1. int daut[50]. i<=m. w[i]). n). m). int dau[500]. connect. "%d%d". t1. &w[i]). "%d%d%d". m. printf("\n Danh sach ke do thi:"). father[50]. printf("\n So canh do thi:%d".

} else j=Last. else k=2*j. return(tro). int j) { . } void Union(int i. dau[k]=t1. t3=w[j]. dau[j]=dau[k]. cuoi[k]=t2. } } int Find(int i) { int tro=i. while(father[tro]>0) tro=father[tro].if( (2*j)<Last && w[2*j + 1]<w[2*j]) k = 2*j +1. t2=cuoi[j]. cuoi[j]=cuoi[k]. w[k]=t3. w[j]=w[k]. j=k. if(w[k]<w[j]) { t1=dau[j].

r1= Find(u). u=dau[1]. if(father[i]>father[j]) { father[i]=j. ncanh. u. i<=n. while(ndinh<n-1 && ncanh<m) { ncanh=ncanh+1. } else { father[j]=i. ndinh=0. v=cuoi[1]. father[j]=x. . for(i= m/2. father[i]=x. ncanh=0.m). i++) father[i]=-1. } } void Krusal(void) { int i. i++) Heap(i.int x = father[i]+father[j].i>0. for(i=1. minl=0. v. r2. ndinh. last. last=m. connect=TRUE. r1.

daut[ndinh]=u. minl=minl+w[1]. Heap(1. printf("\n Cac canh cua cay khung nho nhat:").r2= Find(v). } dau[1]=dau[last].r2). last=last-1. } void Result(void) { int i. printf("\n"). i++) printf("\n %5d%5d". Union(r1. if(r1!=r2) { ndinh=ndinh+1. cuoit[ndinh]=v. printf("\n Do dai cay khung nho nhat:%d". cuoi[1]=cuoi[last]. minl). w[1]=w[last]. last). for(i=1.daut[i]. i<n. cuoit[i]). } if(ndinh!=n-1) connect=FALSE. } .

đó chính là cây bao trùm nhỏ nhất cần tìm. phần thứ nhất d[v] dùng để ghi nhận độ dài cạnh nhỏ nhất trong số các cạnh nối đỉnh v với các đỉnh của cây khung đang xây dựng. Trong thuật toán này. Init(). ở mỗi bước. Phần thứ hai. Tiếp theo. near[v]]. [d[v].void main(void) { clrscr(). Nhãn của một đỉnh v gồm hai phần. Thuật toán Prim được mô tả thông qua thủ tục sau: void Prim (void) { /*bước khởi tạo*/ Chọn s là một đỉnh nào đó của đồ thị. for ( v∈ V\VH ) { . y] là nhỏ nhất. T = φ. ta có thể nhanh chóng chọn đỉnh và cạnh cần bổ sung vào cây khung. getch(). Thuật toán Prim còn được mang tên là người láng giềng gần nhất. Trong quá trình thực hiện thuật toán. Trong những tình huống như vậy. } Thuật toán Kruskal làm việc kém hiệu quả đối với những đồ thị có số cạnh khoảng m=n (n-1)/2. điều này dẫn đến đỉnh thứ ba z và ta thu được cây bộ phận gồm 3 đỉnh 2 cạnh. nối s với đỉnh y sao cho trọng số cạnh c[s. VH = { s }. Krusal(). d[s] = 0. near[s] = s. Trong đó. thuật toán Prim tỏ ra hiệu quả hơn. Quá trình được tiếp tục cho tới khi ta nhận được cây gồm n-1 cạnh. bắt đầu tại một đỉnh tuỳ ý s của đồ thị. near[v] ghi nhận đỉnh của cây khung gần v nhất. các đỉnh của đồ thị được sẽ được gán các nhãn. từ đỉnh s hoặc y tìm cạnh có độ dài nhỏ nhất. Result().

v]) { D[v] = C[u. near[v] = s. v]. VH = VH∪ {u}.D[v] = C[s. stop = TRUE. near[u] ). v]. } else { for ( v ∈ V\VH ) { if (d[v] > C[u. If ( | VH |) == n ) { H = <VH. T = T ∪ (u. T> là cây khung nhỏ nhất của đồ thị. } } } } . while ( not stop ) { Tìm u∈ V\VH thoả mãn: d[u] = min { d[v] với u∈V\VH}. near[v] =u. } /* Bước lặp */ stop = False.

int cbt[100][3].h> #include <stdlib.h> #include <dos.w.i. printf("\n So dinh: %3d ". j<=n.in".&m). int n.h> #include <math. for(i=1.m.h> #define TRUE 1 #define FALSE 0 #define MAX 10000 int a[100][100].j. void nhap(void) { int p.n). fscanf(f. int chuaxet[100].sc.j++) a[i][j]=0."%d%d".h> #include <conio. i."r"). . FILE *f.} Chương trình cài đặt thuật toán Prim tìm cây bao trùm nhỏ nhất được thực hiện như sau: #include <stdio. i++) for(j=1.k. i<=n.&n. f=fopen("baotrum.

printf("\n %3d%3d%3d".&j. a[j][i]=k. j++) { if (i!=j && a[i][j]==0) a[i][j]=MAX.a[i][j]). k). } for (i=1. p++) { fscanf(f. a[i][j]=k. cbt[i][2]). i++) { printf("\n"). j. } . for(p=1."%d%d%d". for (j=1. p<=m. i++) printf("\n %3d%3d". } void Result(void) { for(i=1. } } fclose(f). i. i<=n. printf("\n Danh sach canh:"). j<=n.i<=sc.&i.printf("\n So canh: %3d". cbt[i][1]. m). getch().&k). printf("%7d".

top. s[top]=u. sc=0.min. for (i=1. } } } .t. for(j=1. i++) chuaxet[i]=TRUE. for(i=1.k. while (sc<n-1) { min=MAX. i++) { t=s[i]. i<=n. k=t.j. j++) { if (chuaxet[j] && min>a[t][j]) { min=a[t][j]. chuaxet[u]=FALSE. j<=n. l=j. int s[100].l. top=1. i<=top.u.void PRIM(void) { int i. u=1. w=0.

top++. a[l][k]=MAX. printf("\n Do dai ngan nhat:%d". s[top]=l. cbt[sc][2]=l. cbt[i][1]. getch(). cbt[i][2]).i<=sc. w).sc++. chuaxet[l]=FALSE. PRIM(). Áp dụng : Dùng giải thuật Dijkstra tìm đường đi ngắn nhất từ đỉnh A đến các đỉnh còn lại ở đồ thị sau: . i++) printf("\n %3d%3d". } } void main(void) { clrscr(). } Câu 3 : Anh/chị hãy trình bày ưu và nhược điểm của hai thuật toán tìm đường đi ngắn nhất : Dijkstra. for(i=1. a[k][l]=MAX. printf("\n"). w=w+min. cbt[sc][1]=k. nhap().

gọn hơn so với thuật toán Ford_Bellman . .8 B 3 A 1 E 2 0 3 2 F 4 8 5 13 D C 6 Thuật toán Dijkstra : tìm đường đi ngắn nhất từ đỉnh s đến các đỉnh còn lại được Dijkstra đề nghị áp dụng cho trường hợp đồ thị có hướng với trọng số không âm. chúng ta có thể sử dụng n lần thuật toán Ford_Bellman hoặc Dijkstra (trong trường hợp trọng số không âm). nhãn đó chính là độ dài đường đi ngắn nhất từ s đến đỉnh đó.Nhược điểm là tốn nhiều thời gian và độ phức tạp cao. Một thuật toán khác áp dụng để tìm đường đi ngắn nhất theo các cặp đỉnh đồ thị là thuật toán Floy. . không chạy đồi với số âm. Tuy nhiên. Dùng giải thuật Dijkstra tìm đường đi ngắn nhất từ đỉnh A đến các đỉnh còn lại. Thuật toán được thực hiện trên cơ sở gán tạm thời cho các đỉnh. .Nhược điểm là chỉ xử lý trên số dương. mà ở mỗi bước lặp một số đỉnh sẽ có nhãn không thay đổi. Các nhãn này sẽ được biến đổi (tính lại) nhờ một thủ tục lặp. Thuật Toán Floy : để tìm đường đi ngắn nhất giữa tất cả các cặp đỉnh của đồ thị.Ưu điểm của thuật toán Dijkstra là nhanh. trong cả hai thuật toán được sử dụng đều có độ phức tạp tính toán lớn (chí ít là O(n3)).Ưu điểm tìm nhanh và tổng quát hơn Dijkstra. Thuật toán Floy dùng trong trường hợp tổng quát. Nhãn của mỗi đỉnh cho biết cận trên của độ dài đường đi ngắn nhất tới đỉnh đó. .

Giải thuật được mô tả như bảng sau : STT 1 2 3 4 5 6 A A_E Đường đi B 3 3 3 3 3 3 C ∞ ∞ 11 9 9 9 D ∞ 5 5 5 5 5 E 1 1 1 1 1 1 F ∞ 4 4 4 4 4 A_E_B A_E_B_F A_E_B_F_D A_E_B_F_D_C .

Sign up to vote on this title
UsefulNot useful