chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học chuyên đề cây khung của đồ thị môn tin học
TÊN CHUYÊN ĐỀ: CÂY KHUNG CỦA ĐỒ THỊ MÔN: TIN HỌC LỜI MỞ ĐẦU Việc bồi dưỡng học sinh giỏi tin học, tạo nguồn sinh viên giỏi đáp ứng yêu cầu đào tạo nhân lực chất lượng cao xã hội việc cấp bách giai đoạn Vì với trường chuyên vùng trăn trở làm để nâng cao chất lượng dạy tin học chương trình chuyên Điều thúc đội ngũ giáo viên chuyên phải tìm tòi, nghiên cứu sáng tạo Trong chương trình tin học chuyên đồ thị vấn đề phong phú nhất, đa dạng nhất, khó nhất… nguồn cảm hứng chưa cạn không Vì năm chọn vấn đề đồ thị làm đề tài nghiên cứu lựa chọn hay Việc chọn vấn đề khung đồ thị để tìm tòi, nghiên cứu seri vấn đề cần nghiên cứu đồ thị Đồ thị cấu trúc rời rạc gồm đỉnh cạnh nối đỉnh Mô hình đồ thị sử dụng từ lâu ngày lại có ứng dụng đại Những ý tưởng đồ thị nhà toán học người Thuỵ Sĩ Leonhard Euler đưa từ kỷ 18 để giải toán cầu Konígberg tiếng Đồ thị dùng để giải toán nhiều lĩnh vực khác Chẳng hạn, lĩnh vực giao thông có toán thực tế sau: Hệ thống đường giao thông địa phương biểu diễn đơn đồ thị Để đường lại mùa đông cách phải cào tuyết thường xuyên Chính quyền địa phương muốn cào tuyết số đường cho cho có đường thông suốt nối hai thành phố Có thể làm điều cách nào? A B C D E F Rõ ràng phải cào tuyết năm đường (A,C); (A,F); (A,B); (B,D); (B,E) Đây sơ đồ biểu diễn tập đường đó: A B C E D F Sơ đồ cho ta hình ảnh cây, gồm tất đỉnh đồ thị biểu diễn hệ thống giao thông số cạnh nối đỉnh để hệ thống thông suốt Đó khung (câybao trùm) đồ thị Một đồ thị có khung Từ toán thực tế mở hai vấn đề: Thứ nhất, từ đồ thị cho trước, tìm khung Thứ hai, cạnh đồ thị gán cho trọng số tìm khung có tổng trọng số nhỏ Trong khuôn khổ văn này, xin trình bày cách giải vấn đề nêu CÂY KHUNG CỦA ĐỒ THỊ 1.1 Định nghĩa Cây : đồ thị hữu hạn, vô hướng, liên thông chu trình Rừng: đồ thị hữu hạn, vô hướng chu trình Bụi: Đồ thị G=(X,U) hữu hạn, có hướng bụi có gốc x Є X có hai đỉnh thoả mãn điều kiện sau: • Mỗi đỉnh khác x1 điểm cuối cung • Đỉnh x1 không đỉnh cuối cung • Đồ thị chu trình Ví dụ: Quan sát đồ thị đây: Dựa vào định nghĩa ta thấy: G1, G2 cây; G3 không có chu trình 1.2 Tính chất Định lý Nếu T đồ thị vô hướng, n đỉnh (n>1) T có sáu tính chất sau T Mỗi tính chất mệnh đề Khi đó, mệnh đề sau tương đương: (1) T (2) T chu trình có (n-1) cạnh (3) T có (n-1) cạnh liên thông (4) T liên thông cạnh T cạnh cắt (cầu) (5) Hai đỉnh T nối với đường đơn (6) T không chứa chu trình thêm cạnh vào T ta thêm chu trình Chứng minh định lý: (1) → (2): T → T không chứa chu trình có (n-1) cạnh • Hiển nhiên T không chứa chu trình (do T cây) • Ta cần chứng minh T có (n-1) cạnh • Xét Tn có n đỉnh Ta chứng minh quy nạp theo n: o n = Cây có đỉnh có cạnh Đúng o Giả sử có k đỉnh có (k-1) cạnh o Xét Tk+1 có (k+1) đỉnh Dễ thấy tồn đỉnh treo o Loại đỉnh treo (cùng với cạnh nối) khỏi T k+1 ta đồ thị T’ có k đỉnh Dễ thấy, T’ liên thông chu trình (do Tk+1 chu trình) o Suy T’ Theo giả thiết quy nạp, T’ có k đỉnh có (k-1) cạnh Vậy Tk+1 có k cạnh (đpcm) (2) → (3): T chu trình có (n-1) cạnh → T liên thông có (n-1) cạnh • Hiển nhiên T có (n-1) cạnh (theo giả thiết) • Ta cần chứng minh T liên thông • Giả sử T có k thành phần liên thông với số đỉnh n1, n2,…, nk • Khi thành phần liên thông T có số cạnh n1-1, n2-1,…, nk-1 • Suy ra, số cạnh T là: n1-1 + n2-1 +…+ nk-1 = n-k • Theo giả thiết, số cạnh (n-1) Từ suy k=1 hay T có thành phần liên thông Suy ra, T liên thông (đpcm) (3) → (4): T có (n-1) cạnh liên thông → T liên thông cạnh T cạnh cắt (cầu) • Hiển nhiên T liên thông (theo giả thiết) • Ta cần chứng minh cạnh T cạnh cắt • Xét (u,v) cạnh T Nếu bỏ (u,v) khỏi T ta đồ thị T’ có n đỉnh (n-2) cạnh • Ta chứng minh đồ thị có n đỉnh (n-2) cạnh liên thông • Vậy bỏ cạnh (u,v) làm tình liên thông đồ thị Suy (u,v) cạnh cắt (đpcm) (4) → (5): T liên thông cạnh T cạnh cắt (cầu) → Hai đỉnh T nối với đường đơn • Xét u,v đỉnh T • Do T liên thông nên tồn đường u,v Ta chứng minh đường • Giả sử có hai đường đơn khác u v Khi hai đường tạo thành chu trình • Suy cạnh chu trình cạnh cắt • Vậy u v tồn đường đơn (đpcm) (5) → (6): Hai đỉnh T nối với đường đơn → T không chứa chu trình thêm cạnh vào T ta thêm chu trình • T có chu trình, có chu trình đỉnh chu trình có hai đường đơn khác → Mâu thuẫn với giả thiết • Giả sử ta thêm vào T cạnh (u,v) (trước cạnh T) • Khi cạnh với đường u v T tạo thành chu trình (vì tạo hai chu trình chứng tỏ trước có hai đường khác u v → Mâu thuẫn với giả thiết) (6) → (1): T không chứa chu trình thêm cạnh vào T ta thêm chu trình → T • Hiển nhiên T không chứa chu trình • Giả sử T không liên thông Khi T có nhiều thành phần liên thông • Suy ra, thêm vào cạnh hai đỉnh thuộc hai thành phần liên thông khác không tạo thêm chu trình → Mâu thuẫn với giả thiết • Vậy T phải liên thông → T (đpcm) Định lí Một bụi thay cung cạnh thành 1.3 Cây khung đồ thị Định nghĩa khung: Cho đồ thị vô hướng G=(X,E) liên thông, có n đỉnh (n>1) Mỗi đồ thị phận G gọi khung đồ thị G (hoặc bao trùm) Ví dụ: Đồ thị khung Định lí: Mọi đồ thị vô hướng có số đỉnh n>1 liên thông có khung Chứng minh định lí: • Nếu G có chứa khung tính chất khung liên thông khung chứa tất đỉnh G Suy đỉnh G nối với hay G liên thông • Xét G liên thông Giả sử G tồn chu trình, xoá bớt cạnh chu trình này, đồ thị liên thông Nếu chu trình lặp lại bước Cứ không chu trình Khi ta khung 1.4 Thuật toán tìm khung 1.4.1 Bài toán Cho đồ thị G liên thông, vô hướng, tìm khung • Dữ liệu: số đỉnh danh sách cạnh đồ thị • Kết quả: cạnh khung CK.inp 16 23 45 25 15 12 65 53 CK.out 16 23 45 25 15 1.4.2 Thuật toán Thuật toán 1: Ý tưởng: Duyệt thăm đỉnh, đỉnh lần Vì đồ thị liên thông nên thăm đủ n đỉnh (cùng lúc với qua n-1 cạnh , ta khung) Có thể dùng thuật toán DFS BFS để thăm đỉnh Cài đặt: program tim_caykhung_bang_DFS; const fi='CayKhung.inp'; fo='CKhung.DFS.out'; MN=1000; var A:array[1 MN,1 MN] of longint; vs:array[1 MN] of boolean; n,m:integer; procedure nhap; var i,j:integer; begin assign(input,fi); reset(input); readln(n,m); while not seekeof(input) do thi begin readln(i,j); a[i,j]:=1; A[j,i]:=1; end; close(input); end; // procedure DFS_VS(i:integer); var j:integer; begin vs[i]:=true; for j:=1 to n if not vs[j] and (a[i,j]=1) then begin writeln(i,' ',j); DFS_VS(j); end; end; // Begin nhap; fillchar(vs, sizeof(vs),false); assign(output,fo); rewrite(output); DFS_VS(1); //doc cac canh cua close(output); end Thuật toán 2: Hợp dần vùng liên thông Ý tưởng: Mỗi lần hợp hai vùng liên thông khác cạnh nối hai vùng nạp cạnh vào khung hình thành Quá trình chấm dứt nạp đủ (n-1) cạnh Thực cụ thể: Bước 1: Coi đỉnh thuộc vùng có mã vùng v[i]=i, số cạnh nạp vào khung sl=0 Bước 2: Duyệt tất cạnh đồ thị: • Nếu sl=n-1 dừng vòng lặp duyệt • Nếu cạnh (i,j) có đỉnh i j khác mã vùng (v[i]≠v[j]) thì: o Nếu v[i]v[j]: tất đỉnh mã vùng với i gán lại mã vùng v[j], nạp vào khung cạnh (i,j), tăng biến sl đơn vị Cài đặt: program CayKhung; const fi='CayKhung.inp'; fo='CK.out'; var b,dau,cuoi:array[1 10000] of longint; i,j,k,n,t,sc:longint; f:text; procedure nhap; begin assign(f,fi); reset(f); readln(f,n); for i:=1 to n b[i]:=i; sc :=0; while not eof(f) {doc cac canh cua thi} begin readln(f,i,j); if b[i] b[j] then {khac ma vung lien thong} begin inc(sc); {tang so canh} dau[sc] := i; {nap them mot canh vao mang dau va cuoi} cuoi[sc] := j; if b[i]b[j] then for t:=1 to n if b[t]=b[i] then b[t]:=b[j]; end; if sc = n-1 then break; {nap du n-1 canh thi dung lai} end; close(f); end; procedure xuat; begin assign(f,fo); rewrite(f); for i := to n -1 writeln(f,dau[i],' ',cuoi[i]); close(f); end; begin nhap; xuat; end Thuật toán 3: Hợp dần Ý tưởng: Mỗi lần hợp hai có gốc khác cạnh đồ thị (nối hai đỉnh thuộc hai này)thì cạnh xác nhận cạnh khung hình thành Quá trình kết thúc nạp đủ n-1 cạnh khung Cài đặt: program CayKhung; {su dung thuat toan hop nhat hai cay} const fi='Caykhung.inp'; fo='CKhung.out'; MN=5000; var cha,dau,cuoi:array[1 MN] of integer; n,m,socanh:longint; // -function root(x:integer):integer; //tim goc cay chua dinh x var i:integer; begin i:=x; while cha[i]>0 i:=cha[i]; exit(i); end; // -procedure Union(x,y:integer); // hop nhat hai cay oc x, goc y var temp:integer; begin temp:=cha[x]+cha[y]; if cha[x]>cha[y] then nut hon begin cha[x]:=y; cay hop nhat cha[y]:=temp; cay chua dinh x co it nut hon end else begin hon cha[y]:=x; cha[x]:=temp; end; end; // -procedure nhap_taocay; var i, x, y,r1,r2:longint; begin assign(input,fi); reset(input); readln(n); for i:=1 to n cha[i]:=-1; la chinh no socanh :=0; while not seekeof(input) begin if socanh=n-1 then exit; readln(x,y); r1:=root(x); r2:=root(y); if r1 r2 then begin inc(socanh); dau[socanh] := x; mang dau va cuoi cuoi[socanh] := y; union(r1,r2); end; end; close(input); end; procedure xuat; var i:integer; begin assign(output,fo); rewrite(output); writeln(socanh); for i := to n -1 // cay chua dinh x co it //tam coi y la cha cua x //goc moi cua cay la y //cay chua dinh y co it nut // moi dinh la cay co goc //doc cac canh cua thi //la cay khung, ket thuc //hai cay co goc khac nha //tang so canh //nap them mot canh vao //hop nhat hai cay 10 Để thực yêu cầu này, ta sử dụng thuật toán hợp vùng liên thông Quá trình dừng kết nạp n-1 cạnh vào Cài đặt: program Kruskal; const fi='ck.inp'; fo='ck.out'; type canh = record d,c,l : longint; end; var b:array[1 10000] of longint; a,ck : array[1 10000] of canh; i,j,k,n,m,t,sc,sum:longint; f:text; Procedure QuickSort(dau, cuoi : longint); var x, L, R : longint; tmp : canh; begin x := a[(dau+cuoi) div 2].l; L := dau; R := cuoi; repeat while a[L].l < x inc(L); while a[R].l > x dec(R); if L R; if R > dau then QuickSort(dau,R); if L < cuoi then QuickSort(L,cuoi); end; procedure nhap; begin assign(f,fi); reset(f); readln(f,n); m := 0; while not eof(f) {doc cac canh cua thi} begin inc(m); readln(f,a[m].d,a[m].c,a[m].l); end; close(f); end; procedure xuli; begin QuickSort(1,m); for i:=1 to n b[i]:=i; 12 sc :=0;sum := 0; for k := to m begin i := a[k].d;j:= a[k].c; if b[i] b[j] then {khac ma vung lien thong} begin inc(sc); {tang so canh} ck[sc] := a[k]; {them canh vao cay khung} sum := sum + a[k].l; if b[i]b[j] then for t:=1 to n if b[t]=b[i] then b[t]:=b[j]; end; if sc = n-1 then break; {nap du n-1 canh thi dung lai} end; end; procedure xuat; begin assign(f,fo); rewrite(f); writeln(f,'Cay khung la: '); for i := to n -1 writeln(f,' (',ck[i].d,',',ck[i].c,')'); writeln(f,'Tong so : ',sum); close(f); end; begin nhap; xuli; xuat; end 1.5.3 Thuật toán Prim Ý tưởng: Nạp dần tập cách đỉnh vào khung Mỗi lần chọn đỉnh chưa nạp đỉnh kề gần đỉnh nạp Thuật toán: Bước 1: Nạp đỉnh vào khung (thường đỉnh 1) Bước 2: Lần lượt nạp n-1 đỉnh lại (tương ứng với n-1 cạnh) vào khung cách: lần chọn cạnh có trọng số nhỏ mà đầu cạnh thuộc cây, đầu chưa thuộc (nghĩa chọn đỉnh gần đỉnh nạp nhất) Cài đặt 13 program Prim; const max=100; f1='ck.inp'; f2='ck.out'; var a: array[1 max,1 max] of integer; d1,d2,d:array[1 max] of integer; n: integer; procedure nhap; var g:text; i,j,x:integer; begin assign(g,f1); reset(g); readln(g,n); while not seekeof(g) begin readln(g,i,j,x); a[i,j]:=x; a[j,i]:=x; end; close(g); end; procedure timcanh( var i,j:integer); {Tim canh i, j ngan nhat} var x,y,min:integer; begin min:=maxint; for x:=1 to n if d[x]=1 then for y:=1 to n if d[y]=0 then if (a[x,y]>0) and (a[x,y]0 r dòng tiếp theo, dòng ghi số kênh cần loại bỏ Các số dòng tệp liệu tệp kết cách dấu cách Ví dụ: MRG.INP 57 123 233 342 532 541 522 151 MRG.OUT Cách giải + Xây dựng đồ thị vô hướng với đỉnh máy tính, có N đỉnh Mỗi cạnh kênh M kênh, có M cạnh; trọng số cạnh loại kênh (1, 2, 3) + Dùng thuật toán hợp dần (Thuật toán 3) để tìm khung Cụ thể sau: - Vì đường loại theo yêu cầu phải chứa kênh loại nên ta tìm rừng gồm cạnh loại 3, gọi (R3) - Nếu rừng khung (tức có n-1 cạnh) toán có nghiệm loại bỏ tất cạnh loại loại - Nếu rừng chưa khung phải xem xét bổ sung cạnh loại loại vào rừng để mạng có đường truyền loại loại Tiến hành việc sau: Cùng với R3, xét thêm cạnh loại 1, thực lại thuật toán để xem có tạo thành khung (chỉ gồm cạnh loại 3) không Nếu không khung vô nghiệm (r=-1); có thực tiếp bước 2: 16 Cùng với R3, xét thêm cạnh loại 2, thực tiếp thuật toán xem có tạo thành khung (chỉ gồm cạnh loại 2) không Nếu không vô nghiệm (r=-1); Nếu có khung toán có nghiệm, cạnh cần loại bỏ cạnh loại không thuộc khung tìm thấy Để thực việc cần đánh dấu cạnh nạp vào khung Văn chương trình program mang_rut_gon; const fi='MRG.IN2'; fo='MRG.OUT'; MN=500; MM=10000; type canh=record u,v:integer; w:shortint end; var m,n:integer; socanh, lsc: integer; //so canh, luu so canh l, ll:array[1 MN] of integer; //nhan cua dinh, luu nhan ddinh ds:array[1 MM] of canh; //danh sach canh caykhung:boolean; //co la cay khung hay khong // -procedure readf; var i:integer; begin assign(input,fi); reset(input); readln(n,m); for i:=1 to m with ds[i] readln(u,v,w); close(input); for i:=1 to n l[i]:=-1; // moi cay co goc la chinh no end; // function root(u:integer):integer; chua u begin while l[u]>=0 u:=l[u]; exit(u); end; // procedure union(r1,r2:integer); la r1, r2 var x:integer; begin //tra ve goc cay //hop nhat hai cay co goc 17 x:=l[r1]+l[r2]; //nhan cua goc cay hop nhat if l[r1]>l[r2] then begin l[r1]:=r2; l[r2]:=x end else begin l[r2]:=r1; l[r1]:=x; end; end; // function KRUSKAL(k:integer):boolean; //co la cay khung them canh loai k khong var i, r1,r2:integer; begin for i:=1 to m with ds[i] if w=k then begin r1:=root(u); //goc cua cay chua dinh u r2:=root(v); //goc cay chua dinh v if r1r2 then begin w:=-w; // danh dau da canh da nap vao cay inc(socanh); if socanh=n-1 then exit(true); //la cay khung union(r1,r2); // chua la cay thi hop nhat hai cay end; end; exit(false); end; // procedure writef; var i:integer; begin assign(output,fo); rewrite(output); if not caykhung then writeln(-1) //khong co duong truyen thoa man else begin writeln(m-n+n-2-lsc); for i:=1 to m if ds[i].w>0 then writeln(i); end; close(output); end; // 18 begin readf; socanh:=0; caykhung:=KRUSKAL(3); if not caykhung then begin lsc:=socanh; ll:=l; caykhung:=KRUSKAL(1); socanh:=lsc; l:=ll; caykhung:=caykhung and KRUSKAL(2); end; writef; end Bài toán Mạng giao thông Theo thiết kế, mạng giao thông gồm N nút có số hiệu từ đến N (N≤1000) Chi phí để xây dựng đường hai chiều trực tiếp từ nút i đến nút j A[i,j]=A[j,i] Hai tuyến đường khác không cắt điểm không đầu mút Hiện xây dựng K tuyến đường Bài toán đặt sau: Hệ thống đường xây dựng có bảo đảm lại hai nút bát kỳ chưa? Nếu chưa, chọn số tuyến đường cần xây dựng thêm cho: Các tuyến đường xây dựng thêm với K tuyến đường xây dựng bảo đảm lại hai nút Tổng kinh phí xây dựng thêm tuyến đường Dữ liệu vào từ tệp văn MGT.INP sau: - Dòng chứa hai số N, K (N≤500); M≤10000) - Trong K dòng chứa hai số nguyên dương số hiệu hai nút, tuyến đường xây dựng - Cuối N dòng, dờng thứ i ghi N số A[i,1], A[i,2], …, A[i,N] Kết ghi tệp văn MGT.OUT gồm: - Dòng ghi số CP chi phí xây dựng thêm - Nếu CP>0 N dòng tiếp theo, dòng ghi hai số số hiệu hai nút, hai đầu tuyến đường cần xây dựng thêm 19 Các số dòng tệp liệu tệp kết cách dấu cách Ví dụ: MGT.INP 54 12 23 31 45 01111 10111 11011 11101 11110 MGT.OUT 34 Cách giải: + Dựa vào mạng giao thông xây dựng đồ thị vô hướng, có trọng số: - Mỗi đỉnh đồ thị nút giao thông (N đỉnh); - Mỗi cạnh đoạn đường trực tiếp nối nút; - Trọng số cạnh tương ứng với đoạn đường xây dựng 0; cạnh chưa xây dựng chi phí xây dựng quãng đường tương ứng + Tìm khung ngắn đồ thị Nếu trọng số 0, có nghĩa K đoạn đường xây dựng đảm bảo lại hai nút (đồ thị liên thông) Ngược lại, trọng số khác 0, có đoạn đường chưa xây dựng (là cạnh có trọng số khác 0) Đó đoạn đường cần xây dựng thêm + Văn chương trình Program MangGiaoThong; Const Fi='MGT.INP'; Fo='MGT.OUT'; nm=100; Var f:text; n:integer; a:array[1 nm,1 nm] of longint; tr:array[1 nm] of integer; vs:array[1 nm] of boolean; res:longint; 20 Procedure Nhap; var i,j:integer; k:longint; begin assign(f,fi); reset(f); readln(f,n,k); fillchar(a,sizeof(a),255); while k>0 begin readln(f,i,j); a[i,j]:=0; a[j,i]:=0; dec(k); end; for i:=1 to n for j:=1 to n begin read(f,k); if a[i,j]=-1 then a[i,j]:=k; end; close(f); end; Procedure Prim; var i,j,sc:integer; min:longint; begin for i:=1 to n tr[i]:=1; fillchar(vs,sizeof(vs),false); vs[1]:=true; res:=0; for sc:=1 to n-1 begin min:=High(longint); for i:=1 to n if not vs[i] and (a[tr[i],i]