1. Trang chủ
  2. » Luận Văn - Báo Cáo

Tiểu luận các thuật toán tìm kiếm và độ phức tạp của các thuật toán

17 2K 13

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 17
Dung lượng 582,5 KB

Nội dung

Tiểu luận các thuật toán tìm kiếm và độ phức tạp của các thuật toán

Trang 1

TRƯỜNG ĐẠI HỌC KHOA HỌC KHOA CÔNG NGHỆ THÔNG TIN

TIỂU LUẬN Các thuật toán tìm kiếm và độ phức tạp

của các thuật toán

Giáo viên : PGS.TS Trương Công Tuấn

Trang 2

Phú Yên 2015

M c L c ục Lục ục Lục

I Mở đầu: 3

II Các giải thuật tìm kiếm: 3

1 Giải thuật tìm kiếm không có thông tin 3

1.1 Tìm kiếm trên danh sách 3

1.2 Tìm kiếm trên cây 6

1.3 Tìm kiếm trên đồ thị 9

2 Tìm kiếm có thông tin: 11

2.1 Tìm kiếm tối ưu (Best First Search) 11

2.2 Thuật toán A* 13

3 Tìm kiếm đối kháng 14

3.1 Trò chơi đối kháng 14

3.2 Cây trò chơi 16

III Tổng kết 17

Trang 3

I Mở đầu:

Trong ngành khoa học máy tính, một giải thuật tìm kiếm là một thuật toán lấy đầu vào là một bài toán và trả về kết quả là một lời giải cho bài toán đó, thường là sau khi cân nhắc giữa một loạt các lời giải có thể

Hầu hết các thuật toán được nghiên cứu bởi các nhà khoa học máy tính để giải quyết các bài toán đều là các thuật toán tìm kiếm

Tập hợp tất cả các lời giải có thể đối với một bài toán được gọi là không gian tìm kiếm Thuật toán thử sai (brute-force search) hay các thuật toán tìm kiếm

"sơ đẳng" không có thông tin sử dụng phương pháp đơn giản nhất và trực quan nhất Trong khi đó, các thuật toán tìm kiếm có thông tin sử dụng heuristics để áp dụng các tri thức về cấu trúc của không gian tìm kiếm nhằm giảm thời gian cần thiết cho việc tìm kiếm

II Các giải thuật tìm kiếm:

1 Giải thuật tìm kiếm không có thông tin

Một giải thuật tìm kiếm không có thông tin là giải thuật không tính đến bản chất cụ thể của bài toán Khi đó, các giải thuật dạng này có thể được cài đặt tổng quát, và cùng một cài đặt có thể được sử dụng trong một diện rộng các bài toán (do sử dụng trừu tượng hóa) Nhược điểm của các giải thuật này là phần lớn các không gian tìm kiếm có kích thước cực kì lớn, và một quá trình tìm kiếm (đặc biệt tìm kiếm theo cây) sẽ cần một khoảng thời gian đáng kể cho các ví dụ nhỏ Do đó,

để tăng tốc độ quá trình tìm kiếm, đôi khi chỉ có thể dùng giải thuật tìm kiếm có thông tin

1.1 Tìm kiếm trên danh sách

Có lẽ các giải thuật tìm kiếm trên danh sách là loại giải thuật tìm kiếm cơ bản nhất Mục đích là tìm trong một tập hợp một phần tử chứa một khóa nào đó

Do đây là một bài toán thường gặp trong khoa học máy tính, nên độ phức tạp tính toán của các thuật toán này đã được nghiên cứu kỹ càng

1.1.1 Tìm kiếm tuyến tính(tuần tự)

 Trường hợp sử dụng:

– Dữ liệu được lưu một cách “tự nhiên”, không có xử lý đặc biệt hoặc không được tổ chức ở một định dạng cho trước

Trang 4

– Lưu trên file truy xuất tuần tự.

 Ý tưởng:

– Xét lần lượt các phần tử đang được lưu – Với mỗi phần tử, so sánh khóa của nó với khóa cần tìm

 Nếu bằng nhau thì báo kết quả Xét danh sách là một mảng ta có thuật toán như sau:

Algorithm TKTuanTu(A, k) Input: Một mảng n phần tử số A, k là khóa cần tìm Output: vị trí khóa k trong A Nếu không có trả về -1 For i ← 1 to n do

if (A[i] = k) then

return i Return -1

Phân tích độ phức tạp của thuật toán:

 Trường hợp xấu nhất:

– Không có khóa cần tìm trong dãy A – Độ phức tạp: O(n)

 Trường hợp trung bình:

– Khả năng phần tử cần tìm xuất hiện trong dãy A là n/2 – Độ phức tạp: O(n/2) = O(n)

 Khi dãy A kích thước lớn à thời gian tìm kiếm lớn

1.1.2 Tìm kiếm nhị phân:

 Trường hợp sử dụng:

– Dữ liệu đã được sắp xếp theo khóa – Hỗ trợ truy xuất ngẫu nhiên

 Ý tưởng:

Trang 5

– Dựa trên tính thứ tự của các khóa loại bỏ các phần tử chắc chắn sẽ lớn hơn hoặc nhỏ hơn khóa đang tìm

Xét danh sách là một mảng ta có thuật toán như sau:

Algorithm TKNhiPhan(A, k)

Input: Một mảng n phần tử số A, k là khóa cần tìm Output: vị trí khóa k trong A Nếu không có trả về -1 dau 1

cuoi n while (dau <= cuoi)

giua = (dau+cuoi)/2;

if a[giua] > k then

cuoi = giua – 1 else if a[giua] < k then

dau = giua + 1 else

return giua;

Return -1 Đánh giá độ phức tạp của thuật toán:

 Gọi T(n) thời gian thực thi tìm kiếm nhị phân trên dãy có độ dài n

 Với a là một hằng số

 Trong trường hợp xấu nhất, nghĩa là khóa cần tìm không xuất hiện trong dãy khóa dữ liệu

( )

a n

T n

T n a n



2

2 3

log

2

n

 

 

Trang 6

1.1.3 Cây nhị phân tìm kiếm:

 Ý tưởng:

– So sánh giá trị khóa cần tìm với giá trị lưu trong nút gốc của cây

 Nếu bằng thì trả về nút hiện tại

 Nếu nhỏ hơn thì tìm kiếm trên cây con bên trái

 Nếu lớn hơn thì tìm kiếm trên cây con bên phải – Nếu cây rỗng thì không có giá trị cần tìm trong cây

Algorithm TK_NPTK(x, k) Input: Cây NPTK đặc trưng bởi nút gốc x; k là khóa cần tìm Output: Nút chứa giá trị khóa cần tìm Nếu không có trả về NIL

if x=NIL or k=x->key then return x

else

if k < x->key then

return TK_NPTK(x->left, k) else

return TK_NPTK(x->right, k)

Đánh giá độ phức tạp của thuật toán:

 Trường hợp xấu nhất:

– độ phức tạp thuật toán tỉ lệ với đường đi dài nhất trong cây

= chiều cao của cây – T(n) = O(h)

 Trường hợp trung bình:

– T(n) = O(logn)

Trang 7

1.2 Tìm kiếm trên cây

1.2.1 Thuật toán tìm kiếm theo chiều sâu(DFS)

Tư tưởng chính của thuật toán là: Giả sử chúng ta đang xét trên đồ thị G(V,E) Từ một đỉnh u V hiện thời nào đó ta sẽ thăm tới đỉnh kề v của u và quá trình được lặp lại đối với đỉnh v ở bước tổng quát, giả sử hiện tại đang xét đỉnh u0, chúng ta sẽ có hai khả năng sẽ xảy ra:

Nếu như tồn tại một đỉnh v0 kề với u0 mà chưa được thăm thì đỉnh v0 đó sẽ trở thành đỉnh đã thăm và quá trình tìm kiếm lại bắt đầu từ đỉnh v0 đó

Ngược lại, nếu mọi đỉnh kề với u0 đều đã thăm thì ta sẽ quay trở lại đỉnh mà trước đó ta đến đỉnh u0 để tiếp tục quá trình tìm kiếm

Như vậy, trong quá trình thăm đỉnh bằng thuật toán tìm kiếm theo chiều sâu, đỉnh được thăm càng muộn càng sớm được duyệt xong (Cơ chế Last In First Out - Vào sau ra trước) Do đó, ta có thể tổ chức quá trình này bằng một thủ tục đệ quy như sau:

Procedure DFS(u);

Begin Visit(u);

Daxet[u]:=True;

For v Kề(u do

if not Daxet[v] then DFS(v);

End;

Và thủ tục duyệt hệ thống toàn bộ đỉnh của đồ thị sẽ là:

Procedure Find;

Begin Fillchar(Daxet,SizeOf(Daxet),False);

For u V do

If not Daxet[u] then DFS(u);

End;

Dễ nhận thấy rằng, mỗi lần gọi DFS(u) thì toàn bộ các đỉnh cùng thành phần liên thông với u sẽ được viếng thăm Thủ tục Visit(u) là thao tác trên đỉnh u trong từng bài toán đặt ra cụ thể

1.2.2 Thuật toán tìm kiếm theo chiều rộng(BFS)

Trang 8

Thuật toán này thực ra là sự cải biến về thứ tự duyệt đỉnh trên đồ thị của tìm kiếm theo chiều sâu bằng cách thay vì dùng một STACK thì ta lại dùng một hàng đợi QUEUE để kết nạp đỉnh được thăm Như vậy, đỉnh được thăm càng sớm sẽ càng sớm trở thành duyệt xong (cơ chế First In First Out - Vào trước ra trước) Thủ tục được mô tả dưới đây:

Procedure BFS(u);

Begin Queue:=Empty Kết nạp u vào Queue;

Daxet[u]:=True;

While Queue<>Empty do Begin

Lấy v từ Queue;

Visit(v);

For w Kề(v) do

If not Daxet[w] then Begin

Kết nạp w vào Queue;

Daxet[w]:=True;

End;

End;

End;

Ta có thủ tục tìm kiếm theo chiều rộng là:

Procedure Find;

Begin Fillchar(Daxet,SizeOf(Daxet),False);

For u V do

If not Daxet[u] then BFS(u);

End;

1.2.3 Độ phức tạp của thuật toán DFS và BFS

Trang 9

Quá trình tìm kiếm trên đồ thị bắt đầu từ một đỉnh có thể thăm tất cả các đỉnh còn lại, khi đó cách biểu diễn đồ thị có ảnh hưởng lớn tới chi phí về thời gian thực hiện giải thuật:

 Trong trường hợp ta biểu diễn đồ thị bằng danh sách kề, cả hai thuật toán BFS và DFS đều có độ phức tạp tính toán là O(n + m) = O(max(n, m)) Đây

là cách cài đặt tốt nhất

 Nếu ta biểu diễn đồ thị bằng ma trận kề như ở trên thì độ phức tạp tính toán trong trường hợp này là O(n + n2) = O(n2)

 Nếu ta biểu diễn đồ thị bằng danh sách cạnh, thao tác duyệt những đỉnh kề với đỉnh u sẽ dẫn tới việc phải duyệt qua toàn bộ danh sách cạnh, đây là cài đặt tồi nhất, nó có độ phức tạp tính toán là O(n.m)

1.3 Tìm kiếm trên đồ thị

Nhiều bài toán về lý thuyết đồ thị có thể được giải quyết bằng các thuật toán tìm kiếm

1.3.1 Thuật toán Dijkstra:

Cho đơn đồ thị liên thông, có trọng số G=(V,E) Tìm khoảng cách d(u0,v) từ một đỉnh u0 cho trước đến một đỉnh v bất kỳ của G và tìm đường đi ngắn nhất từ u0 đến v

Phương pháp của thuật toán Dijkstra là: xác định tuần tự đỉnh có khoảng cách đến u0 từ nhỏ đến lớn

Trước tiên, đỉnh có khoảng cách đến a nhỏ nhất chính là a, với d(u0,u0)=0

Trong các đỉnh v ≠ u0, tìm đỉnh có khoảng cách k1 đến u0 là nhỏ nhất Đỉnh này phải là một trong các đỉnh kề với u0 Giả sử đó là u1 Ta có: d(u0,u1) = k1

Trong các đỉnh v ≠ u0 và v ≠ u1, tìm đỉnh có khoảng cách k2 đến u0

là nhỏ nhất Đỉnh này phải là một trong các đỉnh kề với u0 hoặc với u1 Giả sử đó

là u2

Ta có: d(u0,u2) = k2

Tiếp tục như trên, cho đến bao giờ tìm được khoảng cách từ u0 đến mọi đỉnh v của G Nếu V={u0, u1, , un} thì: 0 = d(u0,u0) < d(u0,u1) < d(u0,u2)

< < d(u0,un)

Thuật toán:

Trang 10

procedure Dijkstra (G=(V,E) là đơn đồ thị liên thông, có trọng số với trọng số dương)

{G có các đỉnh a=u0, u1, , un=z và trọng số m(ui,uj),

với m(ui,uj) =∞ nếu (ui,uj) không là một cạnh trong G}

for i := 1 to n L(ui) := ∞ L(a) := 0

S := V \ {a}

u := a while S Rỗng begin

for tất cả các đỉnh v thuộc S

if L(u) +m(u,v) < L(v) then L(v) := L(u)+m(u,v)

u := đỉnh thuộc S có nhãn L(u) nhỏ nhất

{L(u): độ dài đường đi ngắn nhất từ a đến u}

S := S \ {u}

End

Độ phức tạp của thuật toán:

Thuật toán Dijkstra bình thường sẽ có độ phức tạp là O(n^2+m) Tuy nhiên ta có thể sử dụng kết hợp với cấu trúc heap, khi đó độ phức tạp sẽ là O((m+n)\log n), nếu dùng đống Fibonacci thì độ phức tạp giảm xuống còn O(m + n\log n) Trong đó m là số cạnh, n là số đỉnh của đồ thị đang xét

1.3.2 Thuật toán Kruskal

Thuật toán sẽ xây dựng tập cạnh ET của cây khung nhỏ nhất T=(VT, ET) theo từng bước Trước hết sắp xếp các cạnh của đồ thị G theo thứ tự không giảm của trọng số

Bắt đầu từ ET=Rỗng, ở mỗi bước ta sẽ lần lượt duyệt trong danh sách cạnh đã sắp xếp, từ cạnh có độ dài nhỏ đến cạnh có độ dài lớn hơn, để tìm ra cạnh mà việc bổ sung nó vào tập ET không tạo thành chu trình trong tập này

Trang 11

Thuật toán sẽ kết thúc khi ta thu được tập ET gồm n-1 cạnh Cụ thể có thể mô tả như sau:

1 Bắt đầu từ đồ thị rỗng T có n đỉnh

2 Sắp xếp các cạnh của G theo thứ tự không giảm của trọng số

3 Bắt đầu từ cạnh đầu tiên của dãy này, ta cứ thêm dần các cạnh của dãy đã được xếp vào T theo nguyên tắc cạnh thêm vào không được tạo thành chu trình trong T

4 Lặp lại Bước 3 cho đến khi nào số cạnh trong T bằng n-1, ta thu được cây khung nhỏ nhất cần tìm

2 Tìm kiếm có thông tin:

2.1 Tìm kiếm tối ưu (Best First Search)

Mổ tả thuật toán:

Tại mỗi bước của tìm kiếm BFS, ta chọn đi theo trạng thái có khả năng cao nhất trong số các trạng thái đã được xét cho đến thời điểm đó Như vậy, với tiếp cận này, ta sẽ ưu tiên đi vào những nhánh tìm kiếm có khả năng nhất, nhưng ta sẽ không bị lẩn quẩn trong các nhánh này vì nếu càng đi sâu vào một hướng mà ta phát hiện ra rằng hướng này càng đi thì càng tệ, đến mức nó xấu hơn

cả những hướng mà ta chưa đi, thì ta sẽ không đi tiếp hướng hiện tại nữa mà chọn

đi theo một hướng tốt nhất trong số những hướng chưa đi Đó là tư tưởng chủ đạo của tìm kiếm BFS Ví dụ minh họa:

Hình Minh họa thuật giải Best-First Search

Trang 12

Khởi đầu, chỉ có một nút (trạng thái) A nên nó sẽ được mở rộng tạo

ra 3 nút mới B,C và D Các con số dưới nút là giá trị cho biết độ tốt của nút Con

số càng nhỏ, nút càng tốt Do D là nút có khả năng nhất nên nó sẽ được mở rộng tiếp sau nút A và sinh ra 2 nút kế tiếp là E và F Đến đây, ta lại thấy nút B có vẻ

có khả năng nhất (trong các nút B,C,E,F) nên ta sẽ chọn mở rộng nút B và tạo ra 2 nút G và H Nhưng lại một lần nữa, hai nút G, H này được đánh giá ít khả năng hơn E, vì thế sự chú ý lại trở về E E được mở rộng và các nút được sinh ra từ E là

I và J Ở bước kế tiếp, J sẽ được mở rộng vì nó có khả năng nhất Quá trình này tiếp tục cho đến khi tìm thấy một lời giải

Lưu ý rằng tìm kiếm này rất giống với tìm kiếm leo đồi dốc đứng, với 2 ngoại lệ Trong leo núi, một trạng thái được chọn và tất cả các trạng thái khác bị loại bỏ, không bao giờ chúng được xem xét lại Cách xử lý dứt khoát này

là một đặc trưng của leo đồi Trong BFS, tại một bước, cũng có một di chuyển được chọn nhưng những cái khác vẫn được giữ lại, để ta có thể trở lại xét sau đó khi trạng thái hiện tại trở nên kém khả năng hơn những trạng thái đã được lưu trữ Hơn nữa, ta chọn trạng thái tốt nhất mà không quan tâm đến nó có tốt hơn hay không các trạng thái trước đó Điều này tương phản với leo đồi vì leo đồi sẽ dừng nếu không có trạng thái tiếp theo nào tốt hơn trạng thái hiện hành

Để cài đặt các thuật giải theo kiểu tìm kiếm BFS, người ta thường cần dùng 2 tập hợp sau :

OPEN : tập chứa các trạng thái đã được sinh ra nhưng chưa được xét đến (vì ta đã chọn một trạng thái khác) Thực ra, OPEN là một loại hàng đợi ưu tiên (priority queue) mà trong đó, phần tử có độ ưu tiên cao nhất là phần tử tốt nhất Người ta thường cài đặt hàng đợi ưu tiên bằng Heap Các bạn có thể tham khảo thêm trong các tài liệu về Cấu trúc dữ liệu về loại dữ liệu này

CLOSE : tập chứa các trạng thái đã được xét đến Chúng ta cần lưu trữ những trạng thái này trong bộ nhớ để đề phòng trường hợp khi một trạng thái mới được tạo ra lại trùng với một trạng thái mà ta đã xét đến trước đó Trong trường hợp không gian tìm kiếm có dạng cây thì không cần dùng tập này

Thuật giải BEST-FIRST SEARCH

1 Đặt OPEN chứa trạng thái khởi đầu

2 Cho đến khi tìm được trạng thái đích hoặc không còn nút nào trong OPEN, thực hiện :

2.a Chọn trạng thái tốt nhất (Tmax) trong OPEN (và xóa Tmax khỏi OPEN)

2.b Nếu Tmax là trạng thái kết thúc thì thoát

Trang 13

2.c Ngược lại, tạo ra các trạng thái kế tiếp Tk có thể có từ trạng thái Tmax Đối với mỗi trạng thái kế tiếp Tk thực hiện :

Tính f(Tk); Thêm Tk vào OPEN BFS khá đơn giản Tuy vậy, trên thực tế, cũng như tìm kiếm chiều sâu và chiều rộng, hiếm khi ta dùng BFS một cách trực tiếp

2.2 Thuật toán A*

Mô tả thuật toán:

A* lưu giữ một tập các lời giải chưa hoàn chỉnh, nghĩa là các đường

đi qua đồ thị, bắt đầu từ nút xuất phát Tập lời giải này được lưu trong một hàng đợi ưu tiên (priority queue) Thứ tự ưu tiên gán cho một đường đi x được quyết định bởi hàm f(x) = g(x) + h(x)

Trong đó, g(x) là chi phí của đường đi cho đến thời điểm hiện tại, nghĩa là tổng trọng số của các cạnh đã đi qua h(x) là hàm đánh giá heuristic về chi phí nhỏ nhất để đến đích từ x Ví dụ, nếu "chi phí" được tính là khoảng cách

đã đi qua, khoảng cách đường chim bay giữa hai điểm trên một bản đồ là một đánh giá heuristic cho khoảng cách còn phải đi tiếp

Hàm f(x) có giá trị càng thấp thì độ ưu tiên của x càng cao (do đó có thể sử dụng một cấu trúc heap tối thiểu để cài đặt hàng đợi ưu tiên này)

Thuật toán:

function A*(điểm_xuất_phát,đích) var đóng:= tập rỗng

var q:= tạo_hàng_đợi(tạo_đường_đi(điểm_xuất_phát)) while q không phải tập rỗng

var p:= lấy_phần_tử_đầu_tiên(q) var x:= nút cuối cùng của p

if x in đóng continue

if x = đích return p

bổ sung x vào tập đóng foreach y in các_đường_đi_tiếp_theo(p) đưa_vào_hàng_đợi(q, y)

Ngày đăng: 24/08/2015, 07:59

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w