2.4.2.1. Mô hình hóa các bài toán
Công việc truyền thống lý thuyết về thuật toán bắt đầu với một bài toán phát biểu nhƣ sau:
"Cho một tập hợp các điểm trong mặt phẳng ở vị trí tùy ý, tính toán một số cấu trúc X ", nơi cấu trúc X có thể là các bao lồi, Delaunay triangulation, cây tối thiểu Steiner, hoặc tƣơng tự.
Trƣớc khi thiết kế thuật toán, một yêu cầu đặt ra là cần cẩn thận phân tích và hình thức hóa của vấn đề là cần thiết. Đối với các ứng dụng phức tạp, giai đoạn mô hình này là một nhiệm vụ quan trọng và đòi hỏi cao. Việc tìm kiếm hoặc lựa chọn một mô hình thích hợp có thể đảm bảo cho sự thành công của việc tiếp cận thuật toán. Đôi khi ranh giới giữa thời gian đa thức và các vấn đề NP khó đƣợc ẩn trong một chi tiết nhỏ vẻ nhƣ vô hại. Điều này ảnh hƣởng rất nhiều đến các loại phƣơng pháp tiếp cận thuật toán cần cân nhắc trong giai đoạn thiết kế.
2.4.2.2. Thiết kế thuật toán
Sự phát triển của các thuật toán thƣờng đƣợc dựa trên khái niệm trừu tƣợng và đơn giản hóa giả định đối với các mô hình tính toán và tính chất cụ thể của đầu vào. Để chắc chắn rằng một thuật toán thực hiện trong thực tế, cần đảm bảo thuật toán chứa các phần giá trị quan trọng. Các phần bao gồm độ chắc chắn về trị số và các vấn đề không chắc chắn liên quan trong hình học tính toán.
35
Mục đích của việc phân tích thuật toán là để đánh giá các tài nguyên mà các thuật toán yêu cầu.
Đáng tiếc, tất cả các kỹ thuật này luôn có nhƣợc điểm. Phân tích trƣờng hợp xấu nhất thƣờng là quá bi quan đối với các trƣờng hợp xảy ra trong thực tế, trong khi phân tích trƣờng hợp trung bình việc giả định một xác suất phân bố nhất định trên tập hợp các yếu tố đầu vào thƣờng rất khó để lựa chọn trƣờng hợp điển hình mà nó phản ánh. Kỹ nghệ thuật toán là quan tâm đến việc phân tích các thuật toán để biết thêm mô hình đầu vào thực tế.
Nếu chúng ta cho đầu vào bất kỳ, chúng ta phân tích chứng minh cho nhiều thuật toán trƣờng hợp tồi tệ ít nhất. Tuy nhiên, trong thực tế một số các thuật toán có thể thực hiện khá tốt trong khi có khẳng định dự đoán của chúng ta khiêm tốn. Để thu hẹp khoảng cách giữa dự đoán lý thuyết và quan sát thực tế, cần nghiên cứu cấu trúc của các đầu vào kỹ lƣỡng hơn.
2.4.2.4. Mô hình máy tính thực tế
Các mô hình RAM đã rất thành công một mô hình máy tính trong lý thuyết thuật toán. Nhiều phƣơng pháp hiệu quả đã đƣợc thiết kế bằng cách sử dụng mô hình này. Trong khi mô hình RAM là một khái niệm trừu tƣợng hợp lý của máy tính hiện tại nó không phải là một mô hình tốt cho các máy tính hiện đại nữa trong nhiều trƣờng hợp. Các mô hình RAM cơ bản là một máy xử lý duy nhất với bộ nhớ truy cập ngẫu nhiên không giới hạn với chi phí truy cập không đổi. Máy tính hiện đại không có một loại bộ nhớ duy nhất với chi phí truy cập không đổi nữa, nhƣng hệ thống phân cấp bộ nhớ với chi phí tiếp cận rất khác nhau.
Ngày nay các bộ dữ liệu thƣờng rất lớn mà chúng không phù hợp trong bộ nhớ chính của một máy tính. Nghiên cứu thuật toán hiệu quả làm phát sinh mô hình mới cho thiết kế tốt hơn và dự đoán hiệu quả thực tế của các thuật toán khai thác bộ nhớ phân cấp hoặc làm việc với các bộ dữ liệu đòi hỏi phải sử dụng bộ nhớ bên ngoài. Nhƣợc điểm của RAM đối với kiến trúc máy tính hiện đại và mới tốt hơn, các mô hình máy tính thực tế hơn. Đặc biệt, các chƣơng trình bày các mô
36
hình thuật toán bộ nhớ ngoài, I /O- efficiency, cấu trúc dữ liệu bộ nhớ ngoài, và các mô hình các thuật toán khai thác cache.
Hơn nữa, kiến trúc máy tính hiện đại không phải là các máy xử lý đơn nữa. Chúng ta cũng xem xét mô phỏng các thuật toán song song cho thiết kế bộ nhớ ngoài hiệu quả các thuật toán. Các mô hình đƣợc trình bày tất cả các thiếu sót của mô hình RAM và là địa chỉ tin cậy các mô hình thực tế hơn cho các máy tính hiện đại. Tuy nhiên, các mô hình vẫn không cho phép dự đoán hoàn hảo của các hành vi của các thuật toán đƣợc thiết kế cho những mô hình trong thực tế và do đó có thể không làm thí nghiệm không cần thiết.
2.4.2.5. Thực hiện
Thực hiện là mức thấp nhất và thƣờng chiếm nhiều thời gian trong các tiến trình kỹ nghệ thuật toán. Nó liên quan đến mã hóa các kết quả của giai đoạn thiết kế thuật toán trong các ngôn ngữ lập trình đƣợc lựa chọn. Tất nhiên, khi chúng ta bắt đầu với giai đoạn thực hiện, chúng ta giả định rằng thuật toán chúng ta thiết kế là chính xác, trừ khi chúng ta nhằm mục đích cho các thí nghiệm cung cấp cho chúng ta cái nhìn sâu sắc hơn vào sự đúng đắn của một phƣơng pháp.
Đặc biệt là kiểm tra chƣơng trình đã đƣợc chứng minh là rất hữu ích trong Algorithm Engineering.
2.4.2.6. Thư viện
Thƣ viện phần mềm vừa là một công cụ rất hữu ích và cũng là một chủ đề trong Algorithm Engineering. Một thƣ viện cung cấp các thử nghiệm đã đƣợc kiểm tra chính xác và hiệu quả cùng phần mềm thiết kế tốt để tái sử dụng trong các dự án do đó giảm bớt công việc thực hiện của bạn.
Mặt khác, thiết kế và kỹ thuật để tạo một thƣ viện phần mềm tốt là mục tiêu chính của Algorithm Engineering. Thƣ viện phần mềm có khả năng tăng cƣờng chuyển giao công nghệ từ thuật toán cổ điển đến lập trình thực tế, cung cấp thông tin về thuật toán. Kỹ nghệ thuật toán dành cho các thƣ viện phần mềm là khó khăn hơn vì bạn không phải là một nhà tiên nghiệm có thể tiên đoán đƣợc bối cảnh ứng dụng của phần mềm của bạn và do đó bạn không thể điều chỉnh theo hƣớng
37
bối cảnh này. Do đó, tính linh hoạt và khả năng thích ứng là mục tiêu thiết kế quan trọng cho các thƣ viện phần mềm.
Có nhiều thƣ viện phần mềm cho các cấp lập trình khác nhau, từ thƣ viện I/O thông qua các thƣ viện cung cấp các thuật toán cơ bản và cấu trúc dữ liệu để thƣ viện thuật toán cho các tác vụ riêng. Các thƣ viện cấp thấp trƣớc đây thƣờng đƣợc vận chuyển với các trình biên dịch hoặc là một phần của nền tảng phát triển. Thƣ viện cũng có những dạng khác nhau. Đôi khi, các bộ sƣu tập của các phần mềm dành cho các tác vụ có liên quan đã đƣợc gọi là một thƣ viện. Do đó, một tập hợp lỏng lẻo của các chƣơng trình khó sử dụng, không mạch lạc và khó có thể mở rộng.
2.4.2.7. Thí nghiệm
Nhƣ chúng ta đã giải thích trƣớc đây, các thí nghiệm có một vai trò quan trọng trong chu kỳ Algorithm Engineering. Việc thiết kế và lập kế hoạch của các thí nghiệm sâu sắc, các hoạt động của thí nghiệm và đánh giá là những nhiệm vụ thử thách trong đó có chứa nhiều khó khăn không thể lƣờng trƣớc. Jon Bentley đã chỉ ra tại một số lí do mà đôi khi thí nghiệm nhỏ có thể phát hiện ra những hiểu biết đáng ngạc nhiên. Nói chung, thực nghiệm đòi hỏi một số kế hoạch cẩn thận và hệ thống. Bƣớc đầu tiên là xác định các mục tiêu của một thử nghiệm để tìm ra những loại thử nghiệm là cần thiết, những gì để đo lƣờng và những yếu tố nào sẽ đƣợc khám phá. Bƣớc tiếp theo là để chọn trƣờng hợp thử nghiệm phù hợp. Kể từ khi các kết quả đƣợc thử nghiệm trên dữ liệu đầu vào ngẫu nhiên thƣờng rất ít liên quan thực sự cho ứng dụng một nhu cầu chuẩn các bộ thử nghiệm. Nhƣ vậy, tạo ra dữ liệu thử nghiệm và các thiết lập và duy trì kiểm tra các thƣ viện dữ liệu là rất quan trọng.
Một quyết định quan trọng mặc dù thƣờng bị bỏ qua khi tiến hành tất cả thí nghiệm Khoa học Máy tính là đảm bảo khả năng tái sử dụng. Ít nhất điều này có nghĩa là tất cả các yếu tố đó có thể có ảnh hƣởng trực tiếp hoặc gián tiếp vào việc tính toán và sử dụng điều khiển phiên bản để lƣu trữ toàn bộ các môi trƣờng tính toán (bao gồm không chỉ lập trình mã nguồn mà còn là trình biên dịch và các thƣ
38
viện phần mềm bên ngoài). Các bƣớc quan trọng cuối cùng trong quá trình thử nghiệm là để phân tích dữ liệu thu thập đƣợc, để rút ra kết luận bằng các phƣơng pháp thống kê và báo cáo kết quả. Ngƣợc lại với các ngành khoa học tự nhiên và lĩnh vực tƣơng tự nhƣ Toán học, Lập trình và hoạt động nghiên cứu, khoa học máy tính không có truyền thống lâu dài làm thí nghiệm. Mặc dù ít tốn kém hơn nhiều so với trong khoa học tự nhiên, thực nghiệm là một quá trình rất tốn thời gian mà thƣờng đƣợc đánh giá thấp.
2.4.2.8. Kết quả báo cáo của Algorithm Engineering
Đến nay, đã có nhiều ngƣời nổi tiếng, các công ty cạnh tranh cao nhƣ Google Inc., Akamai Technologies, và Nhóm Celera Genomics có vị trí vững chắc của mình trên thị trƣờng đến một mức độ rộng lớn cũng từ Algorithm Engineering.
Một trong những ví dụ ấn tƣợng nhất đối với tiến bộ vững chắc trong năm do Algorithm Engineering là phƣơng pháp chƣơng trình tuyến tính và các thuật toán đơn hình. Chúng ta hãy xem lại ngắn gọn một số sự kiện quan trọng của khả năng để giải quyết Qui hoạch tuyến tính. Năm 1949, khi George B. Dantzig đã phát minh ra thuật toán đơn hình, phải mất 120 ngày công để tính toán bằng tay phƣơng án tối ƣu cho bài toán với 9 ràng buộc và 77 biến. Năm 1952, ngƣời ta có thể giải quyết bài toán với 48 ràng buộc và 71 biến trong 18 giờ tại Cục Tiêu chuẩn Quốc gia. Khoảng hai mƣơi năm sau, vào năm 1970, đã giải đƣợc bài toán qui hoạch tuyến tính với khoảng 4000 ràng buộc và 15.000 biến.
39
CHƯƠNG 3. CÁC CẤU TRÚC DỮ LIỆU VÀ THUẬT TOÁN SSSP KINH ĐIỂN
3.1. Hàng đợi ưu tiên và các cấu trúc Đống 3.1.1. Hàng đợi ưu tiên (Priority queue)
Một kiểu dữ liệu trừu tƣợng nổi tiếng là hàng đợi ƣu tiên. Một hàng đợi ƣu tiên lƣu trữ cặp
, với là một phần tử và là các khóa kết hợp với . Các hàng đợi ƣu tiên trình bày trong luận văn này đều dựa trên những cây nhị phân đƣợc sắp thứ tự, đặc biệt tất cả chúng là đống min. Tóm lại, đống là một cây có gốc, và tất cả các nút đƣợc lƣu trữ theo thứ tự đống, khóa của một nút luôn nhỏ hơn hoặc bằng khóa của các nút con của nó. Do đó, nút gốc luôn lƣu giữ phần tử với khóa nhỏ nhất.
Các hàm hoạt động phổ biến của một hàng đợi ƣu tiên:
insert(e, k): Chèn phần tử e với khóa k vào hàng đợi.
findMin: Trả về phần tử với khóa tối thiểu trong hàng đợi.
deleteMin: Xóa và trả về phần tử với khóa tối thiểu trong hàng đợi.
decreaseKey(e, Δ): Giảm khóa của phần tử bằng . Để thực hiện có hiệu quả, một số hàng đợi cũng đòi hỏi một tham chiếu đến nút đang lƣu trữ e trong hàng đợi.
delete(e): Xóa phần tử khỏi hàng đợi. Một lần nữa, để thực hiệu quả, một số hàng đợi cũng đòi hỏi một tham chiếu đến nút đang lƣu trữ e trong hàng đợi.
meld(Q): Hợp nhất hàng đợi Q vào hàng đợi.
Trong luận văn này, sẽ không cần xóa các phần tử tùy ý cũng không ghép chung đống. Do đó, sẽ không cần các phép toán delete và meld.
Thời gian chạy của đống nhị phân cổ điển đơn giản [16], [12], Đống Fibonacci [9] và Đống D đƣợc thể hiện trong Bảng 1, trong đó là số phần tử trong đống.
40
Operation Đống nhị phân Đống Fibonacci[9] Đống
Worst case Worst case Amortised Amortised Insert O( ) ExtractMin Tính theo tỷ lệ * height = O( ) DecreaseKey O( )
Bảng 3.1.Thời gian thực hiện của đống nhị phân, đống Fibonaccis, và Đống D.
3.1.2. Đống nhị phân
Đống biểu diễn dƣới dạng cây nhị phân hoàn chỉnh tất cả các mức đều đƣợc điền các nút đầy đủ, ngoại trừ một vài trƣờng hợp các mức ở dƣới đƣợc điền từ trái sang phải(có thể chƣa lấp đầy). Độ sâu nhất có thể của cây sẽ là
.
Thứ tự sắp xếp:
- Đống nhỏ nhất (Min Heap): Nếu giá trị nút cha ≤ nút con, nút gốc là nút có giá trị nhỏ nhất.
- Đống lớn nhất (Max Heap): Nếu giá trị nút cha ≥ nút con, nút gốc là nút có giá trị lớn nhất.
Việc hỗ trợ cho các thuật toán tìm đƣờng, các phép toán cơ bản chính của Đống đƣợc sử dụng bao gồm :
Insert: Thêm phần tử x vào Đống nhị phân bằng cách thêm phần tử vào nút mới ở cuối cây tính từ trái sang. So sánh giá trị và hoán đổi vị trí của nó với nút cha mẹ cho đến khi Đống đƣợc sắp xếp theo đúng thứ tự.
Tiến trình thực hiện qua hai bƣớc: 1. Kiểm soát hình dạng của cây. 2. Thiết lập lại trật tự sắp xếp của cây.
DecreaseKey
Thực hiện việc cập nhật giá trị hoặc khóa của nút x đƣợc chọn. Việc thực hiện tiến hành nhƣ sau: Tìm nút x, cập nhật giá trị của nút và tiến hành sắp xếp lại thứ tự của Đống Min tƣơng tự nhƣ Insert.
41
ExtractMin (Delete Min)
Loại bỏ nút x có giá trị nhỏ nhất trong Đống Min. Chúng ta có thể dễ dàng tìm thấy nút x có giá trị nhỏ nhất đó là nút gốc trong Đống Min với thời gian chạy là O (1). Bằng cách hoán đổi giá trị của nút gốc và nút cuối cùng. Loại bỏ nút cuối cùng và sắp xếp lại trật tự cây bằng cách hoán đổi giá trị của nút với các con của nó.
Thời gian chạy của các phép toán
Insert DecreaseKey ExtractMin (Delete Min)
Bảng 3.2. Thời gian chạy của đống nhị phân.
3.1.2. D- Heap (Đống D)
Đống D là một dạng dữ liệu hàng đợi ƣu tiên, là dạng cây của Đống nhị phân nhƣng với số lƣợng d nút con thay vì 2 con nhƣ ban đầu. Tƣơng tự nhƣ Đống nhị phân, Đống D tồn tại Đống lớn nhất và Đống nhỏ nhất. Độ sâu của cây
D với N nút luôn nhỏ hơn hoặc bằng
.
Ngoài ra, Đống có bộ nhớ cache tốt hơn so với Đống Binary mặc dù vẫn tồn tại trƣờng hợp xấu nhất trên lý thuyết. Cây biểu diễn Đống là cây cân bằng trừ mức cuối.
Tƣơng tự nhƣ Đống nhị phân, Đống thực hiện các phép toán tƣơng tự nhƣ trên bao gồm Insert, DecreaseKey, ExtractMin.
Thời gian chạy của các phép toán
Insert DecreaseKey ExtractMin (Delete Min)
O( ) O( ) Tính theo tỷ lệ * height = O( )
Bảng 3. 3. Thời gian chạy của đống D.
3.1.3. Đống Fibonacci (FibonacciHeap)
Đống Fibonacci[9] đƣợc phát triển bởi 2 nhà khoa học Michael L. Fredman and Robert E. Tarjan vào năm 1984 và công bố vào năm 1987. Đống
42
Fibonacci[9] có hiệu suất rất tốt trên lý thuyết và có thể tăng tốc cho thuật toán tìm đƣờng đi ngắn nhất Dijkstra từ
đến
.
Ý tƣởng của đống Fibonacidựa trên đống Binomial nhƣng linh hoạt hơn. Đống Fibonacci[9] là danh sách các cây đã đƣợc sắp xếp giá trị. Không giống nhƣ đống Binomial, các cây trong đống Fibonacci[9] có nút gốc nhƣng không sắp xếp theo vị trị nhất định. Một nút bất kỳ có thể có mức độ lớn hơn 2 nhƣng không lớn hơn O(logn). Kích thƣớc của một cây con bắt nguồn từ nút bậc k là
Fk+2, với Fk là số k của dãy Fibonaci.
Đống Fibonaci luôn duy trì con trỏ về phần tử tối thiểu điều này cho phép phép toán tìm min của đống luôn duy trì thời gian chạy là O(1). Thiết lập các nút đánh dấu (các nút đánh dấu là các nút có nút con đã đƣợc cut bởi phép toán