A. PHẦN MỞ ĐẦU I. Lý do chọn đề tài Phương pháp tìm kiếm theo chiều rộng trên đồ thị được mô phỏng giống như hình ảnh vết dầu loang trên mặt nước. Thực tế ta thấy, nếu đổ một giọt dầu trên bề mặt nước thì vết dầu đó sẽ loang ra theo không gian và thời gian. Nếu như mặt nước đủ lớn thì vết dầu đó sẽ loang theo đường tròn tức là những điểm cùng khoảng cách so với tâm vết loang thì vết dầu sẽ đến cùng một lúc. Đây cũng là điều khá thú vị, bất kể điểm nào trên mặt nước có liên thông với tâm vết dầu thì sẽ loang đến đó. Do đó có thể sử dụng phương pháp này để kiểm tra tính liên thông của bất kỳ điểm nào trong đồ thị. Một ví dụ khá tương tự với vết dầu loang là hiện tượng giao động sóng cơ học trên mặt nước. Một mặt nước phẳng lặng, nếu chúng ta ném một vật vào mặt nước thì trên mặt nước sẽ xuất hiện các gợn sóng mà tâm sóng là điểm chúng ta ném xuống. Các gợn sóng này sẽ dao động từ trong ra ngoài, với bán kính lớn dần. Với điều kiện tốt thì các gợn sóng sẽ làm cả bề mặt nước đều phải dao động. Phương pháp này có một số đặc điểm khá hay và thực tế: Những điểm có cùng khoảng cách so với tâm đường tròn thì sẽ được loang (dao động) cùng một lúc. Những điểm mà vết dầu đi qua lần thứ nhất thì đường đi trong quá trình loang (dao động) là đường đi ngắn nhất để đi từ tâm đến điểm đó. Những điểm có vết dầu loang đến là những điểm liên thông với nhau, hay là tồn tại đường đi từ hai điểm bất kỳ trên bề mặt loang. Những điểm không có vết dầu loang đến thì không tồn tại đường đi đến những điểm có vết dầu loang. Hiện nay việc nghiên cứu, khai thác thuật toán tìm kiếm theo chiều rộng trên đồ thị hay còn gọi là thuật toán “Loang” vào giải toán cũng được một số tác giả quan tâm nhưng chưa có đề tài nào được công bố có tính chất hệ thống. Từ thực tiễn dạy học trong nhiều năm qua, nhất là công tác bồi dưỡng học sinh giỏi, tôi đã áp dụng nhiều lần và đưa lại kết quả khả quan. Với những lý do trên, tôi mạnh dạn chọn đề tài: Vận dụng thuật toán tìm kiếm theo chiều rộng trên đồ thị vào giải hai dạng bài toán thường gặp trong Tin học làm đề tài nghiên cứu. Hy vọng sẽ góp phần nâng cao chất lượng dạy học Tin học hiện nay ở trưởng phổ thông. II. Mục đích, nhiệm vụ của việc thực hiện đề tài nghiên cứu Việc đưa ra các bài toán cùng phương pháp giải chúng bằng cách vận dụng một thuật toán cụ thể là rất cần thiết nhằm giúp cho giáo viên, cũng như học sinh hệ thống lại các kiến thức đã học đồng thời làm phong phú thêm tư duy thuật toán và lập trình của mình. Tôi xin đưa ra mục đích, nhiệm vụ cụ thể của việc thực hiện đề tài: Giới thiệu về các khái niệm có liên quan trong quá trình thực hiện đề tài. Hệ thống các bài toán thường gặp theo mức độ từ đơn giản đến phức tạp với cách giải đơn giản và dễ hiểu nhằm giúp cho giáo viên và học sinh bước đầu nhận dạng và giải một số bài tập liên quan. Giới thiệu một số bài tập áp dụng. Cấu trúc nội dung gồm: Mục I. Cơ sở lý luận Mục II. Hai dạng toán thường gặp. Mục III. Bài tập đề nghị. III. Đối tượng, thời gian và phương pháp nghiên cứu 1. Đối tượng nghiên cứu Bài viết SKKN Vận dụng thuật toán tìm kiếm theo chiều rộng trên đồ thị vào giải hai dạng bài toán thường gặp trong Tin học có đối tượng nghiên cứu là các bài toán tìm kiếm thành phần liên thông trên đồ thị và các bài toán tìm đường đi ngắn nhất (qua ít đỉnh nhất). 2. Thời gian nghiên cứu SKKN được thực hiện trong năm học 2015 2016 3. Phương pháp nghiên cứu Để hoàn thành SKKN này tôi sử dụng phối kết hợp nhiều phương pháp, trong đó phương pháp chủ yếu là nghiên cứu tài liệu, tham khảo ý kiến của cấp trên và đồng nghiệp. IV. Điểm mới trong kết quả nghiên cứu Điểm mới trong kết quả nghiên cứu của đề tài được thể hiện qua hai khía cạnh sau: Thứ nhất: Đưa ra được hệ thống các bài tập theo các dạng, mức độ khác nhau và cách cài đặt chương trình cũng được cải tiến dần từ mức dễ đến khó, từ đơn giản đến phức tạp. Hơn nữa, từ việc hiểu rõ bản chất của phương pháp tìm kiếm theo chiều rộng trên đồ thị, chúng ta có thể cài đặt thuật toán bằng các cấu trúc dữ liệu khác nhau vào từng bài toán cụ thể nhằm tối ưu bộ nhớ cũng như thời gian tìm kiếm. Một số bài toán cần vận dụng thuật toán một cách linh hoạt, có thể trong một chương trình phải sử dụng thuật toán nhiều lần với các tùy chọn khác nhau…Hơn nữa nhiều lúc chúng ta cần phối hợp cả hai phương pháp tìm kiếm (theo chiều rộng và chiều sâu) để nâng cao hiệu quả giải toán. Thứ hai: nhận diện được các bài toán cùng dạng hoặc tìm cách đưa chúng về cùng dạng sao cho việc vận dụng phương pháp tìm kiếm theo chiều rộng trên đồ thị để giải sẽ mang lại hiệu quả. B. PHẦN NỘI DUNG I. CƠ SỞ LÝ LUẬN 1. Một số khái niệm. Định nghĩa 1: Đường đi có độ dài k (k nguyên dương) từ đỉnh u tới đỉnh v trên đồ thị vô hướng G=(V, E) là dãy các đỉnh u=x0, x1, x2, x3,…, xk=v mà các cạnh (xi, xi+1)E, i=0,1,2,…,k1. Đường đi này còn có thể biểu diễn dưới dạng dãy các cạnh: (x0,x1), (x1,x2),….,(xk1,xk). Đỉnh u gọi là đỉnh đầu (xuất phát), đỉnh v gọi là đỉnh cuối (đỉnh đích) của đường đi. Đường đi có đỉnh đầu trùng với đỉnh cuối gọi là một chu trình. Đường đi hay chu trình được gọi là đơn nếu không có cạnh nào bị lặp lại. Đường đi hay chu trình được gọi là cơ bản nếu không có đỉnh nào bị lặp lại (trừ trường hợp trong chu trình thì đỉnh đầu trùng đỉnh cuối là được lặp lại) Định nghĩa 2: Đường đi có độ dài k (k nguyên dương) từ đỉnh u tới đỉnh v trên đồ thị có hướng G=(V, E) là dãy các đỉnh u=x0, x1, x2, x3,…, xk=v mà các cung (xi, xi+1)E, i=0,1,2,…,k1. Đường đi này còn có thể biểu diễn dưới dạng dãy các cung: (x0, x1), (x1,x2),….,(xk1,xk). Đỉnh u gọi là đỉnh đầu (xuất phát), đỉnh v gọi là đỉnh cuối (đỉnh đích) của đường đi. Đường đi có đỉnh đầu trùng với đỉnh cuối gọi là một chu trình (mạch vòng). Định nghĩa 3: Đồ thị vô hướng G=(V,E) được gọi là liên thông nếu luôn tìm được đường đi giữa 2 đỉnh bất kỳ của nó. Định nghĩa 4: Cho đồ thị vô hướng G=(V,E) và đồ thị con của G là đồ thị G’=(V’,E’). Đồ thị G’ được gọi là một vùng liên thông (hoặc thành phần liên thông) của G nếu: + G’ liên thông; + Không tồn tại đường đi nào từ một đỉnh thuộc G’ tới một đỉnh không thuộc G’ (nói cách khác là bảo đảm tính tối đại của liên thông trong G’). VD: Trong hình 5 xét 2 đồ thị G và H: G chỉ có một vùng liên thông duy nhất, H có ba vùng liên thông là H1, H2, H3. Định nghĩa 5: Đỉnh v được gọi là đỉnh khớp (đỉnh rẻ nhánh) của đồ thị vô hướng G=(V,E) nếu khi loại bỏ đỉnh v và các cạnh liên thuộc với nó thì số thành phần liên thông của G tăng thêm. Cạnh eE được gọi là cầu nếu loại bỏ nó khỏi đồ thị G thì số thành phần liên thông của G tăng thêm 1 đơn vị. Định nghĩa 6: Đồ thị có hướng G=(V, E) được gọi là liên thông mạnh nếu với mọi cặp đỉnh u và v luôn tồn tại hai đường đi: đường đi có hướng từ đỉnh u đến đỉnh v và đường đi có hướng từ đỉnh v đến đỉnh u. Định nghĩa 7: Queue (hàng đợi). “Hàng đợi” là một cấu trúc dữ liệu quan trọng được áp dụng rất nhiều trong công nghệ thông tin, khoa học và đời sống. Để dễ dàng hình dung về hàng đợi chúng ta có thể xem ví dụ sau: Trong một quầy bán vé ở sân bay, mọi người phải xếp hàng tuần tự để mua vé, đến lượt hành khách nào thì hành khách đó mua vé và ra khỏi hàng, giả sử không có sự chen lấn. Trong hàng đợi này có một số tính chất sau: Hình 1: Minh họa hàng đợi mua vé. Người mua vé sẽ được thêm vào cuối hàng. Người đầu hàng sẽ bị xóa khỏi hàng khi thực hiện xong công việc mua vé. Cấu trúc Hàng đợi được sử dụng trong bài nghiên cứu: 1. Hàng đợi là danh sách có thứ tự trong đó phép toán chèn luôn thực hiện ở một phía gọi là phía sau (giống như cuối hàng trong mua vé). Còn phép toán xóa chỉ thực hiện ở phía còn lại gọi là phía trước (giống như người mua vé xong thì ra khỏi hàng). 2. Thuật ngữ được dùng cho hai thao tác chèn và xóa đối với hàng đợi tương ứng là enqueue (đưa vào) và dequeue (đưa ra). Và từ nay về sau, trong bài nghiên cứu khi tôi viết là enqueue(u) thì được hiểu là thêm phần tử u vào hàng đợi, dequeue() được hiểu là xóa phần tử đầu tiên của hàng đợi. 3. Các phần tử được lấy ra khỏi hàng đợi theo quy tắc FIFO “vào trước – ra trước” (Fisrt In – First Out). Minh họa cho thao tác trên hàng đợi: Hình 2: Minh họa thao tác với hàng đợi. Các hàm được sử dụng trong nghiên cứu: 4. enqueue(u) : thêm phần tử u vào hàng đợi. 5. dequeue() : lấy ra giá trị phần từ đầu tiên của hàng đợi, đồng thời xóa phần tử đó ra khỏi hàng đợi. • isEmpty() : kiểm tra hàng đợi có rỗng hay không? (hàng đợi rỗng tức là số phần tử có trong hàng đợi bằng 0). 2. Ý tưởng thuật toán tìm kiếm theo chiều rộng trên đồ thị. Ý tưởng: Bắt đầu tìm kiếm từ một đỉnh v. Việc thăm đỉnh v sẽ phát sinh thứ tự duyệt những đỉnh (x1, x2, x3,…,xp) kề với v (những đỉnh có cạnh nối trực tiếp với đỉnh v). Khi thăm đỉnh x1 sẽ lại phát sinh yêu cầu duyệt những đỉnh (u1, u2, u3,…,uq) kề với x1. Các đỉnh u này “xa” v hơn những đỉnh x nên chúng sẽ được duyệt khi tất cả những đỉnh x đã duyệt xong. Tức thứ tự duyệt đỉnh sau khi đã thăm x1 sẽ là: (x2, x3,…,xp, u1, u2, u3,…,uq). Giả sử ta có một danh sách chứa những đỉnh đang chờ thăm. Tại mỗi bước, ta thăm một đỉnh đầu danh sách và cho những đỉnh chưa xếp hàng kề với nó xếp hàng thêm vào cuối danh sách. Chính vì nguyên tắc đó nên danh sách chứa những đỉnh đang chờ sẽ được tổ chức dưới dạng hàng đợi (Queue – vào trước ra trước) Một khái niệm tương đương: Đường đồng mức là đường nối các điểm có cùng giá trị tìm kiếm. Nếu như trong thực tế lý tưởng thì đường đồng mức là đường tròn có tâm là điểm bắt đầu tìm kiếm. VD: Với đồ thị như hình 1, giử sử ta cho tìm kiếm từ đỉnh 1 thì sẽ được kết quả như sau: 1 rồi đến 2, 3, 7, tiếp theo là 4, 5, và 6, 9, cuối cùng là 8 Minh họa quá trình tìm kiếm: 3. Hai hướng tìm kiếm. Trong thực tế thuật toán tìm kiếm theo chiều rộng trên đồ thị được áp dụng theo một trong hai trường hợp sau: Thứ nhất: Tìm kiếm theo chiều rộng, tức là tìm kiếm theo bán kính. Những điểm có cùng bán kính thì sẽ được xét cùng một lượt, điểm ở xa tâm nhất thì sẽ được xét đến cuối cùng. Một ví dụ đặc trưng của trường hợp này là nếu như đổ một giọt dầu xuống nước thì sau một thời gian nó sẽ lan ra cả bề mặt, những điểm gần tâm nhất sẽ được lan đến trước. Dựa vào vết dầu này chúng ta có thể phát sinh thêm để giải bài toán kiểm tra tính liên thông của đồ thị, đường đi ngắn nhất trong đồ thị, tìm đường đi tối ưu giữa hai hoặc nhiều đỉnh bất kỳ. Thứ hai: Tìm kiếm theo chiều sâu (tìm kiếm theo độ cao), nghĩa là những điểm có cùng độ cao sẽ được thăm cùng một lượt (cơ bản cùng một lượt nhưng thực tế sẽ mất một khoảng thời gian delta(t) nào đó để di chuyển nữa). Đặc trưng cụ thể của bài toán thể hiện qua ví dụ sau: trong một thùng nước chúng ta bỏ vào đáy thùng những vật đặc có chiều cao khác nhau chẳng hạn các viên đá có kích thước khác nhau. Nếu như chúng ta cho mực nước dâng lên một cách từ từ chúng ta sẽ có một số nhận xét thực tiễn như sau: Những vật cản có chiều cao thấp nhất sẽ bị tràn qua trước hay là sẽ bị ngập trước (với trường hợp không bị bao quanh bởi các vật cản cao hơn), có một số trường hợp đặc biệt như có một vật nào đó có độ cao thấp nhưng bị vây quanh bởi các vật có chiều cao cao hơn thì nước phải tràn qua các vật ngoài rồi sau đó mới tràn vào trong. Một số bài toán có thể được áp dụng vào thực tiễn thì sẽ dễ dàng giải quyết hơn. 4. Thuật toán tìm kiếm theo chiều rộng trên đồ thị. Thuật toán dưới đây được cài đặt bằng hàng đợi Queue: Procedure BFS(v); {Tìm kiếm theo chiều rộng bắt đầu từ đỉnh v } Begin queue:=f; {Queue xuất phát bằng rỗng} enqueue(v); {nạp đỉnh v vào Queue} Chuaxetv :=false ; {đánh dấu v đã xét} While isEmpty() do {trong khi Queue khác rỗng:} Begin u=dequeue();{Lấy đỉnh u từ Queue ra, không chỉ lấy ra mà phải xóa nó ở trong queue nữa, đồng thời trả về giá trị của phần tử lấy ra} Thăm_đỉnh(u); For yÎ Ke(u) do {duyệt các đỉnh kề với đỉnh u} If chuaxety then {nếu đỉnh y chưa xét đến} Begin enqueue(y) ; {nạp đỉnh y vào Queue} Chuaxety:=false; {đánh dấu đã xét} End; End; End; BEGIN For vÎ V do Chuaxetv:=true; {Khởi tạo} For vÎ V do If Chuaxetv then BFS(v); END. II. HAI DẠNG BÀI TẬP THƯỜNG GẶP 1. Bài toán tìm thành phần liên thông của đồ thị. 1.1. Phương pháp chung. Giả sử đồ thị vô hướng G=(V, E) có n đỉnh đánh số 1, 2, 3,…,n. Để liệt kê các thành phần liên thông của G phương pháp cơ bản nhất là: Đánh dấu đỉnh 1 và những đỉnh có thể đến từ đỉnh 1, thông báo những đỉnh đó thuộc thành phần liên thông thứ nhất. Nếu tất cả các đỉnh đều đã bị đánh dấu thì G là đồ thị liên thông, nếu không thì sẽ tồn tại một đỉnh v nào đó chưa bị đánh dấu, ta sẽ đánh dấu v và các đỉnh có thể đến được từ v, thông báo những đỉnh đó thuộc thành phần liên thông thứ 2 Và cứ tiếp tục như vậy cho tới khi tất cả các đỉnh đều đã bị đánh dấu. Số lần gọi thực hiện thuật toán BFS cho ta tương ứng số thành phần liên thông của đồ thị. Thuật toán minh họa phương pháp trên: Procedure BFS(u); { thuật toán “BFS” liệt kê và đánh dấu các đỉnh có thể đến được từ u} Begin … End; BEGIN {chương trình chính} Khởi tạo tất cả các đỉnh đều chưa đánh dấu (Sử dụng câu lệnh For – Do hoặc fillchar) dem:=0; For u:=1 to n do If then Begin dem:=dem+1; Write(‘Thanh phan lien thong thu ’, dem, ‘ gom cac dinh:’); BFS(u); End; END. 1.2. Một số ví dụ áp dụng Ví dụ 1: Đếm nhóm bạn trong Hội trại (Trích đề thi chọn HSG lớp 12 Tỉnh Quãng Bình năm học 2012 2013) Trong một Hội trại hè do Tỉnh Đoàn tổ chức, có N học sinh tham gia, trong đó, có một số học sinh quen nhau. Một số học sinh được gọi là cùng một nhóm bạn, nếu bất kì một học sinh nào thuộc nhóm đều có quen ít nhất một học sinh khác trong cùng nhóm đó. Yêu cầu: Hãy đếm xem có bao nhiêu nhóm bạn trong N học sinh tham gia Hội trại. Dữ liệu vào: Cho trong file văn bản NHOMBAN.INP, có cấu trúc như sau: Dòng 1: Ghi số nguyên dương N, là số lượng học sinh tham gia Hội trại. (1 ≤ N ≤ 1000). Trong N dòng tiếp theo: Mỗi dòng ghi N số nguyên dương ai,j với ý nghĩa: ai,j = 1 nếu học sinh i quen học sinh j (với i ≠j). ai,j = 0 nếu học sinh i không quen học sinh j (với i ≠j). ai,i = 1 (học sinh i được xem là quen bản thân nó). Các số trên cùng một dòng được ghi cách nhau ít nhất một dấu cách. Dữ liệu ra: Ghi ra file văn bản NHOMBAN.OUT, theo cấu trúc như sau: Dòng 1: Ghi số nguyên dương K, là số lượng nhóm bạn tìm được trong N học sinh tham gia Hội trại. Ví dụ: NHOMBAN.INP NHOMBAN.OUT 5 1 0 0 1 1 0 1 1 0 0 0 1 1 0 0 1 0 0 1 1 1 0 0 1 1 2 Ý tưởng giải thuật: Có thể giải bài toán theo nhiều cách. Cách giải sau đây áp dụng thuật toán tìm kiếm theo chiều rộng trên đồ thị. Với mỗi học sinh tham gia hội trại được biểu diễn tương ứng là một đỉnh của một đồ thị vô hướng. Yêu cầu của bài toán được chuyển thành tìm số thành phần liên thông của đồ thị. Giả sử ta đang tìm các bạn học sinh thuộc một nhóm bạn nào đó, ví dụ nhóm 1, lúc này ta tiến hành tìm kiếm từ một học sinh x bất kỳ trong số N học sinh tham gia hội trại và đánh dấu học sinh này là đã tham gia vào một nhóm 1. Từ học sinh x ta tìm và đánh dấu những học sinh nào có quen với học sinh x và có quen nhau đưa vào nhóm 1. Nếu tất cả học sinh đều được đánh dấu thì kết luận có một nhóm bạn, ngược lại ta tiếp tục tìm kiếm nhóm bạn khác bắt đầu từ một bạn học sinh chưa đánh dấu. Quá trình tìm nhóm bạn kết thúc khi tất cả các học sinh đều được đánh dấu. Số lần gọi thuật toán BFS cho biết số nhóm bạn cần tìm. Chương trình tham khảo Program Dem_nhom_ban_trong_hoi_trai; Const fi= NHOMBAN.INP; fo= NHOMBAN.OUT; Var A:array1..1000,1..1000 of 0..1; Q:array1..1000 of integer; kt:array1..1000 of boolean; i,j,d,x,dem,n:integer; f:text; (=====================================) Procedure doctep; Begin Assign(f,fi);reset(f); readln(f,n); for i:=1 to n do begin for j:=1 to n do read(f,ai,j); readln(f); end; close(f); Assign(f,fo);rewrite(f); End; (=====================================) Procedure BFS(m:integer); var dau,cuoi,x,y:Integer; Begin dau:=1; cuoi:=1; Qcuoi:=m; ktm:=false; while dau