Để hiểu sự khác biệt giữa thời gian đa thức và thời gian giả phân thức, chúng ta cần bắt đầu bằng cách chính thức hóa "thời gian đa thức" nghĩa là gì.
Trực giác chung cho thời gian đa thức là "thời gian O (n k ) với k nào đó." Ví dụ: sắp xếp lựa chọn chạy trong thời gian O (n 2 ), là thời gian đa thức, trong khi TSP giải quyết bạo lực cần thời gian O (n · n!), Không phải là thời gian đa thức.
Các thời gian chạy này đều tham chiếu đến một số biến n theo dõi kích thước của đầu vào. Ví dụ, trong sắp xếp lựa chọn, n đề cập đến số phần tử trong mảng, trong khi TSP n đề cập đến số lượng nút trong đồ thị. Để chuẩn hóa định nghĩa "n" thực sự có nghĩa là gì trong bối cảnh này, định nghĩa chính thức về độ phức tạp thời gian xác định "kích thước" của một vấn đề như sau:
Kích thước của đầu vào cho một vấn đề là số bit cần thiết để ghi đầu vào đó.
Ví dụ: nếu đầu vào cho một thuật toán sắp xếp là một mảng các số nguyên 32 bit, thì kích thước của đầu vào sẽ là 32n, trong đó n là số mục nhập trong mảng. Trong một đồ thị có n nút và m cạnh, đầu vào có thể được chỉ định là danh sách tất cả các nút, theo sau là danh sách tất cả các cạnh, yêu cầu Ω (n + m) bit.
Với định nghĩa này, định nghĩa chính thức của thời gian đa thức là như sau:
Một thuật toán chạy trong thời gian đa thức nếu thời gian chạy của nó là O (x k ) đối với một hằng số k nào đó, trong đó x biểu thị số bit đầu vào được cung cấp cho thuật toán.
Khi làm việc với các thuật toán xử lý đồ thị, danh sách, cây, v.v., định nghĩa này ít nhiều đồng ý với định nghĩa thông thường. Ví dụ: giả sử bạn có một thuật toán sắp xếp sắp xếp các mảng gồm các số nguyên 32 bit. Nếu bạn sử dụng một cái gì đó như sắp xếp lựa chọn để làm điều này, thời gian chạy, như một hàm của số lượng phần tử đầu vào trong mảng, sẽ là O (n 2 ). Nhưng làm thế nào để n, số phần tử trong mảng đầu vào, tương ứng với số bit đầu vào? Như đã đề cập trước đó, số bit đầu vào sẽ là x = 32n. Do đó, nếu chúng ta biểu thị thời gian chạy của thuật toán theo x thay vì n, chúng ta nhận được rằng thời gian chạy là O (x 2 ), và do đó thuật toán chạy theo thời gian đa thức.
Tương tự, giả sử rằng bạn thực hiện tìm kiếm theo chiều sâu theo trên đồ thị, mất thời gian O (m + n), trong đó m là số cạnh trong đồ thị và n là số nút. Điều này liên quan như thế nào đến số lượng bit đầu vào đã cho? Vâng, nếu chúng ta giả sử rằng đầu vào được chỉ định dưới dạng danh sách kề (danh sách tất cả các nút và cạnh), thì như đã đề cập trước đó số lượng bit đầu vào sẽ là x = Ω (m + n). Do đó, thời gian chạy sẽ là O (x), vì vậy thuật toán chạy trong thời gian đa thức.
Tuy nhiên, mọi thứ bị phá vỡ khi chúng ta bắt đầu nói về các thuật toán hoạt động trên các con số. Chúng ta hãy xem xét vấn đề kiểm tra xem một số có phải là số nguyên tố hay không. Cho một số n, bạn có thể kiểm tra xem n có phải là số nguyên tố hay không bằng cách sử dụng thuật toán sau:
function isPrime(n):
for i from 2 to n - 1:
if (n mod i) = 0, return false
return true
Vậy độ phức tạp về thời gian của mã này là gì? Vâng, vòng lặp bên trong đó chạy O (n) lần và mỗi lần thực hiện một số công việc để tính n mod i (như một giới hạn trên thực sự bảo thủ, điều này chắc chắn có thể được thực hiện trong thời gian O (n 3 )). Do đó, thuật toán tổng thể này chạy trong thời gian O (n 4 ) và có thể nhanh hơn rất nhiều.
Năm 2004, ba nhà khoa học máy tính đã xuất bản một bài báo tên là PRIMES in P đưa ra một thuật toán thời gian đa thức để kiểm tra xem một số có phải là số nguyên tố hay không. Đó được coi là một kết quả mang tính bước ngoặt. Vậy thỏa thuận lớn nào? Không phải chúng ta đã có một thuật toán thời gian đa thức cho việc này, cụ thể là thuật toán ở trên?
Thật không may, chúng tôi không. Hãy nhớ rằng, định nghĩa chính thức về độ phức tạp thời gian nói về độ phức tạp của thuật toán như một hàm của số lượng bit đầu vào. Thuật toán của chúng tôi chạy trong thời gian O (n 4 ), nhưng đó là một hàm của số bit đầu vào là gì? Chà, viết ra số n sẽ lấy O (log n) bit. Do đó, nếu chúng ta đặt x là số bit cần thiết để ghi ra đầu vào n, thì thời gian chạy của thuật toán này thực sự là O (2 4x ), không phải là một đa thức trong x.
Đây là trọng tâm của sự phân biệt giữa thời gian đa thức và thời gian giả đơn thức. Một mặt, thuật toán của chúng ta là O (n 4 ), trông giống như một đa thức, nhưng mặt khác, theo định nghĩa chính thức của thời gian đa thức, nó không phải là thời gian đa thức.
Để có trực giác về lý do tại sao thuật toán không phải là thuật toán thời gian đa thức, hãy suy nghĩ về những điều sau. Giả sử tôi muốn thuật toán phải thực hiện nhiều thao tác. Nếu tôi viết ra một đầu vào như thế này:
10001010101011
thì sẽ mất một khoảng thời gian trong trường hợp xấu nhất T
để hoàn thành. Nếu bây giờ tôi thêm một bit vào cuối số, như thế này:
100010101010111
Thời gian chạy bây giờ (trong trường hợp xấu nhất) sẽ là 2T. Tôi có thể tăng gấp đôi khối lượng công việc mà thuật toán thực hiện chỉ bằng cách thêm một bit nữa!
Một thuật toán chạy trong thời gian giả thức nếu thời gian chạy là một số đa thức trong giá trị số của đầu vào , thay vì số bit cần thiết để biểu diễn nó. Thuật toán kiểm tra cơ bản của chúng tôi là thuật toán thời gian giả đơn thức, vì nó chạy trong thời gian O (n 4 ), nhưng nó không phải là thuật toán thời gian đa thức vì là một hàm của số bit x cần thiết để ghi đầu vào, thời gian chạy là O (2 4x ). Lý do mà bài báo "PRIMES in P" có ý nghĩa quan trọng như vậy là thời gian chạy của nó là (gần đúng) O (log 12 n), theo hàm của số bit là O (x 12 ).
Vậy tại sao điều này lại quan trọng? Chà, chúng ta có nhiều thuật toán thời gian giả phân thức để tính các số nguyên. Tuy nhiên, về mặt kỹ thuật, các thuật toán này là thuật toán thời gian hàm mũ. Điều này rất hữu ích cho mật mã: nếu bạn muốn sử dụng mã hóa RSA, bạn cần phải tin tưởng rằng chúng tôi không thể tính toán các số một cách dễ dàng. Bằng cách tăng số lượng bit trong các con số lên một giá trị lớn (giả sử, 1024 bit), bạn có thể làm cho lượng thời gian mà thuật toán tính toán theo thời gian theo thời gian giả tạo phải lớn đến mức nó sẽ hoàn toàn và hoàn toàn không thể thực hiện được những con số. Mặt khác, nếu chúng ta có thể tìm thấy một đa thức thuật toán bao thanh toán theo thời gian , thì điều này không nhất thiết phải như vậy. Việc thêm nhiều bit hơn có thể khiến công việc tăng lên rất nhiều, nhưng sự tăng trưởng sẽ chỉ là tăng trưởng đa thức, không phải tăng trưởng theo cấp số nhân.
Điều đó nói rằng, trong nhiều trường hợp, các thuật toán thời gian giả đơn thức hoàn toàn ổn vì kích thước của các con số sẽ không quá lớn. Ví dụ, sắp xếp đếm có thời gian chạy O (n + U), trong đó U là số lớn nhất trong mảng. Đây là thời gian giả tạo (vì giá trị số của U yêu cầu ghi ra các bit O (log U), vì vậy thời gian chạy là cấp số nhân với kích thước đầu vào). Nếu chúng ta giới hạn U một cách giả tạo để U không quá lớn (giả sử, nếu chúng ta đặt U là 2), thì thời gian chạy là O (n), thực sự là thời gian đa thức. Đây là cách sắp xếp theo cơ số hoạt động: bằng cách xử lý các số từng bit một, thời gian chạy của mỗi vòng là O (n), do đó thời gian chạy tổng thể là O (n log U). Đây thực sự là thời gian đa thức, vì việc viết ra n số để sắp xếp sử dụng Ω (n) bit và giá trị của log U tỷ lệ thuận với số bit cần thiết để viết ra giá trị lớn nhất trong mảng.
Hi vọng điêu nay co ich!