Tôi muốn càng ít định nghĩa chính thức càng tốt và toán học đơn giản.
Tôi muốn càng ít định nghĩa chính thức càng tốt và toán học đơn giản.
Câu trả lời:
Ghi chú nhanh, điều này gần như chắc chắn gây nhầm lẫn với ký hiệu Big O (là giới hạn trên) với ký hiệu Theta "" (là giới hạn hai mặt). Theo kinh nghiệm của tôi, đây thực sự là điển hình của các cuộc thảo luận trong môi trường phi học thuật. Xin lỗi cho bất kỳ sự nhầm lẫn gây ra.
Độ phức tạp Big O có thể được hình dung bằng biểu đồ này:
Định nghĩa đơn giản nhất tôi có thể đưa ra cho ký hiệu Big-O là:
Ký hiệu Big-O là một đại diện tương đối về độ phức tạp của thuật toán.
Có một số từ quan trọng và được chọn có chủ ý trong câu đó:
- Họ hàng: bạn chỉ có thể so sánh táo với táo. Bạn không thể so sánh một thuật toán để thực hiện phép nhân số học với thuật toán sắp xếp danh sách các số nguyên. Nhưng so sánh hai thuật toán để thực hiện các phép toán số học (một phép nhân, một phép cộng) sẽ cho bạn biết điều gì đó có ý nghĩa;
- đại diện: Big-O (ở dạng đơn giản nhất) làm giảm sự so sánh giữa các thuật toán với một biến duy nhất. Biến đó được chọn dựa trên các quan sát hoặc giả định. Ví dụ, các thuật toán sắp xếp thường được so sánh dựa trên các hoạt động so sánh (so sánh hai nút để xác định thứ tự tương đối của chúng). Điều này giả định rằng so sánh là đắt tiền. Nhưng nếu so sánh là rẻ nhưng trao đổi là đắt? Nó thay đổi sự so sánh; và
- độ phức tạp: nếu tôi mất một giây để sắp xếp 10.000 phần tử, tôi sẽ mất bao lâu để sắp xếp một triệu? Sự phức tạp trong trường hợp này là một biện pháp tương đối với một cái gì đó khác.
Quay lại và đọc lại những điều trên khi bạn đọc phần còn lại.
Ví dụ tốt nhất về Big-O tôi có thể nghĩ đến là làm số học. Lấy hai số (123456 và 789012). Các phép toán số học cơ bản chúng ta đã học ở trường là:
- thêm vào;
- phép trừ;
- phép nhân; và
- phân chia.
Mỗi trong số này là một hoạt động hoặc một vấn đề. Một phương pháp giải quyết những điều này được gọi là một thuật toán .
Việc bổ sung là đơn giản nhất. Bạn xếp các số lên (bên phải) và thêm các chữ số trong một cột viết số cuối cùng của phép cộng đó vào kết quả. Phần 'hàng chục' của số đó được chuyển sang cột tiếp theo.
Chúng ta hãy giả sử rằng việc thêm các số này là hoạt động tốn kém nhất trong thuật toán này. Lý do là để cộng hai số này lại với nhau, chúng ta phải cộng 6 chữ số lại với nhau (và có thể mang số 7). Nếu chúng ta cộng hai số 100 chữ số với nhau, chúng ta phải thực hiện thêm 100 số. Nếu chúng ta thêm hai số 10.000 chữ số, chúng ta phải thực hiện thêm 10.000 số.
Xem mô hình? Độ phức tạp (là số lượng thao tác) tỷ lệ thuận với số chữ số n trong số lớn hơn. Chúng tôi gọi đây là O (n) hoặc độ phức tạp tuyến tính .
Phép trừ cũng tương tự (ngoại trừ bạn có thể cần phải vay thay vì mang theo).
Phép nhân là khác nhau. Bạn xếp các số lên, lấy chữ số đầu tiên ở số dưới cùng và nhân nó lần lượt với từng chữ số trong số trên cùng và cứ thế qua từng chữ số. Vì vậy, để nhân hai số có 6 chữ số của chúng ta, chúng ta phải thực hiện 36 phép nhân. Chúng tôi có thể cần phải thực hiện thêm 10 hoặc 11 cột để có kết quả cuối cùng.
Nếu chúng ta có hai số có 100 chữ số, chúng ta cần thực hiện 10.000 phép nhân và 200 số cộng. Đối với hai số một triệu chữ số, chúng ta cần thực hiện phép nhân một nghìn tỷ (10 12 ) và thêm hai triệu.
Khi thuật toán chia tỷ lệ với n bình phương , đây là O (n 2 ) hoặc độ phức tạp bậc hai . Đây là thời điểm tốt để giới thiệu một khái niệm quan trọng khác:
Chúng tôi chỉ quan tâm đến phần phức tạp nhất.
Người thông minh có thể đã nhận ra rằng chúng ta có thể biểu thị số lượng hoạt động là: n 2 + 2n. Nhưng như bạn đã thấy từ ví dụ của chúng tôi với hai số một triệu chữ số, số hạng thứ hai (2n) trở nên không đáng kể (chiếm 0,0002% tổng số hoạt động trong giai đoạn đó).
Người ta có thể nhận thấy rằng chúng ta đã giả sử trường hợp xấu nhất ở đây. Trong khi nhân số có 6 chữ số, nếu một trong số chúng có 4 chữ số và số còn lại có 6 chữ số, thì chúng tôi chỉ có 24 số nhân. Tuy nhiên, chúng tôi tính toán trường hợp xấu nhất cho trường hợp đó, tức là khi cả hai đều là số có 6 chữ số. Do đó ký hiệu Big-O là về trường hợp xấu nhất của thuật toán.
Ví dụ tốt nhất tiếp theo tôi có thể nghĩ đến là danh bạ điện thoại, thường được gọi là Trang Trắng hoặc tương tự nhưng nó thay đổi theo từng quốc gia. Nhưng tôi đang nói về một danh sách liệt kê mọi người theo họ và sau đó là tên viết tắt hoặc tên, có thể là địa chỉ và sau đó là số điện thoại.
Bây giờ nếu bạn đang hướng dẫn một máy tính tra cứu số điện thoại cho "John Smith" trong một danh bạ điện thoại có chứa 1.000.000 tên, bạn sẽ làm gì? Bỏ qua thực tế là bạn có thể đoán được S đã bắt đầu bao xa (giả sử bạn không thể), bạn sẽ làm gì?
Một thực hiện điển hình có thể được mở ra vào giữa, lấy 500.000 thứ và so sánh nó với "Smith". Nếu đó là "Smith, John", chúng ta đã thực sự may mắn. Nhiều khả năng là "John Smith" sẽ ở trước hoặc sau cái tên đó. Nếu đó là sau khi chúng ta chia nửa cuối của danh bạ điện thoại thành một nửa và lặp lại. Nếu đó là trước thì chúng ta chia nửa đầu của danh bạ điện thoại làm đôi và lặp lại. Và như thế.
Đây được gọi là tìm kiếm nhị phân và được sử dụng hàng ngày trong lập trình cho dù bạn có nhận ra hay không.
Vì vậy, nếu bạn muốn tìm tên trong danh bạ điện thoại gồm một triệu tên, bạn thực sự có thể tìm thấy bất kỳ tên nào bằng cách thực hiện việc này nhiều nhất là 20 lần. Khi so sánh các thuật toán tìm kiếm, chúng tôi quyết định rằng so sánh này là 'n' của chúng tôi.
- Đối với một danh bạ điện thoại gồm 3 tên, phải mất 2 lần so sánh (nhiều nhất).
- Đối với 7 mất tối đa 3.
- Trong 15 phải mất 4.
- Giáo dục
- Đối với 1.000.000 phải mất 20.
Điều đó thật tuyệt vời phải không?
Trong thuật ngữ Big-O, đây là độ phức tạp O (log n) hoặc logarit . Bây giờ logarit trong câu hỏi có thể là ln (cơ sở e), log 10 , log 2 hoặc một số cơ sở khác. Không thành vấn đề, nó vẫn là O (log n) giống như O (2n 2 ) và O (100n 2 ) vẫn là cả O (n 2 ).
Tại thời điểm này đáng để giải thích rằng Big O có thể được sử dụng để xác định ba trường hợp với một thuật toán:
- Trường hợp tốt nhất: Trong tìm kiếm danh bạ điện thoại, trường hợp tốt nhất là chúng tôi tìm thấy tên trong một so sánh. Đây là O (1) hoặc độ phức tạp không đổi ;
- Trường hợp dự kiến: Như đã thảo luận ở trên, đây là O (log n); và
- Trường hợp xấu nhất: Đây cũng là O (log n).
Thông thường chúng ta không quan tâm đến trường hợp tốt nhất. Chúng tôi quan tâm đến trường hợp dự kiến và tồi tệ nhất. Đôi khi một hoặc một trong những điều này sẽ quan trọng hơn.
Quay lại với danh bạ điện thoại.
Nếu bạn có số điện thoại và muốn tìm tên thì sao? Cảnh sát có một danh bạ điện thoại đảo ngược nhưng những cái nhìn như vậy bị từ chối đối với công chúng. Hoặc là họ? Về mặt kỹ thuật, bạn có thể đảo ngược tìm kiếm một số trong một danh bạ điện thoại thông thường. Làm sao?
Bạn bắt đầu từ tên đầu tiên và so sánh số lượng. Nếu đó là một trận đấu, tuyệt vời, nếu không, bạn chuyển sang tiếp theo. Bạn phải làm theo cách này vì danh bạ điện thoại không có thứ tự (dù sao cũng bằng số điện thoại).
Vì vậy, để tìm tên cho số điện thoại (tra cứu ngược):
- Trường hợp tốt nhất: O (1);
- Trường hợp dự kiến: O (n) (với giá 500.000); và
- Trường hợp xấu nhất: O (n) (cho 1.000.000).
Đây là một vấn đề khá nổi tiếng trong khoa học máy tính và đáng được nhắc đến. Trong vấn đề này, bạn có N thị trấn. Mỗi thị trấn được liên kết với 1 hoặc nhiều thị trấn khác bằng một con đường có khoảng cách nhất định. Vấn đề của nhân viên bán hàng du lịch là tìm ra tour du lịch ngắn nhất ghé thăm mọi thị trấn.
Nghe có vẻ đơn giản? Nghĩ lại.
Nếu bạn có 3 thị trấn A, B và C có đường giữa tất cả các cặp thì bạn có thể đi:
- A → B → C
- A → C → B
- B → C → A
- B → A → C
- C → A → B
- C → B → A
Chà, thực ra có ít hơn thế bởi vì một số trong số này là tương đương (A → B → C và C → B → A là tương đương, ví dụ, vì chúng sử dụng cùng một con đường, chỉ ngược lại).
Trong thực tế, có 3 khả năng.
- Mang nó đến 4 thị trấn và bạn có (iirc) 12 khả năng.
- Với 5 là 60.
- 6 trở thành 360.
Đây là một chức năng của một hoạt động toán học được gọi là giai thừa . Về cơ bản:
- 5! = 5 × 4 × 3 × 2 × 1 = 120
- 6! = 6 × 5 × 4 × 3 × 2 × 1 = 720
- 7! = 7 × 6 × 5 × 4 × 3 × 2 × 1 = 5040
- Giáo dục
- 25! = 25 × 24 × ngày × 2 × 1 = 15,511,210,043,330,985,984.000.000
- Giáo dục
- 50! = 50 × 49 × ngày × 2 × 1 = 3.04140932 × 10 64
Vì vậy, vấn đề Big-O của Người bán hàng du lịch là O (n!) Hoặc độ phức tạp giai thừa hoặc tổ hợp .
Vào thời điểm bạn đến 200 thị trấn, không còn đủ thời gian trong vũ trụ để giải quyết vấn đề với máy tính truyền thống.
Đôi điều suy nghĩ.
Một điểm khác tôi muốn đề cập nhanh là bất kỳ thuật toán nào có độ phức tạp O (n a ) được cho là có độ phức tạp đa thức hoặc có thể giải được trong thời gian đa thức .
O (n), O (n 2 ) v.v ... đều là thời gian đa thức. Một số vấn đề không thể được giải quyết trong thời gian đa thức. Một số thứ được sử dụng trên thế giới vì điều này. Mật mã khóa công khai là một ví dụ điển hình. Thật khó để tìm ra hai yếu tố chính của một số lượng rất lớn. Nếu không, chúng tôi không thể sử dụng các hệ thống khóa công khai mà chúng tôi sử dụng.
Dù sao, đó là lời giải thích của tôi (hy vọng là tiếng Anh đơn giản) về Big O (đã được sửa đổi).
Nó cho thấy một thuật toán quy mô dựa trên kích thước đầu vào.
O (n 2 ) : được gọi là độ phức tạp bậc hai
Lưu ý rằng số lượng vật phẩm tăng theo hệ số 10, nhưng thời gian tăng theo hệ số 10 2 . Về cơ bản, n = 10 và do đó O (n 2 ) cung cấp cho chúng ta hệ số tỷ lệ n 2 là 10 2 .
O (n) : được gọi là độ phức tạp tuyến tính
Lần này số lượng vật phẩm tăng theo hệ số 10, và thời gian cũng vậy. n = 10 và vì vậy hệ số tỷ lệ của O (n) là 10.
O (1) : được gọi là độ phức tạp không đổi
Số lượng vật phẩm vẫn tăng theo hệ số 10, nhưng hệ số tỷ lệ của O (1) luôn là 1.
O (log n) : được gọi là độ phức tạp logarit
Số lượng tính toán chỉ được tăng lên bởi một bản ghi của giá trị đầu vào. Vì vậy, trong trường hợp này, giả sử mỗi lần tính toán mất 1 giây, do đó nhật ký của đầu vào n
là thời gian cần thiết, do đó log n
.
Đó là ý chính của nó. Họ giảm các phép toán xuống để nó có thể không chính xác bằng 2 hoặc bất cứ điều gì họ nói, nhưng đó sẽ là yếu tố chi phối trong việc nhân rộng.
Ký hiệu Big-O (còn gọi là ký hiệu "tăng trưởng tiệm cận") là chức năng "trông như thế nào" khi bạn bỏ qua các yếu tố không đổi và các công cụ gần gốc . Chúng tôi sử dụng nó để nói về quy mô điều .
Khái niệm cơ bản
cho "đủ" đầu vào lớn ...
f(x) ∈ O(upperbound)
có nghĩa là f
"phát triển không nhanh hơn"upperbound
f(x) ∈ Ɵ(justlikethis)
có nghĩa là f
"phát triển chính xác như"justlikethis
f(x) ∈ Ω(lowerbound)
có nghĩa là f
"phát triển không chậm hơn"lowerbound
ký hiệu big-O không quan tâm đến các yếu tố không đổi: chức năng 9x²
được cho là "phát triển chính xác như thế nào" 10x²
. Cả ký hiệu tiệm cận big-O cũng không quan tâm đến những thứ không có triệu chứng ("thứ gần nguồn gốc" hoặc "điều gì xảy ra khi kích thước vấn đề nhỏ"): chức năng 10x²
được cho là "phát triển chính xác như" 10x² - x + 2
.
Tại sao bạn muốn bỏ qua các phần nhỏ hơn của phương trình? Bởi vì chúng trở nên hoàn toàn bị lấn át bởi các phần lớn của phương trình khi bạn xem xét quy mô lớn hơn và lớn hơn; đóng góp của họ trở nên lùn và không liên quan. (Xem phần ví dụ.)
Nói cách khác, đó là tất cả về tỷ lệ khi bạn đi đến vô tận. Nếu bạn chia thời gian thực tế cho nó O(...)
, bạn sẽ nhận được một yếu tố không đổi trong giới hạn của đầu vào lớn. Theo trực giác, điều này có ý nghĩa: các hàm "chia tỷ lệ" với nhau nếu bạn có thể nhân cái này để lấy cái kia. Đó là khi chúng ta nói ...
actualAlgorithmTime(N) ∈ O(bound(N))
e.g. "time to mergesort N elements
is O(N log(N))"
... điều này có nghĩa là đối với kích thước bài toán "đủ lớn" N (nếu chúng ta bỏ qua những thứ gần gốc), tồn tại một số hằng số (ví dụ 2.5, hoàn toàn được tạo thành) sao cho:
actualAlgorithmTime(N) e.g. "mergesort_duration(N) "
────────────────────── < constant ───────────────────── < 2.5
bound(N) N log(N)
Có nhiều sự lựa chọn không đổi; thường thì sự lựa chọn "tốt nhất" được gọi là "hệ số không đổi" của thuật toán ... nhưng chúng ta thường bỏ qua nó giống như chúng ta bỏ qua các thuật ngữ không lớn nhất (xem phần Yếu tố không đổi để biết tại sao chúng không quan trọng). Bạn cũng có thể nghĩ phương trình trên là một ràng buộc, nói rằng " Trong trường hợp xấu nhất, thời gian cần thiết sẽ không bao giờ tệ hơn đại khái N*log(N)
, trong phạm vi 2,5 (một yếu tố không đổi chúng tôi không quan tâm nhiều) " .
Nói chung, O(...)
là một trong những hữu ích nhất bởi vì chúng ta thường quan tâm đến hành vi trong trường hợp xấu nhất. Nếu f(x)
đại diện cho một cái gì đó "xấu" như bộ xử lý hoặc sử dụng bộ nhớ, thì " f(x) ∈ O(upperbound)
" có nghĩa là " upperbound
là trường hợp xấu nhất của việc sử dụng bộ xử lý / bộ nhớ".
Các ứng dụng
Là một cấu trúc toán học thuần túy, ký hiệu big-O không giới hạn trong việc nói về thời gian xử lý và bộ nhớ. Bạn có thể sử dụng nó để thảo luận về sự không triệu chứng của bất cứ điều gì mà tỷ lệ có ý nghĩa, chẳng hạn như:
N
những người trong một bữa tiệc ( Ɵ(N²)
cụ thể N(N-1)/2
, nhưng điều quan trọng là nó "quy mô như thế nào" N²
)Thí dụ
Đối với ví dụ bắt tay ở trên, mọi người trong phòng bắt tay người khác. Trong ví dụ đó , #handshakes ∈ Ɵ(N²)
. Tại sao?
Sao lưu một chút: số lần bắt tay chính xác là n-select-2 hoặc N*(N-1)/2
(mỗi người N bắt tay N-1 người khác, nhưng số lần bắt tay này đếm gấp đôi nên chia cho 2):
Tuy nhiên, đối với số lượng người rất lớn, thuật ngữ tuyến tính N
bị lùn và đóng góp hiệu quả 0 vào tỷ lệ (trong biểu đồ: tỷ lệ các ô trống trên đường chéo trên tổng số hộp sẽ nhỏ hơn khi số lượng người tham gia trở nên lớn hơn). Do đó, hành vi mở rộng là order N²
hoặc số lần bắt tay "phát triển như N²".
#handshakes(N)
────────────── ≈ 1/2
N²
Như thể các ô trống trên đường chéo của biểu đồ (dấu kiểm N * (N-1) / 2) thậm chí không có ở đó (N 2 dấu kiểm không có triệu chứng).
(phân tích tạm thời từ "tiếng Anh đơn giản" :) Nếu bạn muốn chứng minh điều này với chính mình, bạn có thể thực hiện một số đại số đơn giản trên tỷ lệ để chia nó thành nhiều thuật ngữ ( lim
có nghĩa là "được xem xét trong giới hạn", chỉ cần bỏ qua nếu bạn chưa thấy nó, đó chỉ là ký hiệu cho "và N thực sự rất lớn"):
N²/2 - N/2 (N²)/2 N/2 1/2
lim ────────── = lim ( ────── - ─── ) = lim ─── = 1/2
N→∞ N² N→∞ N² N² N→∞ 1
┕━━━┙
this is 0 in the limit of N→∞:
graph it, or plug in a really large number for N
tl; dr: Số lần bắt tay 'trông giống như' x² rất nhiều cho các giá trị lớn, nếu chúng ta viết ra tỷ lệ # bắt tay / x², thì thực tế là chúng ta không cần bắt tay x² chính xác sẽ không xuất hiện trong số thập phân trong một thời gian lớn tùy ý.
ví dụ: x = 1million, tỷ lệ # bắt tay / x²: 0.499999 ...
Xây dựng trực giác
Điều này cho phép chúng tôi đưa ra tuyên bố như ...
"Đối với đầu vào đủ lớn = N, bất kể hệ số không đổi là gì, nếu tôi nhân đôi kích thước đầu vào ...
N → (2N) = 2 ( N )
N² → (2N) ² = 4 ( N² )
cN³ → c (2N) = 8 ( cN³ )
c log (N) → c log (2N) = (c log (2)) + ( c log (N) ) = (số lượng cố định) + ( c log (N) )
c * 1 → c * 1
nó nhỏ hơn O (N 1.000001 ), mà bạn có thể sẵn sàng gọi về cơ bản tuyến tính
2 N → 2 2N = (4 N ) ............ đặt cách khác ...... 2 N → 2 N + 1 = 2 N 2 1 = 2 2 N
[đối với toán học nghiêng, bạn có thể di chuột qua các spoilers cho các sidenote nhỏ]
(có tín dụng vào https://stackoverflow.com/a/487292/711085 )
(về mặt kỹ thuật, yếu tố không đổi có thể có thể quan trọng trong một số ví dụ bí truyền hơn, nhưng tôi đã diễn đạt những điều ở trên (ví dụ như trong nhật ký (N)) sao cho không
Đây là những mệnh lệnh tăng trưởng bánh mì mà các lập trình viên và các nhà khoa học máy tính ứng dụng sử dụng làm điểm tham chiếu. Họ thấy những điều này mọi lúc. (Vì vậy, trong khi về mặt kỹ thuật, bạn có thể nghĩ "Nhân đôi đầu vào làm cho thuật toán O (√N) chậm hơn 1,414 lần", tốt hơn nên nghĩ về nó như là "điều này tồi tệ hơn logarit nhưng tốt hơn tuyến tính".)
Các yếu tố không đổi
Thông thường, chúng tôi không quan tâm các yếu tố hằng số cụ thể là gì, vì chúng không ảnh hưởng đến cách thức phát triển của chức năng. Ví dụ, hai thuật toán có thể mất cả O(N)
thời gian để hoàn thành, nhưng một thuật toán có thể chậm gấp đôi so với thuật toán kia. Chúng ta thường không quan tâm quá nhiều trừ khi yếu tố rất lớn vì tối ưu hóa là công việc khó khăn ( Khi nào tối ưu hóa sớm? ); ngoài ra, hành động đơn thuần là chọn một thuật toán có chữ O lớn hơn sẽ thường cải thiện hiệu suất theo các đơn đặt hàng cường độ.
Một số thuật toán vượt trội không có triệu chứng (ví dụ: loại không so sánh O(N log(log(N)))
) có thể có hệ số không đổi lớn (ví dụ 100000*N log(log(N))
) hoặc chi phí tương đối lớn như O(N log(log(N)))
ẩn + 100*N
, chúng hiếm khi được sử dụng ngay cả trên "dữ liệu lớn".
Tại sao O (N) đôi khi là tốt nhất bạn có thể làm, tức là tại sao chúng ta cần cơ sở dữ liệu
O(N)
Các thuật toán theo nghĩa nào đó là thuật toán "tốt nhất" nếu bạn cần đọc tất cả dữ liệu của mình. Chính hành động đọc một loạt dữ liệu là một O(N)
hoạt động. Tải nó vào bộ nhớ thường là O(N)
(hoặc nhanh hơn nếu bạn có hỗ trợ phần cứng hoặc hoàn toàn không có thời gian nếu bạn đã đọc dữ liệu). Tuy nhiên, nếu bạn chạm hoặc thậm chí nhìn vào mọi phần dữ liệu (hoặc thậm chí mọi phần dữ liệu khác), thuật toán của bạn sẽ mất O(N)
thời gian để thực hiện tìm kiếm này. Cho dù thuật toán thực tế của bạn mất bao lâu, thì ít nhất nó cũng sẽ mất O(N)
vì nó đã dành thời gian đó để xem xét tất cả dữ liệu.
Điều tương tự có thể được nói cho chính hành động viết . Tất cả các thuật toán in ra N thứ sẽ mất N thời gian vì đầu ra ít nhất là lâu (ví dụ: in ra tất cả các hoán vị (cách sắp xếp lại) một bộ thẻ chơi N là giai đoạn O(N!)
:).
Điều này thúc đẩy việc sử dụng các cấu trúc dữ liệu : một cấu trúc dữ liệu yêu cầu chỉ đọc dữ liệu một lần (thường là O(N)
thời gian), cộng với một số lượng tiền xử lý tùy ý (ví dụ O(N)
hoặc O(N log(N))
hoặc O(N²)
) mà chúng tôi cố gắng giữ nhỏ. Sau đó, sửa đổi cấu trúc dữ liệu (chèn / xóa / v.v.) và thực hiện các truy vấn trên dữ liệu mất rất ít thời gian, chẳng hạn như O(1)
hoặcO(log(N))
. Sau đó, bạn tiến hành thực hiện một số lượng lớn các truy vấn! Nói chung, bạn càng sẵn sàng làm nhiều việc hơn trước, bạn sẽ phải làm ít việc hơn sau này.
Ví dụ: giả sử bạn có tọa độ kinh độ và vĩ độ của hàng triệu đoạn đường và muốn tìm tất cả các giao lộ trên đường phố.
O(N)
làm việc ngây thơ một lần, nhưng nếu bạn muốn thực hiện nhiều lần (trong trường hợp này, N
lần, một lần cho mỗi phân đoạn), chúng tôi 'phải thực hiện O(N²)
công việc hoặc 1000000² = 1000000000000 hoạt động. Không tốt (một máy tính hiện đại có thể thực hiện khoảng một tỷ thao tác mỗi giây).O(N)
kịp thời mọi thứ . Sau đó, trung bình chỉ mất thời gian liên tục để tìm kiếm thứ gì đó bằng khóa của nó (trong trường hợp này, khóa của chúng tôi là tọa độ vĩ độ và kinh độ, được làm tròn thành một lưới; chúng tôi tìm kiếm các lưới không gian liền kề chỉ có 9, đó là một không thay đổi).O(N²)
thành có thể quản lý được O(N)
và tất cả những gì chúng tôi phải làm là trả một khoản chi phí nhỏ để tạo một bảng băm.Đạo đức của câu chuyện: một cấu trúc dữ liệu cho phép chúng ta tăng tốc hoạt động. Thậm chí, các cấu trúc dữ liệu nâng cao có thể cho phép bạn kết hợp, trì hoãn hoặc thậm chí bỏ qua các hoạt động theo những cách cực kỳ thông minh. Các vấn đề khác nhau sẽ có sự tương tự khác nhau, nhưng tất cả chúng đều liên quan đến việc tổ chức dữ liệu theo cách khai thác một số cấu trúc mà chúng tôi quan tâm hoặc chúng tôi đã áp đặt một cách giả tạo vào sổ sách kế toán. Chúng tôi làm việc trước thời hạn (về cơ bản lập kế hoạch và tổ chức), và bây giờ các nhiệm vụ lặp đi lặp lại dễ dàng hơn nhiều!
Ví dụ thực tế: trực quan hóa các lệnh tăng trưởng trong khi mã hóa
Ký hiệu tiệm cận là, cốt lõi của nó, khá tách biệt với lập trình. Ký hiệu tiệm cận là một khung toán học để suy nghĩ về cách mọi thứ quy mô và có thể được sử dụng trong nhiều lĩnh vực khác nhau. Điều đó nói rằng ... đây là cách bạn áp dụng ký hiệu tiệm cận vào mã hóa.
Khái niệm cơ bản: Bất cứ khi nào chúng ta tương tác với mọi phần tử trong tập hợp kích thước A (chẳng hạn như mảng, tập hợp, tất cả các khóa của bản đồ, v.v.) hoặc thực hiện Lặp lại vòng lặp, đó là hệ số nhân của kích thước A Tại sao tôi nói "một yếu tố nhân"? - bởi vì các vòng lặp và các hàm (gần như theo định nghĩa) có thời gian chạy nhân: số lần lặp, số lần thực hiện trong vòng lặp (hoặc cho các hàm: số lần bạn gọi chức năng, thời gian làm việc trong chức năng). (Điều này đúng nếu chúng ta không làm bất cứ điều gì ưa thích, như bỏ qua các vòng lặp hoặc thoát khỏi vòng lặp sớm hoặc thay đổi luồng điều khiển trong hàm dựa trên các đối số, rất phổ biến.) Dưới đây là một số ví dụ về các kỹ thuật trực quan, với mã giả đi kèm.
(ở đây, x
s đại diện cho các đơn vị công việc thời gian không đổi, hướng dẫn bộ xử lý, opcodes phiên dịch, bất cứ điều gì)
for(i=0; i<A; i++) // A * ...
some O(1) operation // 1
--> A*1 --> O(A) time
visualization:
|<------ A ------->|
1 2 3 4 5 x x ... x
other languages, multiplying orders of growth:
javascript, O(A) time and space
someListOfSizeA.map((x,i) => [x,i])
python, O(rows*cols) time and space
[[r*c for c in range(cols)] for r in range(rows)]
Ví dụ 2:
for every x in listOfSizeA: // A * (...
some O(1) operation // 1
some O(B) operation // B
for every y in listOfSizeC: // C * (...
some O(1) operation // 1))
--> O(A*(1 + B + C))
O(A*(B+C)) (1 is dwarfed)
visualization:
|<------ A ------->|
1 x x x x x x ... x
2 x x x x x x ... x ^
3 x x x x x x ... x |
4 x x x x x x ... x |
5 x x x x x x ... x B <-- A*B
x x x x x x x ... x |
................... |
x x x x x x x ... x v
x x x x x x x ... x ^
x x x x x x x ... x |
x x x x x x x ... x |
x x x x x x x ... x C <-- A*C
x x x x x x x ... x |
................... |
x x x x x x x ... x v
Ví dụ 3:
function nSquaredFunction(n) {
total = 0
for i in 1..n: // N *
for j in 1..n: // N *
total += i*k // 1
return total
}
// O(n^2)
function nCubedFunction(a) {
for i in 1..n: // A *
print(nSquaredFunction(a)) // A^2
}
// O(a^3)
Nếu chúng tôi làm một cái gì đó hơi phức tạp, bạn vẫn có thể tưởng tượng trực quan những gì đang diễn ra:
for x in range(A):
for y in range(1..x):
simpleOperation(x*y)
x x x x x x x x x x |
x x x x x x x x x |
x x x x x x x x |
x x x x x x x |
x x x x x x |
x x x x x |
x x x x |
x x x |
x x |
x___________________|
Ở đây, phác thảo nhỏ nhất có thể nhận ra bạn có thể vẽ là những gì quan trọng; hình tam giác là hình hai chiều (0,5 A ^ 2), giống như hình vuông là hình hai chiều (A ^ 2); yếu tố không đổi của hai ở đây vẫn nằm ở tỷ lệ tiệm cận giữa hai yếu tố, tuy nhiên, chúng tôi bỏ qua nó giống như tất cả các yếu tố ... (Có một số sắc thái đáng tiếc cho kỹ thuật này tôi không đi vào đây; nó có thể đánh lừa bạn.)
Tất nhiên điều này không có nghĩa là các vòng lặp và chức năng là xấu; ngược lại, chúng là các khối xây dựng của các ngôn ngữ lập trình hiện đại và chúng tôi yêu thích chúng. Tuy nhiên, chúng ta có thể thấy rằng cách chúng ta dệt các vòng lặp và các hàm và điều kiện cùng với dữ liệu của chúng ta (luồng điều khiển, v.v.) bắt chước thời gian và không gian sử dụng chương trình của chúng ta! Nếu việc sử dụng thời gian và không gian trở thành một vấn đề, đó là khi chúng ta sử dụng sự thông minh và tìm ra một thuật toán hoặc cấu trúc dữ liệu dễ dàng mà chúng ta đã không cân nhắc, để giảm thứ tự tăng trưởng bằng cách nào đó. Tuy nhiên, các kỹ thuật trực quan này (mặc dù chúng không luôn hoạt động) có thể cho bạn một dự đoán ngây thơ vào thời gian chạy trong trường hợp xấu nhất.
Đây là một điều chúng ta có thể nhận ra một cách trực quan:
<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x
x x x x x x x x
x x x x
x x
x
Chúng ta chỉ có thể sắp xếp lại cái này và thấy nó là O (N):
<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x|x x x x x x x x|x x x x|x x|x
Hoặc có thể bạn thực hiện các lần truyền nhật ký (N) của dữ liệu, với tổng thời gian O (N * log (N)):
<----------------------------- N ----------------------------->
^ x x x x x x x x x x x x x x x x|x x x x x x x x x x x x x x x x
| x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x x x x
lgN x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x
| x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x
v x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x
Không liên quan nhưng đáng nói lại: Nếu chúng tôi thực hiện băm (ví dụ: tra cứu từ điển / hashtable), đó là một yếu tố của O (1). Điều đó khá nhanh.
[myDictionary.has(x) for x in listOfSizeA]
\----- O(1) ------/
--> A*1 --> O(A)
Nếu chúng ta làm một cái gì đó rất phức tạp, chẳng hạn như với hàm đệ quy hoặc thuật toán chia và chinh phục, bạn có thể sử dụng Định lý tổng thể (thường hoạt động) hoặc trong các trường hợp lố bịch Định lý Akra-Bazzi (hầu như luôn hoạt động) bạn tìm kiếm thời gian chạy thuật toán của bạn trên Wikipedia.
Nhưng, các lập trình viên không nghĩ như vậy bởi vì cuối cùng, trực giác thuật toán chỉ trở thành bản chất thứ hai. Bạn sẽ bắt đầu mã hóa một cái gì đó không hiệu quả và ngay lập tức nghĩ rằng "tôi đang làm một cái gì đó không hiệu quả? ". Nếu câu trả lời là "có" VÀ bạn thấy nó thực sự quan trọng, thì bạn có thể lùi lại một bước và nghĩ ra nhiều thủ thuật khác nhau để khiến mọi thứ chạy nhanh hơn (câu trả lời gần như luôn luôn là "sử dụng hàm băm", hiếm khi "sử dụng cây", và rất hiếm khi một cái gì đó phức tạp hơn một chút).
Khấu hao và độ phức tạp trung bình
Ngoài ra còn có khái niệm "khấu hao" và / hoặc "trường hợp trung bình" (lưu ý rằng đây là những trường hợp khác nhau).
Trường hợp trung bình : Điều này không hơn gì việc sử dụng ký hiệu big-O cho giá trị mong đợi của một hàm, thay vì chính hàm đó. Trong trường hợp thông thường khi bạn coi tất cả các yếu tố đầu vào đều có khả năng như nhau, trường hợp trung bình chỉ là mức trung bình của thời gian chạy. Ví dụ với quicksort, mặc dù trường hợp xấu nhất là O(N^2)
đối với một số đầu vào thực sự xấu, trường hợp trung bình là bình thường O(N log(N))
(đầu vào thực sự xấu rất ít về số lượng, rất ít chúng ta không chú ý đến chúng trong trường hợp trung bình).
Trường hợp xấu nhất được khấu hao : Một số cấu trúc dữ liệu có thể có độ phức tạp trong trường hợp xấu nhất lớn, nhưng đảm bảo rằng nếu bạn thực hiện nhiều thao tác này, số lượng công việc trung bình bạn sẽ làm tốt hơn trường hợp xấu nhất. Ví dụ: bạn có thể có cấu trúc dữ liệu thường mất O(1)
thời gian không đổi . Tuy nhiên, đôi khi nó sẽ 'nấc' và mấtO(N)
thời gian cho một thao tác ngẫu nhiên, bởi vì có thể nó cần thực hiện một số sổ sách hoặc thu gom rác hoặc một cái gì đó ... nhưng nó hứa với bạn rằng nếu nó bị trục trặc, nó sẽ không bị nấc nữa hoạt động nhiều hơn. Chi phí trong trường hợp xấu nhất vẫn là O(N)
cho mỗi hoạt động, nhưng chi phí khấu hao trong nhiều lần chạy là O(N)/N
=O(1)
mỗi hoạt động. Bởi vì các hoạt động lớn là đủ hiếm, số lượng lớn công việc không thường xuyên có thể được coi là hòa trộn với phần còn lại của công việc như là một yếu tố không đổi. Chúng tôi nói rằng công việc được "khấu hao" qua một số lượng cuộc gọi đủ lớn mà nó biến mất một cách không có triệu chứng.
Sự tương tự để phân tích khấu hao:
Bạn lái xe ô tô. Thỉnh thoảng, bạn cần dành 10 phút để đến trạm xăng và sau đó dành 1 phút để đổ đầy bình xăng. Nếu bạn đã làm điều này mỗi khi bạn đi bất cứ nơi nào với chiếc xe của bạn (dành 10 phút lái xe đến trạm xăng, dành vài giây để đổ đầy một phần của một gallon), nó sẽ rất không hiệu quả. Nhưng nếu cứ vài ngày bạn lại đổ đầy bình xăng, thì 11 phút lái xe tới trạm xăng sẽ được "khấu hao" trong một số lượng chuyến đi đủ lớn, bạn có thể bỏ qua nó và giả vờ tất cả các chuyến đi của bạn có thể dài hơn 5%.
So sánh giữa trường hợp trung bình và trường hợp xấu nhất được khấu hao:
Mặc dù, nếu bạn lo lắng một cách hợp lý về kẻ tấn công, có rất nhiều vectơ tấn công thuật toán khác phải lo lắng bên cạnh việc khấu hao và trường hợp trung bình.)
Cả trường hợp trung bình và khấu hao đều là những công cụ cực kỳ hữu ích để suy nghĩ và thiết kế với quy mô trong tâm trí.
(Xem Sự khác biệt giữa trường hợp trung bình và phân tích khấu hao nếu quan tâm đến chủ đề phụ này.)
Big-O đa chiều
Hầu hết thời gian, mọi người không nhận ra rằng có nhiều hơn một biến trong công việc. Ví dụ, trong thuật toán tìm kiếm chuỗi, thuật toán của bạn có thể mất thời gian O([length of text] + [length of query])
, tức là nó là tuyến tính theo hai biến như O(N+M)
. Các thuật toán ngây thơ khác có thể O([length of text]*[length of query])
hoặc O(N*M)
. Bỏ qua nhiều biến là một trong những điểm vượt trội nhất mà tôi thấy trong phân tích thuật toán và có thể khiến bạn gặp khó khăn khi thiết kế một thuật toán.
Toàn bộ câu chuyện
Hãy nhớ rằng big-O không phải là toàn bộ câu chuyện. Bạn có thể tăng tốc đáng kể một số thuật toán bằng cách sử dụng bộ đệm, khiến chúng bị lãng quên bộ nhớ cache, tránh tắc nghẽn bằng cách làm việc với RAM thay vì đĩa, sử dụng song song hoặc thực hiện trước thời hạn - các kỹ thuật này thường không phụ thuộc vào thứ tự tăng trưởng Ký hiệu "big-O", mặc dù bạn sẽ thường thấy số lượng lõi trong ký hiệu big-O của các thuật toán song song.
Ngoài ra, hãy nhớ rằng do các ràng buộc ẩn của chương trình của bạn, bạn có thể không thực sự quan tâm đến hành vi tiệm cận. Bạn có thể đang làm việc với một số giá trị giới hạn, ví dụ:
O(N log(N))
quicksort ; bạn muốn sử dụng sắp xếp chèn, điều này xảy ra để thực hiện tốt trên các đầu vào nhỏ. Những tình huống này thường xuất hiện trong các thuật toán phân chia và chinh phục, trong đó bạn chia vấn đề thành các bài toán con nhỏ hơn và nhỏ hơn, chẳng hạn như sắp xếp đệ quy, biến đổi Fourier nhanh hoặc nhân ma trận.Trong thực tế, ngay cả trong số các thuật toán có hiệu suất tiệm cận giống hoặc tương tự nhau, giá trị tương đối của chúng thực sự có thể được điều khiển bởi những thứ khác, chẳng hạn như: các yếu tố hiệu suất khác (quicksort và mergesort đều có O(N log(N))
, nhưng quicksort tận dụng bộ đệm CPU); cân nhắc không thực hiện, như dễ thực hiện; liệu một thư viện có sẵn, và thư viện có uy tín và duy trì như thế nào.
Các chương trình cũng sẽ chạy chậm hơn trên máy tính 500 MHz so với máy tính 2GHz. Chúng tôi không thực sự coi đây là một phần của giới hạn tài nguyên, bởi vì chúng tôi nghĩ về việc nhân rộng về mặt tài nguyên máy (ví dụ: trên mỗi chu kỳ đồng hồ), không phải trên mỗi giây thực. Tuy nhiên, có những điều tương tự có thể 'bí mật' ảnh hưởng đến hiệu suất, chẳng hạn như liệu bạn có đang chạy theo mô phỏng hay trình biên dịch có tối ưu hóa mã hay không. Những điều này có thể làm cho một số thao tác cơ bản mất nhiều thời gian hơn (thậm chí tương đối với nhau) hoặc thậm chí tăng tốc hoặc làm chậm một số thao tác không theo triệu chứng (thậm chí tương đối với nhau). Hiệu quả có thể nhỏ hoặc lớn giữa việc thực hiện và / hoặc môi trường khác nhau. Bạn có chuyển đổi ngôn ngữ hoặc máy móc để thực hiện công việc làm thêm đó không? Điều đó phụ thuộc vào hàng trăm lý do khác (sự cần thiết, kỹ năng, đồng nghiệp, năng suất lập trình viên,
Các vấn đề trên, như ảnh hưởng của việc lựa chọn ngôn ngữ lập trình nào được sử dụng, hầu như không bao giờ được coi là một phần của yếu tố không đổi (cũng không nên như vậy); Tuy nhiên, người ta nên biết về chúng bởi vì đôi khi (mặc dù hiếm khi) chúng có thể ảnh hưởng đến mọi thứ. Ví dụ, trong cpython, việc thực hiện hàng đợi ưu tiên riêng là không tối ưu về mặt không tối ưu ( O(log(N))
thay vì O(1)
lựa chọn chèn hoặc tìm-min); Bạn có sử dụng thực hiện khác? Có lẽ là không, vì việc triển khai C có thể nhanh hơn và có thể có những vấn đề tương tự khác ở nơi khác. Có sự đánh đổi; đôi khi họ quan trọng và đôi khi họ không.
( chỉnh sửa : Giải thích "tiếng Anh đơn giản" kết thúc tại đây.)
Chương trình toán
Để đầy đủ, định nghĩa chính xác của ký hiệu big-O như sau: f(x) ∈ O(g(x))
có nghĩa là "f không có giới hạn trên không giới hạn bởi const * g": bỏ qua mọi thứ bên dưới một giá trị hữu hạn nào đó của x, tồn tại một hằng số như vậy |f(x)| ≤ const * |g(x)|
. (Các ký hiệu khác như sau: giống như O
, Ω
có nghĩa là ≥. Có các biến thể chữ thường: o
có nghĩa là <và ω
có nghĩa là>.) Có f(x) ∈ Ɵ(g(x))
nghĩa là cả f(x) ∈ O(g(x))
và f(x) ∈ Ω(g(x))
(giới hạn trên và dưới của g): tồn tại một số hằng số sao cho f sẽ luôn nằm trong "dải" giữa const1*g(x)
và . (Xin lỗi, tôi đã chọn trì hoãn việc đề cập đến các biểu tượng giá trị tuyệt đối cho đến bây giờ, vì rõ ràng, đặc biệt là vì tôi chưa bao giờ thấy các giá trị tiêu cực xuất hiện trong bối cảnh khoa học máy tính.)const2*g(x)
. Đây là tuyên bố tiệm cận mạnh nhất mà bạn có thể thực hiện và gần tương đương với==
Mọi người sẽ thường sử dụng = O(...)
, có lẽ là ký hiệu 'comp-sci' chính xác hơn và hoàn toàn hợp pháp để sử dụng; "F = O (...)" được đọc "f là thứ tự ... / f bị giới hạn bởi xxx ..." và được cho là "f là một số biểu thức có triệu chứng không có triệu chứng là ...". Tôi được dạy để sử dụng nghiêm ngặt hơn ∈ O(...)
. ∈
có nghĩa là "là một yếu tố của" (vẫn đọc như trước). Trong trường hợp đặc biệt này, O(N²)
chứa các yếu tố như { 2 N²
, 3 N²
, 1/2 N²
, 2 N² + log(N)
, - N² + N^1.9
, ...} và là vô cùng lớn, nhưng nó vẫn còn một bộ.
O và không đối xứng (n = O (n²), nhưng n² không phải là O (n)), nhưng là đối xứng, và (vì các quan hệ này đều là chuyển tiếp và phản xạ) therefore, do đó, là đối xứng và phản xạ và phản xạ và do đó phân vùng tập hợp tất cả các hàm thành các lớp tương đương . Một lớp tương đương là một tập hợp những thứ mà chúng ta coi là giống nhau. Điều đó có nghĩa là, với bất kỳ chức năng nào bạn có thể nghĩ đến, bạn có thể tìm thấy một "đại diện tiệm cận" chính tắc / duy nhất của lớp (bằng cách nói chung là lấy giới hạn ... tôi nghĩ ); giống như bạn có thể nhóm tất cả các số nguyên thành tỷ lệ cược hoặc evens, bạn có thể nhóm tất cả các hàm với Ɵ thành x-ish, log (x) ^ 2-ish, v.v ... bằng cách bỏ qua các thuật ngữ nhỏ hơn (nhưng đôi khi bạn có thể bị mắc kẹt với các hàm phức tạp hơn là các lớp riêng biệt với nhau).
Các =
ký hiệu có thể là một phổ biến hơn và thậm chí được sử dụng trong các giấy tờ do các nhà khoa học máy tính nổi tiếng thế giới. Ngoài ra, thường là trong một khung cảnh bình thường, mọi người sẽ nói O(...)
khi họ có ý nghĩa Ɵ(...)
; điều này đúng về mặt kỹ thuật vì tập hợp các thứ Ɵ(exactlyThis)
là một tập hợp con của O(noGreaterThanThis)
... và nó dễ gõ hơn. ;-)
EDIT: Ghi chú nhanh, điều này gần như chắc chắn gây nhầm lẫn ký hiệu Big O (là giới hạn trên) với ký hiệu Theta (cả hai giới hạn trên và dưới). Theo kinh nghiệm của tôi, đây thực sự là điển hình của các cuộc thảo luận trong môi trường phi học thuật. Xin lỗi cho bất kỳ sự nhầm lẫn gây ra.
Trong một câu: Khi quy mô công việc của bạn tăng lên, mất bao lâu để hoàn thành nó?
Rõ ràng đó chỉ là sử dụng "kích thước" làm đầu vào và "mất thời gian" làm đầu ra - ý tưởng tương tự được áp dụng nếu bạn muốn nói về việc sử dụng bộ nhớ, v.v.
Đây là một ví dụ nơi chúng tôi có áo phông N mà chúng tôi muốn làm khô. Chúng tôi sẽ cho rằng nó cực kỳ nhanh để đưa chúng vào vị trí sấy khô (tức là tương tác của con người là không đáng kể). Đó không phải là trường hợp trong cuộc sống thực, tất nhiên ...
Sử dụng dây phơi bên ngoài: giả sử bạn có sân sau rộng vô hạn, giặt khô trong thời gian O (1). Tuy nhiên, bạn có rất nhiều, nó sẽ có cùng ánh nắng mặt trời và không khí trong lành, vì vậy kích thước không ảnh hưởng đến thời gian sấy.
Sử dụng máy sấy quần áo: bạn đặt 10 chiếc áo vào mỗi tải, và sau đó chúng được thực hiện một giờ sau đó. (Bỏ qua những con số thực tế ở đây - chúng không liên quan.) Vì vậy, làm khô 50 áo sơ mi mất khoảng 5 lần thời gian làm khô 10 chiếc áo sơ mi.
Đặt mọi thứ vào tủ chứa không khí: Nếu chúng ta đặt mọi thứ vào một đống lớn và chỉ để hơi ấm chung làm điều đó, sẽ mất nhiều thời gian để áo sơ mi giữa bị khô. Tôi không muốn đoán chi tiết, nhưng tôi nghi ngờ đây ít nhất là O (N ^ 2) - khi bạn tăng tải giặt, thời gian sấy tăng nhanh hơn.
Một khía cạnh quan trọng của ký hiệu "big O" là nó không cho biết thuật toán nào sẽ nhanh hơn cho một kích thước nhất định. Lấy một hashtable (khóa chuỗi, giá trị số nguyên) so với một mảng các cặp (chuỗi, số nguyên). Là nhanh hơn để tìm một khóa trong hashtable hoặc một phần tử trong mảng, dựa trên một chuỗi? (tức là đối với mảng, "tìm phần tử đầu tiên trong đó phần chuỗi khớp với khóa đã cho.") Hashtables thường được khấu hao (~ = "trung bình") O (1) - khi chúng được thiết lập, sẽ mất khoảng đồng thời để tìm một mục trong bảng 100 mục như trong bảng mục 1.000.000. Tìm một phần tử trong một mảng (dựa trên nội dung chứ không phải chỉ mục) là tuyến tính, tức là O (N) - trung bình, bạn sẽ phải xem xét một nửa các mục.
Điều này có làm cho một hashtable nhanh hơn một mảng để tra cứu không? Không cần thiết. Nếu bạn có một bộ sưu tập các mục rất nhỏ, một mảng có thể nhanh hơn - bạn có thể kiểm tra tất cả các chuỗi trong thời gian cần thiết để tính mã băm của chuỗi bạn đang xem. Tuy nhiên, khi tập dữ liệu phát triển lớn hơn, hashtable cuối cùng sẽ đánh bại mảng.
Big O mô tả giới hạn trên đối với hành vi tăng trưởng của hàm, ví dụ thời gian chạy của chương trình, khi đầu vào trở nên lớn.
Ví dụ:
O (n): Nếu tôi nhân đôi kích thước đầu vào, thời gian chạy tăng gấp đôi
O (n 2 ): Nếu kích thước đầu vào tăng gấp đôi gấp bốn lần thời gian chạy
O (log n): Nếu kích thước đầu vào tăng gấp đôi thời gian chạy tăng thêm một
O (2 n ): Nếu kích thước đầu vào tăng thêm một, thời gian chạy tăng gấp đôi
Kích thước đầu vào thường là không gian tính bằng bit cần thiết để thể hiện đầu vào.
Ký hiệu Big O thường được các lập trình viên sử dụng như một thước đo gần đúng về thời gian tính toán (thuật toán) sẽ mất bao lâu để hoàn thành được biểu thị dưới dạng một hàm của kích thước của tập hợp đầu vào.
Big O rất hữu ích để so sánh mức độ hai thuật toán sẽ tăng lên khi số lượng đầu vào được tăng lên.
Chính xác hơn là ký hiệu Big O được sử dụng để thể hiện hành vi tiệm cận của hàm. Điều đó có nghĩa là cách chức năng hoạt động khi nó tiến đến vô tận.
Trong nhiều trường hợp, "O" của thuật toán sẽ thuộc một trong các trường hợp sau:
Big O bỏ qua các yếu tố không đóng góp một cách có ý nghĩa vào đường cong tăng trưởng của hàm khi kích thước đầu vào tăng dần về phía vô cực. Điều này có nghĩa là các hằng số được thêm vào hoặc nhân với hàm chỉ đơn giản là bị bỏ qua.
Big O chỉ là một cách để "Thể hiện" bản thân bạn theo cách thông thường, "Mất bao nhiêu thời gian / không gian để chạy mã của tôi?".
Bạn có thể thường thấy O (n), O (n 2 ), O (nlogn) và vv, tất cả những điều này chỉ là cách để hiển thị; Làm thế nào để một thuật toán thay đổi?
O (n) có nghĩa là Big O là n, và bây giờ bạn có thể nghĩ, "n là gì!?" Vâng "n" là số lượng phần tử. Hình ảnh bạn muốn tìm kiếm một Mục trong Mảng. Bạn sẽ phải xem xét từng yếu tố và như "Bạn có phải là thành phần / vật phẩm chính xác không?" trong trường hợp xấu nhất, mục nằm ở chỉ mục cuối cùng, điều đó có nghĩa là nó mất nhiều thời gian như có các mục trong danh sách, vì vậy để nói chung, chúng tôi nói "oh hey, n là một lượng giá trị nhất định!" .
Vì vậy, sau đó bạn có thể hiểu "n 2 " nghĩa là gì, nhưng để cụ thể hơn, hãy chơi với ý nghĩ bạn có một thuật toán sắp xếp đơn giản, đơn giản nhất; bong bóng. Thuật toán này cần xem qua toàn bộ danh sách, cho từng mục.
Danh sách của tôi
Dòng chảy ở đây sẽ là:
Đây là O n 2 bởi vì, bạn cần xem tất cả các mục trong danh sách có các mục "n". Đối với mỗi mục, bạn nhìn vào tất cả các mục một lần nữa, để so sánh, đây cũng là "n", vì vậy với mỗi mục, bạn nhìn "n" lần có nghĩa là n * n = n 2
Tôi hy vọng điều này là đơn giản như bạn muốn nó.
Nhưng hãy nhớ rằng, Big O chỉ là một cách để thể hiện bản thân theo cách của thời gian và không gian.
Big O mô tả bản chất mở rộng cơ bản của một thuật toán.
Có rất nhiều thông tin mà Big O không cho bạn biết về một thuật toán nhất định. Nó cắt xương và chỉ cung cấp thông tin về bản chất tỷ lệ của thuật toán, cụ thể là cách sử dụng tài nguyên (thời gian suy nghĩ hoặc bộ nhớ) của một thuật toán để đáp ứng với "kích thước đầu vào".
Hãy xem xét sự khác biệt giữa động cơ hơi nước và tên lửa. Chúng không chỉ đơn thuần là các giống khác nhau của cùng một thứ (như, nói, động cơ Prius so với động cơ Lamborghini) mà chúng là các loại hệ thống đẩy khác nhau đáng kể, ở cốt lõi của chúng. Một động cơ hơi nước có thể nhanh hơn một tên lửa đồ chơi, nhưng không có động cơ pít-tông hơi nước sẽ có thể đạt được tốc độ của một phương tiện phóng quỹ đạo. Điều này là do các hệ thống này có các đặc điểm tỷ lệ khác nhau liên quan đến mối quan hệ của nhiên liệu cần thiết ("sử dụng tài nguyên") để đạt đến một tốc độ nhất định ("kích thước đầu vào").
Tại sao cái này lại quan trọng đến vậy? Bởi vì phần mềm xử lý các vấn đề có thể khác nhau về kích thước theo các yếu tố lên tới cả nghìn tỷ. Hãy xem xét điều đó trong một thời điểm. Tỷ lệ giữa tốc độ cần thiết để di chuyển lên Mặt trăng và tốc độ đi bộ của con người là dưới 10.000: 1, và điều đó hoàn toàn nhỏ so với phạm vi trong phần mềm kích cỡ đầu vào có thể phải đối mặt. Và bởi vì phần mềm có thể phải đối mặt với một phạm vi thiên văn trong các kích thước đầu vào, có khả năng độ phức tạp Big O của thuật toán, đó là bản chất mở rộng cơ bản, để xử lý mọi chi tiết triển khai.
Hãy xem xét ví dụ sắp xếp chính tắc. Sắp xếp bong bóng là O (n 2 ) trong khi hợp nhất sắp xếp là O (n log n). Giả sử bạn có hai ứng dụng sắp xếp, ứng dụng A sử dụng sắp xếp bong bóng và ứng dụng B sử dụng sắp xếp hợp nhất và giả sử rằng đối với kích thước đầu vào của khoảng 30 yếu tố, ứng dụng A nhanh hơn 1.000 lần so với ứng dụng B sắp xếp. Nếu bạn không bao giờ phải sắp xếp nhiều hơn 30 phần tử thì rõ ràng là bạn nên thích ứng dụng A hơn, vì nó nhanh hơn nhiều ở các kích thước đầu vào này. Tuy nhiên, nếu bạn thấy rằng bạn có thể phải sắp xếp mười triệu mục thì điều bạn mong đợi là ứng dụng B thực sự kết thúc nhanh hơn hàng nghìn lần so với ứng dụng A trong trường hợp này, hoàn toàn do cách mỗi thuật toán chia tỷ lệ.
Dưới đây là mục từ tiếng Anh đơn giản mà tôi có xu hướng sử dụng khi giải thích các loại phổ biến của Big-O
Trong mọi trường hợp, thích các thuật toán cao hơn trong danh sách cho các thuật toán thấp hơn trong danh sách. Tuy nhiên, chi phí chuyển sang một lớp phức tạp đắt hơn thay đổi đáng kể.
Ô (1):
Không tăng trưởng. Bất kể vấn đề lớn như thế nào, bạn có thể giải quyết nó trong cùng một khoảng thời gian. Điều này hơi giống với phát sóng trong đó cần một lượng năng lượng tương đương để phát trên một khoảng cách nhất định, bất kể số lượng người nằm trong phạm vi phát sóng.
O (log n ):
Độ phức tạp này giống như O (1) ngoại trừ việc nó chỉ tệ hơn một chút. Đối với tất cả các mục đích thực tế, bạn có thể coi đây là một quy mô không đổi rất lớn. Sự khác biệt trong công việc giữa xử lý 1 nghìn và 1 tỷ mặt hàng chỉ là một yếu tố sáu.
Ô ( n ):
Chi phí giải quyết vấn đề tỷ lệ thuận với quy mô của vấn đề. Nếu vấn đề của bạn tăng gấp đôi kích thước, thì chi phí của giải pháp tăng gấp đôi. Vì hầu hết các vấn đề phải được quét vào máy tính theo một cách nào đó, như nhập dữ liệu, đọc đĩa hoặc lưu lượng mạng, đây thường là một yếu tố mở rộng phải chăng.
O ( n log n ):
Độ phức tạp này rất giống với O ( n ) . Đối với tất cả các mục đích thực tế, hai là tương đương. Mức độ phức tạp này nhìn chung vẫn sẽ được coi là có thể mở rộng. Bằng cách điều chỉnh các giả định, một số thuật toán O ( n log n ) có thể được chuyển đổi thành thuật toán O ( n ) . Ví dụ, giới hạn kích thước của các phím làm giảm việc sắp xếp từ O ( n log n ) đến O ( n ) .
O ( n 2 ):
Mọc như hình vuông, trong đó n là chiều dài cạnh của hình vuông. Đây là tốc độ tăng trưởng tương tự như "hiệu ứng mạng", nơi mọi người trong mạng có thể biết mọi người khác trong mạng. Tăng trưởng là tốn kém. Hầu hết các giải pháp có thể mở rộng không thể sử dụng các thuật toán với mức độ phức tạp này mà không cần thực hiện các bài thể dục quan trọng. Điều này thường áp dụng cho tất cả các phức tạp đa thức khác - O ( n k ) - là tốt.
O (2 n ):
Không quy mô. Bạn không có hy vọng giải quyết bất kỳ vấn đề không tầm thường. Hữu ích cho việc biết những gì cần tránh và cho các chuyên gia để tìm các thuật toán gần đúng trong O ( n k ) .
Big O là thước đo thời gian / không gian thuật toán sử dụng so với kích thước đầu vào của nó.
Nếu một thuật toán là O (n) thì thời gian / không gian sẽ tăng với tốc độ tương tự như đầu vào của nó.
Nếu một thuật toán là O (n 2 ) thì thời gian / không gian tăng theo tốc độ bình phương đầu vào của nó.
và như thế.
Một lời giải thích tiếng Anh đơn giản của Big O là gì? Với ít định nghĩa chính thức nhất có thể và toán học đơn giản.
Giải thích bằng tiếng Anh về sự cần thiết của ký hiệu Big-O:
Khi chúng tôi lập trình, chúng tôi đang cố gắng giải quyết một vấn đề. Những gì chúng tôi mã được gọi là một thuật toán. Ký hiệu Big O cho phép chúng ta so sánh hiệu suất trường hợp xấu hơn của các thuật toán của chúng tôi theo cách được tiêu chuẩn hóa. Thông số kỹ thuật phần cứng thay đổi theo thời gian và những cải tiến trong phần cứng có thể giảm thời gian chạy thuật toán. Nhưng thay thế phần cứng không có nghĩa là thuật toán của chúng tôi tốt hơn hoặc được cải thiện theo thời gian, vì thuật toán của chúng tôi vẫn như vậy. Vì vậy, để cho phép chúng tôi so sánh các thuật toán khác nhau, để xác định xem một thuật toán có tốt hơn hay không, chúng tôi sử dụng ký hiệu Big O.
Một Giải thích Plain tiếng Anh của gì Big O Notation là:
Không phải tất cả các thuật toán đều chạy trong cùng một khoảng thời gian và có thể thay đổi dựa trên số lượng mục trong đầu vào, chúng tôi sẽ gọi n . Dựa trên điều này, chúng tôi xem xét phân tích trường hợp xấu hơn, hoặc giới hạn trên của thời gian chạy khi n ngày càng lớn hơn. Chúng ta phải nhận thức được n là gì , bởi vì nhiều ký hiệu Big O tham chiếu nó.
Rất khó để đo tốc độ của các chương trình phần mềm và khi chúng tôi thử, các câu trả lời có thể rất phức tạp và chứa đầy các trường hợp ngoại lệ và trường hợp đặc biệt. Đây là một vấn đề lớn, bởi vì tất cả những trường hợp ngoại lệ và trường hợp đặc biệt đó đều gây mất tập trung và không có ích khi chúng ta muốn so sánh hai chương trình khác nhau với nhau để tìm ra cái nào là "nhanh nhất".
Do kết quả của sự phức tạp không có ích này, mọi người cố gắng mô tả tốc độ của các chương trình phần mềm bằng cách sử dụng các biểu thức (toán học) nhỏ nhất và phức tạp nhất có thể. Các biểu thức này là các xấp xỉ rất thô: Mặc dù, với một chút may mắn, chúng sẽ nắm bắt được "bản chất" của việc một phần mềm là nhanh hay chậm.
Bởi vì chúng là gần đúng, chúng tôi sử dụng chữ "O" (Big Oh) trong biểu thức, như một quy ước để báo hiệu cho người đọc rằng chúng tôi đang thực hiện một sự đơn giản hóa quá mức. (Và để chắc chắn rằng không ai nhầm tưởng rằng biểu thức đó là chính xác theo bất kỳ cách nào).
Nếu bạn đọc "Oh" có nghĩa là "theo thứ tự" hoặc "xấp xỉ", bạn sẽ không đi quá xa. (Tôi nghĩ rằng sự lựa chọn của Big-Oh có thể là một nỗ lực hài hước).
Điều duy nhất mà các biểu thức "Big-Oh" này cố gắng làm là mô tả mức độ phần mềm chậm lại khi chúng tôi tăng lượng dữ liệu mà phần mềm phải xử lý. Nếu chúng ta nhân đôi lượng dữ liệu cần xử lý, phần mềm có cần gấp đôi thời gian để hoàn thành nó không? Dài gấp mười lần? Trong thực tế, có một số lượng rất lớn các biểu thức big-Oh mà bạn sẽ gặp phải và cần phải lo lắng:
Tốt:
O(1)
Hằng số : Chương trình mất cùng thời gian để chạy cho dù đầu vào có lớn đến đâu.O(log n)
Logarit : Thời gian chạy chương trình chỉ tăng chậm, thậm chí với sự gia tăng lớn về kích thước của đầu vào.Những người xấu:
O(n)
Tuyến tính : Thời gian chạy chương trình tăng tỷ lệ thuận với kích thước của đầu vào.O(n^k)
Đa thức : - Thời gian xử lý tăng nhanh hơn và nhanh hơn - như một hàm đa thức - khi kích thước của đầu vào tăng.... và xấu xí:
O(k^n)
Hàm mũ Thời gian chạy chương trình tăng rất nhanh với mức tăng vừa phải về kích thước của vấn đề - chỉ thực tế khi xử lý các tập dữ liệu nhỏ với các thuật toán theo cấp số nhân.O(n!)
Factorial Chương trình thời gian chạy sẽ lâu hơn bạn có thể đủ khả năng để chờ đợi bất cứ điều gì nhưng bộ dữ liệu rất nhỏ và tầm thường-dường như nhất.O(n log n)
sẽ được coi là tốt.
Một câu trả lời đơn giản có thể là:
Big O đại diện cho thời gian / không gian tồi tệ nhất có thể cho thuật toán đó. Thuật toán sẽ không bao giờ mất nhiều không gian / thời gian hơn giới hạn đó. Big O đại diện cho sự phức tạp thời gian / không gian trong trường hợp cực đoan.
Ok, 2cents của tôi.
Big-O, là tốc độ tăng tài nguyên được tiêu thụ bởi chương trình, kích thước sự cố theo trường hợp
Tài nguyên: Có thể là tổng thời gian CPU, có thể là không gian RAM tối đa. Theo mặc định đề cập đến thời gian CPU.
Nói vấn đề là "Tìm tổng",
int Sum(int*arr,int size){
int sum=0;
while(size-->0)
sum+=arr[size];
return sum;
}
vấn đề-instance = {5,10,15} ==> vấn đề-instance-size = 3, iterations-in-loop = 3
vấn đề-instance = {5,10,15,20,25} ==> vấn đề-instance-size = 5 iterations-in-loop = 5
Đối với đầu vào có kích thước "n", chương trình đang phát triển với tốc độ lặp "n" trong mảng. Do đó Big-O là N được biểu thị là O (n)
Nói vấn đề là "Tìm sự kết hợp",
void Combination(int*arr,int size)
{ int outer=size,inner=size;
while(outer -->0) {
inner=size;
while(inner -->0)
cout<<arr[outer]<<"-"<<arr[inner]<<endl;
}
}
vấn đề-instance = {5,10,15} ==> vấn đề-instance-size = 3, tổng số lần lặp = 3 * 3 = 9
vấn đề-instance = {5,10,15,20,25} ==> vấn đề-instance-size = 5, tổng số lần lặp = 5 * 5 = 25
Đối với đầu vào có kích thước "n", chương trình đang phát triển với tốc độ lặp "n * n" trong mảng. Do đó Big-O là N 2 được biểu thị là O (n 2 )
Ký hiệu Big O là cách mô tả giới hạn trên của thuật toán về không gian hoặc thời gian chạy. N là số phần tử trong bài toán (nghĩa là kích thước của một mảng, số nút trong cây, v.v.) Chúng tôi quan tâm đến việc mô tả thời gian chạy khi n trở nên lớn.
Khi chúng ta nói một số thuật toán là O (f (n)), chúng ta đang nói rằng thời gian chạy (hoặc không gian cần thiết) bởi thuật toán đó luôn thấp hơn một số lần không đổi f (n).
Để nói rằng tìm kiếm nhị phân có thời gian chạy là O (logn) là để nói rằng tồn tại một số hằng số c mà bạn có thể nhân log (n) bởi nó sẽ luôn lớn hơn thời gian chạy của tìm kiếm nhị phân. Trong trường hợp này, bạn sẽ luôn có một số yếu tố so sánh log (n) không đổi.
Nói cách khác, trong đó g (n) là thời gian chạy của thuật toán của bạn, chúng tôi nói rằng g (n) = O (f (n)) khi g (n) <= c * f (n) khi n> k, trong đó c và k là một số hằng số.
" Giải thích tiếng Anh đơn giản về Big O là gì? Với định nghĩa chính thức càng ít càng tốt và toán học đơn giản. "
Một câu hỏi đẹp và đơn giản như vậy ít nhất có vẻ xứng đáng với một câu trả lời ngắn như nhau, giống như một học sinh có thể nhận được trong khi dạy kèm.
Ký hiệu Big O chỉ đơn giản cho biết thời gian * thuật toán có thể chạy trong bao nhiêu, chỉ tính theo lượng dữ liệu đầu vào **.
(* trong một cảm giác tuyệt vời, không có đơn vị thời gian!)
(** đó là điều quan trọng, bởi vì mọi người sẽ luôn muốn nhiều hơn , cho dù họ sống hôm nay hay ngày mai)
Chà, có gì tuyệt vời về ký hiệu Big O nếu đó là những gì nó làm?
Thực tế mà nói, Big O phân tích là rất hữu ích và quan trọng bởi vì Big O đặt trọng tâm thẳng vào của thuật toán riêng phức tạp và hoàn toàn bỏ qua bất cứ điều gì đó là chỉ đơn thuần là một tỉ lệ không đổi như một công cụ JavaScript, tốc độ của CPU, kết nối Internet của bạn, và tất cả những điều đó trở thành một cách nhanh chóng trở nên nực cười lỗi thời như một mô hình T . Big O chỉ tập trung vào hiệu suất theo cách quan trọng ngang với những người sống ở hiện tại hoặc tương lai.
Ký hiệu Big O cũng chiếu rọi trực tiếp vào nguyên tắc quan trọng nhất của lập trình / kỹ thuật máy tính, thực tế truyền cảm hứng cho tất cả các lập trình viên giỏi để tiếp tục suy nghĩ và mơ ước: cách duy nhất để đạt được kết quả vượt ra ngoài công nghệ chậm tiến là phát minh tốt hơn thuật toán .
Ví dụ về thuật toán (Java):
// given a list of integers L, and an integer K
public boolean simple_search(List<Integer> L, Integer K)
{
// for each integer i in list L
for (Integer i : L)
{
// if i is equal to K
if (i == K)
{
return true;
}
}
return false;
}
Mô tả thuật toán:
Thuật toán này tìm kiếm một danh sách, từng mục, tìm kiếm một khóa,
Lặp lại trên từng mục trong danh sách, nếu đó là khóa thì trả về True,
Nếu vòng lặp kết thúc mà không tìm thấy khóa, hãy trả về Sai.
Ký hiệu Big-O đại diện cho giới hạn trên của Độ phức tạp (Thời gian, Không gian, ..)
Để tìm Big-O về độ phức tạp thời gian:
Tính toán thời gian (liên quan đến kích thước đầu vào) trong trường hợp xấu nhất:
Trường hợp xấu nhất: khóa không tồn tại trong danh sách.
Thời gian (Trường hợp xấu nhất) = 4n + 1
Thời gian: O (4n + 1) = O (n) | trong Big-O, hằng số bị bỏ qua
O (n) ~ Tuyến tính
Ngoài ra còn có Big-Omega, đại diện cho sự phức tạp của Trường hợp tốt nhất:
Best-Case: chìa khóa là mục đầu tiên.
Thời gian (Trường hợp tốt nhất) = 4
Thời gian: (4) = O (1) ~ Tức thì \ Hằng
C
sẽ tốt hơn
Ôi lớn
f (x) = O ( g (x)) khi x đi đến a (ví dụ: a = +) có nghĩa là có một hàm k sao cho:
f (x) = k (x) g (x)
k được giới hạn trong một số lân cận của a (nếu a = +, điều này có nghĩa là có các số N và M sao cho với mọi x> N, | k (x) | <M).
Nói cách khác, bằng tiếng Anh đơn giản: f (x) = O ( g (x)), x → a, có nghĩa là trong một vùng lân cận của a, f phân hủy thành tích của g và một số hàm giới hạn.
Nhỏ o
Nhân tiện, đây là để so sánh định nghĩa của o nhỏ.
f (x) = o ( g (x)) khi x đi đến một phương tiện có chức năng k sao cho:
f (x) = k (x) g (x)
k (x) đi về 0 khi x đi đến a.
Ví dụ
sin x = O (x) khi x → 0.
sin x = O (1) khi x → + ∞,
x 2 + x = O (x) khi x → 0,
x 2 + x = O (x 2 ) khi x → + ∞,
ln (x) = o (x) = O (x) khi x → +.
Chú ý! Ký hiệu có dấu bằng "=" sử dụng "đẳng thức giả": đúng là o (g (x)) = O (g (x)), nhưng sai rằng O (g (x)) = o (g (x)). Tương tự, bạn có thể viết "ln (x) = o (x) khi x → +", nhưng công thức "o (x) = ln (x)" sẽ không có ý nghĩa.
Thêm ví dụ
O (1) = O (n) = O (n 2 ) khi n → + (nhưng không phải theo cách khác, đẳng thức là "giả"),
O (n) + O (n 2 ) = O (n 2 ) khi n → +
O (O (n 2 )) = O (n 2 ) khi n → +
O (n 2 ) O (n 3 ) = O (n 5 ) khi n → +
Đây là bài viết Wikipedia: https://en.wikipedia.org/wiki/Big_O_notation
Ký hiệu Big O là cách mô tả thuật toán sẽ chạy nhanh như thế nào với số lượng tham số đầu vào tùy ý, mà chúng ta sẽ gọi là "n". Nó rất hữu ích trong khoa học máy tính vì các máy khác nhau hoạt động ở tốc độ khác nhau và chỉ cần nói rằng thuật toán mất 5 giây không cho bạn biết nhiều vì trong khi bạn có thể đang chạy một hệ thống với bộ xử lý lõi tám 4,5 Ghz, tôi có thể đang chạy một hệ thống 15 năm tuổi, 800 Mhz, có thể mất nhiều thời gian hơn bất kể thuật toán. Vì vậy, thay vì chỉ định thuật toán chạy nhanh như thế nào về mặt thời gian, chúng tôi nói rằng nó chạy nhanh như thế nào về số lượng tham số đầu vào, hoặc "n". Bằng cách mô tả các thuật toán theo cách này, chúng tôi có thể so sánh tốc độ của các thuật toán mà không cần phải tính đến tốc độ của chính máy tính.
Không chắc chắn tôi sẽ tiếp tục đóng góp cho chủ đề này nhưng vẫn nghĩ tôi muốn chia sẻ: Tôi đã từng thấy bài đăng trên blog này có một số giải thích và ví dụ khá hữu ích (mặc dù rất cơ bản) trên Big O:
Thông qua các ví dụ, điều này đã giúp đưa những điều cơ bản trần trụi vào hộp sọ giống như con rùa của tôi, vì vậy tôi nghĩ rằng đó là một bài đọc 10 phút khá tuyệt vời để giúp bạn đi đúng hướng.
Bạn muốn biết tất cả những gì cần biết về O lớn? Tôi cũng thế.
Vì vậy, để nói về chữ O lớn, tôi sẽ sử dụng những từ chỉ có một nhịp trong đó. Một âm thanh trên mỗi từ. Lời nhỏ thì nhanh. Bạn biết những từ này, và tôi cũng vậy. Chúng tôi sẽ sử dụng các từ có một âm thanh. Chúng nhỏ. Tôi chắc chắn bạn sẽ biết tất cả các từ chúng tôi sẽ sử dụng!
Bây giờ, hãy để tôi và bạn nói về công việc. Hầu hết thời gian, tôi không thích làm việc. Bạn có thích làm việc không Nó có thể là trường hợp mà bạn làm, nhưng tôi chắc chắn rằng tôi không.
Tôi không thích đi làm Tôi không thích dành thời gian tại nơi làm việc. Nếu tôi có cách của mình, tôi chỉ muốn chơi và làm những điều thú vị. Bạn có cảm thấy giống như tôi không?
Bây giờ nhiều lúc, tôi phải đi làm. Thật đáng buồn, nhưng sự thật. Vì vậy, khi tôi ở nơi làm việc, tôi có một quy tắc: tôi cố gắng làm ít việc hơn. Gần như không có công việc như tôi có thể. Rồi tôi đi chơi!
Vì vậy, đây là tin tức lớn: O lớn có thể giúp tôi không làm việc! Tôi có thể chơi nhiều thời gian hơn, nếu tôi biết O. Ít làm việc, chơi nhiều hơn! Đó là những gì O lớn giúp tôi làm.
Bây giờ tôi có một số công việc. Tôi có danh sách này: một, hai, ba, bốn, năm, sáu. Tôi phải thêm tất cả những thứ trong danh sách này.
Wow, tôi ghét công việc. Nhưng tốt, tôi phải làm điều này. Vì vậy, tôi đi đây.
Một cộng hai là ba cộng với ba là sáu ... và bốn là ... tôi không biết. Tôi bị lạc. Nó quá khó để tôi làm trong đầu. Tôi không quan tâm nhiều đến loại công việc này.
Vì vậy, chúng ta không làm việc. Hãy để tôi và bạn nghĩ rằng nó khó như thế nào. Tôi phải làm bao nhiêu công việc, để thêm sáu số?
Được rồi để xem. Tôi phải thêm một và hai, rồi thêm nó vào ba, rồi thêm nó vào bốn Tất cả, tôi đếm sáu lần thêm. Tôi phải làm thêm sáu để giải quyết điều này.
Ở đây có chữ O lớn, để cho chúng ta biết toán học này khó đến mức nào.
Big O nói: chúng ta phải thực hiện sáu bổ sung để giải quyết điều này. Thêm một, cho mỗi điều từ một đến sáu. Sáu bit công việc nhỏ ... mỗi bit công việc là một thêm.
Chà, tôi sẽ không làm việc để thêm chúng bây giờ. Nhưng tôi biết nó sẽ khó như thế nào. Nó sẽ là sáu thêm.
Ồ không, bây giờ tôi có nhiều việc hơn. Sheesh. Ai làm ra thứ này?!
Bây giờ họ yêu cầu tôi thêm từ một đến mười! Tại sao tôi phải làm điều đó? Tôi không muốn thêm một đến sáu. Để thêm từ một đến mười thì cũng rất khó!
Nó sẽ khó hơn bao nhiêu? Tôi phải làm thêm bao nhiêu công việc nữa? Tôi có cần nhiều hay ít bước?
Chà, tôi đoán là tôi sẽ phải thực hiện thêm mười lần một cho mỗi thứ từ một đến mười. Mười là hơn sáu. Tôi sẽ phải làm việc nhiều hơn nữa để thêm từ một đến mười, hơn một đến sáu!
Tôi không muốn thêm ngay bây giờ. Tôi chỉ muốn nghĩ về việc khó có thể thêm bao nhiêu. Và, tôi hy vọng, chơi sớm nhất có thể.
Để thêm từ một đến sáu, đó là một số công việc. Nhưng bạn có thấy, để thêm từ một đến mười, đó là công việc nhiều hơn?
Big O là bạn của bạn và của tôi. Big O giúp chúng ta suy nghĩ về bao nhiêu công việc chúng ta phải làm, vì vậy chúng ta có thể lên kế hoạch. Và, nếu chúng ta là bạn của O lớn, anh ấy có thể giúp chúng ta chọn công việc không quá khó!
Bây giờ chúng ta phải làm công việc mới. Ôi không. Tôi không thích công việc này chút nào.
Công việc mới là: thêm tất cả mọi thứ từ một đến n.
Chờ đợi! N là gì Tôi đã bỏ lỡ điều đó? Làm thế nào tôi có thể thêm từ một đến n nếu bạn không cho tôi biết n là gì?
Chà, tôi không biết n là gì. Tôi đã không nói. Là bạn? Không? Ồ tốt Vì vậy, chúng tôi không thể làm việc. Phù
Nhưng mặc dù chúng tôi sẽ không thực hiện công việc bây giờ, chúng tôi có thể đoán nó sẽ khó như thế nào, nếu chúng tôi biết n. Chúng ta sẽ phải thêm n thứ, phải không? Tất nhiên!
Bây giờ đến O lớn, và anh ấy sẽ cho chúng tôi biết công việc này khó khăn như thế nào. Ông nói: để thêm tất cả mọi thứ từ một đến N, từng cái một, là O (n). Để thêm tất cả những điều này, [Tôi biết tôi phải thêm n lần.] [1] Đó là O lớn! Anh ấy nói với chúng tôi khó khăn như thế nào để làm một số loại công việc.
Đối với tôi, tôi nghĩ về O lớn như một ông chủ lớn, chậm chạp. Anh ấy nghĩ về công việc, nhưng anh ấy không làm điều đó. Anh ta có thể nói, "Công việc đó nhanh chóng." Hoặc, anh ta có thể nói, "Công việc đó rất chậm và vất vả!" Nhưng anh ta không làm việc. Anh ấy chỉ nhìn vào công việc, và sau đó anh ấy cho chúng tôi biết nó có thể mất bao nhiêu thời gian.
Tôi quan tâm rất nhiều cho O. Tại sao? Tôi không thích làm việc Không ai thích làm việc. Đó là lý do tại sao tất cả chúng ta yêu O lớn! Anh ấy nói với chúng tôi làm thế nào chúng ta có thể làm việc nhanh chóng. Anh ấy giúp chúng tôi nghĩ về công việc khó khăn như thế nào.
Uh oh, công việc nhiều hơn. Bây giờ, chúng ta đừng làm việc. Nhưng, hãy lập một kế hoạch để thực hiện nó, từng bước một.
Họ đã cho chúng tôi một bộ bài gồm mười lá bài. Tất cả đều bị xáo trộn: bảy, bốn, hai, sáu Lọ không thẳng chút nào. Và bây giờ ... công việc của chúng tôi là sắp xếp chúng.
Ơ. Nghe có vẻ nhiều việc!
Làm thế nào chúng ta có thể sắp xếp bộ bài này? Tôi có một kế hoạch.
Tôi sẽ xem xét từng cặp thẻ, từng cặp, qua bộ bài, từ đầu đến cuối. Nếu thẻ đầu tiên trong một cặp lớn và thẻ tiếp theo trong cặp đó nhỏ, tôi trao đổi chúng. Khác, tôi đi đến cặp tiếp theo, vân vân và vân vân ... và ngay sau đó, bộ bài đã hoàn thành.
Khi bộ bài được hoàn thành, tôi hỏi: tôi đã trao đổi thẻ trong thẻ đó chưa? Nếu vậy, tôi phải làm tất cả một lần nữa, từ đầu.
Tại một số thời điểm, tại một số thời điểm, sẽ không có giao dịch hoán đổi, và loại sàn của chúng tôi sẽ được thực hiện. Quá nhiều việc!
Chà, có bao nhiêu công việc sẽ được, để sắp xếp các thẻ với các quy tắc đó?
Tôi có mười thẻ. Và, hầu hết thời gian - đó là, nếu tôi không gặp nhiều may mắn - tôi phải trải qua toàn bộ bộ bài lên đến mười lần, với tối đa mười lần đổi thẻ mỗi lần qua bộ bài.
O lớn, giúp tôi với!
Big O đến và nói: đối với một cỗ bài n, để sắp xếp nó theo cách này sẽ được thực hiện trong thời gian O (N bình phương).
Tại sao anh ta nói n bình phương?
À, bạn biết n bình phương là n lần n. Bây giờ, tôi đã nhận được nó: n thẻ đã được kiểm tra, cho đến những gì có thể là n lần qua bộ bài. Đó là hai vòng, mỗi vòng có n bước. Đó là n bình phương nhiều việc phải làm. Rất nhiều công việc, chắc chắn!
Bây giờ khi O lớn nói rằng nó sẽ làm cho O (n bình phương) hoạt động, anh ta không có nghĩa là n bình phương thêm vào, trên mũi. Nó có thể là một chút ít, trong một số trường hợp. Nhưng trong trường hợp xấu nhất, nó sẽ ở gần n bước bình phương để sắp xếp bộ bài.
Bây giờ đây là nơi O lớn là bạn của chúng tôi.
Big O chỉ ra điều này: khi n trở nên lớn, khi chúng ta sắp xếp thẻ, công việc sẽ RẤT NHIỀU CỨNG hơn so với công việc cũ chỉ thêm những thứ này. Làm sao chúng ta biết được điều này?
Chà, nếu n trở nên thực sự lớn, chúng ta không quan tâm những gì chúng ta có thể thêm vào n hoặc n bình phương.
Đối với n lớn, n bình phương lớn hơn n.
Big O nói với chúng ta rằng để sắp xếp mọi thứ khó hơn là thêm vào. O (n bình phương) nhiều hơn O (n) cho n lớn. Điều đó có nghĩa là: nếu n trở nên thực sự lớn, để sắp xếp một bộ hỗn hợp gồm n thứ thì PHẢI mất nhiều thời gian hơn là chỉ thêm n thứ hỗn hợp.
Big O không giải quyết công việc cho chúng tôi. Big O cho chúng ta biết công việc khó khăn như thế nào.
Tôi có một bộ bài. Tôi đã sắp xếp chúng. Bạn đã giúp. Cảm ơn.
Có cách nào nhanh hơn để sắp xếp các thẻ? O lớn có thể giúp chúng tôi?
Vâng, có một cách nhanh hơn! Phải mất một chút thời gian để tìm hiểu, nhưng nó hoạt động ... và nó hoạt động khá nhanh. Bạn cũng có thể thử nó, nhưng hãy dành thời gian cho từng bước và không mất vị trí của bạn.
Theo cách mới này để sắp xếp một cỗ bài, chúng tôi không kiểm tra các cặp thẻ như cách chúng tôi đã làm trước đây. Dưới đây là các quy tắc mới của bạn để sắp xếp bộ bài này:
Một: Tôi chọn một thẻ trong phần của bộ bài chúng tôi làm việc bây giờ. Bạn có thể chọn một cho tôi nếu bạn muốn. (Lần đầu tiên chúng tôi làm điều này, dĩ nhiên, một phần của bộ bài chúng tôi làm việc bây giờ là toàn bộ bộ bài.)
Hai: Tôi chơi bài trên thẻ mà bạn đã chọn. Splay này là gì; Làm thế nào để tôi chơi? Chà, tôi đi từ thẻ bắt đầu xuống, từng cái một, và tôi tìm một thẻ cao hơn thẻ splay.
Ba: Tôi đi từ thẻ cuối lên và tôi tìm một thẻ thấp hơn thẻ splay.
Khi tôi đã tìm thấy hai thẻ này, tôi trao đổi chúng và tiếp tục tìm kiếm thêm thẻ để trao đổi. Đó là, tôi quay lại bước Hai và chơi bài bạn chọn thêm.
Tại một số điểm, vòng lặp này (từ Hai đến Ba) sẽ kết thúc. Nó kết thúc khi cả hai nửa của tìm kiếm này gặp nhau tại thẻ splay. Sau đó, chúng tôi vừa chơi bài với thẻ bạn đã chọn trong bước Một. Bây giờ, tất cả các thẻ gần khi bắt đầu thấp hơn thẻ splay; và các thẻ gần cuối cao hơn thẻ splay. Thủ thuật tuyệt vời!
Bốn (và đây là phần thú vị): Bây giờ tôi có hai sàn nhỏ, một thấp hơn thẻ splay và một cao hơn. Bây giờ tôi đi đến bước một, trên mỗi boong nhỏ! Điều đó có nghĩa là, tôi bắt đầu từ bước Một trên boong nhỏ đầu tiên và khi công việc đó hoàn thành, tôi bắt đầu từ bước Một trên boong nhỏ tiếp theo.
Tôi chia bộ bài theo từng phần, và sắp xếp từng phần, nhỏ hơn và nhỏ hơn, và đôi lúc tôi không còn việc gì để làm. Bây giờ điều này có vẻ chậm, với tất cả các quy tắc. Nhưng hãy tin tôi, nó không chậm chút nào. Đó là công việc ít hơn nhiều so với cách đầu tiên để sắp xếp mọi thứ!
Loại này được gọi là gì? Nó được gọi là Sắp xếp nhanh! Loại đó được tạo ra bởi một người đàn ông tên là CAR Hoare và anh ta gọi nó là Sắp xếp nhanh. Bây giờ, Quick Sort được sử dụng mọi lúc!
Sắp xếp nhanh chóng phá vỡ sàn lớn trong những cái nhỏ. Đó là để nói, nó phá vỡ các nhiệm vụ lớn trong những người nhỏ.
Hừm. Có thể có một quy tắc trong đó, tôi nghĩ. Để làm cho các nhiệm vụ lớn nhỏ, phá vỡ chúng.
Loại này khá nhanh. Làm thế nào nhanh? Big O cho chúng ta biết: loại này cần O (n log n) hoạt động, trong trường hợp trung bình.
Là nó nhiều hơn hoặc ít hơn so với loại đầu tiên? O lớn, xin hãy giúp đỡ!
Loại đầu tiên là O (n bình phương). Nhưng Sắp xếp nhanh là O (n log n). Bạn biết rằng n log n nhỏ hơn n bình phương, đối với n lớn, phải không? Chà, đó là cách chúng tôi biết rằng Quick Sort rất nhanh!
Nếu bạn phải sắp xếp một bộ bài, cách tốt nhất là gì? Chà, bạn có thể làm những gì bạn muốn, nhưng tôi sẽ chọn Sắp xếp nhanh.
Tại sao tôi chọn Sắp xếp nhanh? Tôi không thích làm việc, tất nhiên! Tôi muốn công việc được thực hiện ngay khi tôi có thể hoàn thành nó.
Làm thế nào để tôi biết Quick Sort là công việc ít hơn? Tôi biết rằng O (n log n) nhỏ hơn O (n bình phương). Chữ O nhỏ hơn, vì vậy Sắp xếp nhanh là công việc ít hơn!
Bây giờ bạn biết bạn của tôi, Big O. Anh ấy giúp chúng tôi làm ít việc hơn. Và nếu bạn biết O lớn, bạn cũng có thể làm ít việc hơn!
Bạn đã học được tất cả những điều đó với tôi! Bạn thật thông minh! Cảm ơn bạn rất nhiều!
Bây giờ công việc đã xong, hãy đi chơi!
[1]: Có một cách để gian lận và thêm tất cả mọi thứ từ một đến n, tất cả cùng một lúc. Một số đứa trẻ tên Gauss đã phát hiện ra điều này khi anh tám tuổi. Mặc dù tôi không thông minh lắm, vì vậy đừng hỏi tôi làm thế nào .
Tôi có cách đơn giản hơn để hiểu độ phức tạp thời gian mà anh ta đo lường phổ biến nhất để tính độ phức tạp thời gian là ký hiệu Big O. Điều này loại bỏ tất cả các yếu tố không đổi để thời gian chạy có thể được ước tính liên quan đến N khi N tiến đến vô cùng. Nói chung bạn có thể nghĩ về nó như thế này:
statement;
Là hằng số. Thời gian chạy của câu lệnh sẽ không thay đổi liên quan đến N
for ( i = 0; i < N; i++ )
statement;
Là tuyến tính. Thời gian chạy của vòng lặp tỷ lệ thuận với N. Khi N nhân đôi, thời gian chạy cũng vậy.
for ( i = 0; i < N; i++ )
{
for ( j = 0; j < N; j++ )
statement;
}
Là bậc hai. Thời gian chạy của hai vòng tỷ lệ với bình phương của N. Khi N nhân đôi, thời gian chạy tăng thêm N * N.
while ( low <= high )
{
mid = ( low + high ) / 2;
if ( target < list[mid] )
high = mid - 1;
else if ( target > list[mid] )
low = mid + 1;
else break;
}
Là logarit. Thời gian chạy của thuật toán tỷ lệ thuận với số lần N có thể chia cho 2. Điều này là do thuật toán chia vùng làm việc thành một nửa với mỗi lần lặp.
void quicksort ( int list[], int left, int right )
{
int pivot = partition ( list, left, right );
quicksort ( list, left, pivot - 1 );
quicksort ( list, pivot + 1, right );
}
Là N * log (N). Thời gian chạy bao gồm N vòng lặp (lặp hoặc đệ quy) là logarit, do đó thuật toán là sự kết hợp giữa tuyến tính và logarit.
Nói chung, làm một cái gì đó với mọi mục trong một chiều là tuyến tính, làm một cái gì đó với mọi mục trong hai chiều là bậc hai, và chia khu vực làm việc thành một nửa là logarit. Có các biện pháp Big O khác như khối vuông, hàm mũ và căn bậc hai, nhưng chúng gần như không phổ biến. Ký hiệu Big O được mô tả là O () trong đó thước đo. Thuật toán quicksort sẽ được mô tả là O (N * log (N)).
Lưu ý: Không ai trong số này đã tính đến các biện pháp tốt nhất, trung bình và tồi tệ nhất. Mỗi người sẽ có ký hiệu Big O riêng. Cũng lưu ý rằng đây là một lời giải thích đơn giản RẤT. Big O là phổ biến nhất, nhưng nó cũng phức tạp hơn mà tôi đã thể hiện. Ngoài ra còn có các ký hiệu khác như omega lớn, ít o và theta lớn. Bạn có thể sẽ không gặp phải chúng bên ngoài một khóa học phân tích thuật toán.
Giả sử bạn đặt mua Harry Potter: Complete 8-Film Collection [Blu-ray] từ Amazon và tải xuống cùng một bộ sưu tập phim trực tuyến cùng một lúc. Bạn muốn kiểm tra phương pháp nào nhanh hơn. Việc giao hàng mất gần một ngày để đến nơi và việc tải xuống hoàn thành trước đó khoảng 30 phút. Tuyệt quá! Vì vậy, đó là một cuộc đua chặt chẽ.
Điều gì sẽ xảy ra nếu tôi đặt hàng một số phim Blu-ray như Chúa tể của những chiếc nhẫn, Twilight, The Dark Knight Trilogy, v.v. và tải xuống tất cả các bộ phim trực tuyến cùng một lúc? Lần này, việc giao hàng vẫn mất một ngày để hoàn thành, nhưng việc tải xuống trực tuyến mất 3 ngày để hoàn tất. Đối với mua sắm trực tuyến, số lượng mặt hàng đã mua (đầu vào) không ảnh hưởng đến thời gian giao hàng. Đầu ra không đổi. Chúng tôi gọi đây là O (1) .
Để tải xuống trực tuyến, thời gian tải xuống tỷ lệ thuận với kích thước tệp phim (đầu vào). Chúng tôi gọi đây là O (n) .
Từ các thí nghiệm, chúng tôi biết rằng quy mô mua sắm trực tuyến tốt hơn so với tải xuống trực tuyến. Điều rất quan trọng để hiểu ký hiệu O lớn vì nó giúp bạn phân tích khả năng mở rộng và hiệu quả của các thuật toán.
Lưu ý: Ký hiệu Big O đại diện cho trường hợp xấu nhất của thuật toán. Giả sử rằng O (1) và O (n) là các trường hợp xấu nhất của ví dụ trên.
Tham khảo : http://carlcheo.com/compsci
Giả sử chúng ta đang nói về một thuật toán A , nên làm một cái gì đó với một tập dữ liệu có kích thước n .
Có O( <some expression X involving n> )
nghĩa là, bằng tiếng Anh đơn giản:
Nếu bạn không may mắn khi thực hiện A, có thể mất nhiều thao tác X (n) để hoàn thành.
Khi nó xảy ra, có những chức năng nhất định (suy nghĩ của họ như là hiện thực của X (n) ) mà có xu hướng xảy ra khá thường xuyên. Đây là những nổi tiếng và dễ dàng so sánh (ví dụ: 1
, Log N
, N
, N^2
, N!
, vv ..)
Bằng cách so sánh những điều này khi nói về A và các thuật toán khác, thật dễ dàng để xếp hạng các thuật toán theo số lượng hoạt động mà chúng có thể (trường hợp xấu nhất) cần phải hoàn thành.
Nói chung, mục tiêu của chúng tôi sẽ là tìm hoặc cấu trúc một thuật toán A theo cách nó sẽ có một hàm X(n)
trả về số càng thấp càng tốt.
Nếu bạn có một khái niệm phù hợp về sự vô hạn trong đầu, thì có một mô tả rất ngắn gọn:
Ký hiệu Big O cho bạn biết chi phí giải quyết một vấn đề vô cùng lớn.
Và hơn thế nữa
Các yếu tố không đổi là không đáng kể
Nếu bạn nâng cấp lên một máy tính có thể chạy thuật toán của bạn nhanh gấp đôi, ký hiệu O lớn sẽ không nhận thấy điều đó. Cải tiến hệ số không đổi là quá nhỏ để thậm chí được chú ý trong quy mô mà ký hiệu O lớn hoạt động. Lưu ý rằng đây là một phần có chủ ý trong thiết kế ký hiệu O lớn.
Mặc dù bất cứ điều gì "lớn hơn" một yếu tố không đổi có thể được phát hiện, tuy nhiên.
Khi quan tâm đến việc thực hiện các tính toán có kích thước "lớn" đủ để được coi là xấp xỉ vô hạn, thì ký hiệu O lớn là xấp xỉ chi phí giải quyết vấn đề của bạn.
Nếu những điều trên không có ý nghĩa, thì bạn không có một khái niệm trực quan tương thích về sự vô hạn trong đầu, và có lẽ bạn nên bỏ qua tất cả những điều trên; cách duy nhất tôi biết để làm cho những ý tưởng này trở nên nghiêm ngặt hoặc để giải thích chúng nếu chúng không hữu ích bằng trực giác, trước tiên là dạy cho bạn ký hiệu O lớn hoặc một cái gì đó tương tự. (mặc dù, một khi bạn hiểu rõ ký hiệu O lớn trong tương lai, có thể đáng để xem lại những ý tưởng này)
Một lời giải thích bằng tiếng Anh đơn giản về ký hiệu của Big Big O là gì?
Ghi chú rất nhanh:
Chữ O trong "Big O" được gọi là "Thứ tự" (hay chính xác là "thứ tự")
để bạn có thể hiểu ý nghĩa của nó theo nghĩa đen là nó được sử dụng để đặt thứ gì đó để so sánh chúng.
"Big O" thực hiện hai điều:
Notations
.Có bảy ký hiệu được sử dụng nhiều nhất
1
từng bước, thật tuyệt vời, được đặt hàng số 1logN
các bước, tốt, được đặt hàng số 2N
các bước, công bằng của nó, Lệnh số 3O(NlogN)
các bước, điều đó không tốt, Lệnh số 4N^2
các bước, điều đó thật tệ, Đơn hàng số 52^N
các bước, thật kinh khủng, Lệnh số 6N!
các bước, thật tệ, Lệnh số 7Giả sử bạn nhận được ký hiệu O(N^2)
, không chỉ bạn rõ ràng phương pháp thực hiện các bước N * N để hoàn thành một nhiệm vụ, bạn cũng thấy rằng nó không tốt như O(NlogN)
từ xếp hạng của nó.
Vui lòng lưu ý thứ tự ở cuối dòng, chỉ để bạn hiểu rõ hơn. Có hơn 7 ký hiệu nếu tất cả các khả năng được xem xét.
Trong CS, tập hợp các bước để hoàn thành một nhiệm vụ được gọi là thuật toán.
Trong Thuật ngữ, ký hiệu Big O được sử dụng để mô tả hiệu suất hoặc độ phức tạp của thuật toán.
Ngoài ra, Big O thiết lập trường hợp xấu nhất hoặc đo các bước Giới hạn trên.
Bạn có thể tham khảo Big-(Big-Omega) cho trường hợp tốt nhất.
Ký hiệu Big-Ω (Big-Omega) (bài viết) | Học viện Khan
Tóm tắt
"Big O" mô tả hiệu suất của thuật toán và đánh giá nó.
hoặc giải quyết chính thức, "Big O" phân loại các thuật toán và chuẩn hóa quy trình so sánh.
Cách đơn giản nhất để xem xét nó (bằng tiếng Anh)
Chúng tôi đang cố gắng xem số lượng tham số đầu vào, ảnh hưởng đến thời gian chạy của thuật toán. Nếu thời gian chạy ứng dụng của bạn tỷ lệ thuận với số lượng tham số đầu vào, thì nó được gọi là trong Big O của n.
Tuyên bố trên là một khởi đầu tốt nhưng không hoàn toàn đúng.
Một lời giải thích chính xác hơn (toán học)
Giả sử
n = số lượng tham số đầu vào
T (n) = Hàm thực tế biểu thị thời gian chạy của thuật toán là hàm của n
c = một hằng số
f (n) = Hàm gần đúng biểu thị thời gian chạy của thuật toán là hàm của n
Sau đó, theo như Big O có liên quan, phép tính gần đúng f (n) được coi là đủ tốt miễn là điều kiện dưới đây là đúng.
lim T(n) ≤ c×f(n)
n→∞
Phương trình được đọc khi As n tiến đến vô cùng, T của n, nhỏ hơn hoặc bằng c lần f của n.
Trong ký hiệu O lớn, nó được viết là
T(n)∈O(n)
Điều này được đọc là T của n nằm trong O lớn của n.
Trở lại tiếng anh
Dựa trên định nghĩa toán học ở trên, nếu bạn nói thuật toán của bạn là Big O của n, điều đó có nghĩa là nó là hàm của n (số lượng tham số đầu vào) hoặc nhanh hơn . Nếu thuật toán của bạn là Big O của n, thì nó cũng tự động là Big O của n vuông.
Big O của n có nghĩa là thuật toán của tôi chạy ít nhất là nhanh như thế này. Bạn không thể nhìn vào ký hiệu Big O của thuật toán của bạn và nói nó chậm. Bạn chỉ có thể nói nhanh thôi.
Kiểm tra điều này để xem hướng dẫn bằng video về Big O từ UC Berkley. Đó thực sự là một khái niệm đơn giản. Nếu bạn nghe giáo sư Shewchuck (còn gọi là giáo viên cấp Chúa) giải thích về nó, bạn sẽ nói "Ồ đó là tất cả!".
Tôi tìm thấy một lời giải thích thực sự tuyệt vời về ký hiệu O lớn, đặc biệt là đối với một người không biết nhiều về toán học.
https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/
Ký hiệu Big O được sử dụng trong Khoa học máy tính để mô tả hiệu suất hoặc độ phức tạp của thuật toán. Big O mô tả cụ thể kịch bản trường hợp xấu nhất và có thể được sử dụng để mô tả thời gian thực hiện cần thiết hoặc không gian được sử dụng (ví dụ trong bộ nhớ hoặc trên đĩa) bằng thuật toán.
Bất cứ ai đọc Ngọc trai lập trình hoặc bất kỳ cuốn sách Khoa học máy tính nào khác và không có nền tảng về Toán học đều sẽ gặp phải khó khăn khi họ đạt được các chương có đề cập đến O (N log N) hoặc cú pháp có vẻ điên rồ khác. Hy vọng bài viết này sẽ giúp bạn hiểu được những điều cơ bản của Big O và Logarit.
Là một lập trình viên đầu tiên và là một nhà toán học thứ hai (hoặc có thể là thứ ba hoặc thứ tư), tôi đã tìm ra cách tốt nhất để hiểu kỹ về Big O là tạo ra một số ví dụ trong mã. Vì vậy, dưới đây là một số thứ tự tăng trưởng phổ biến cùng với các mô tả và ví dụ nếu có thể.
Ô (1)
O (1) mô tả một thuật toán sẽ luôn luôn thực thi trong cùng một thời gian (hoặc không gian) bất kể kích thước của tập dữ liệu đầu vào.
bool IsFirstElementNull(IList<string> elements) { return elements[0] == null; }
TRÊN)
O (N) mô tả một thuật toán có hiệu suất sẽ tăng tuyến tính và tỷ lệ thuận với kích thước của tập dữ liệu đầu vào. Ví dụ dưới đây cũng cho thấy Big O ủng hộ kịch bản hiệu suất trong trường hợp xấu nhất như thế nào; một chuỗi phù hợp có thể được tìm thấy trong bất kỳ lần lặp nào của vòng lặp for và hàm sẽ quay lại sớm, nhưng ký hiệu Big O sẽ luôn giả sử giới hạn trên trong đó thuật toán sẽ thực hiện số lần lặp tối đa.
bool ContainsValue(IList<string> elements, string value) { foreach (var element in elements) { if (element == value) return true; } return false; }
O (N 2 )
O (N 2 ) đại diện cho một thuật toán có hiệu suất tỷ lệ thuận với bình phương kích thước của tập dữ liệu đầu vào. Điều này là phổ biến với các thuật toán liên quan đến các lần lặp lồng nhau trên tập dữ liệu. Lặp lại lồng nhau sâu hơn sẽ dẫn đến O (N 3 ), O (N 4 ), v.v.
bool ContainsDuplicates(IList<string> elements) { for (var outer = 0; outer < elements.Count; outer++) { for (var inner = 0; inner < elements.Count; inner++) { // Don't compare with self if (outer == inner) continue; if (elements[outer] == elements[inner]) return true; } } return false; }
Ô (2 N )
O (2 N ) biểu thị một thuật toán có tốc độ tăng gấp đôi với mỗi additon cho tập dữ liệu đầu vào. Đường cong tăng trưởng của hàm O (2 N ) là theo cấp số nhân - bắt đầu rất nông, sau đó tăng lên nhanh chóng. Một ví dụ về hàm O (2 N ) là phép tính đệ quy của các số Fibonacci:
int Fibonacci(int number) { if (number <= 1) return number; return Fibonacci(number - 2) + Fibonacci(number - 1); }
Logarit
Logarit hơi khó giải thích hơn nên tôi sẽ sử dụng một ví dụ phổ biến:
Tìm kiếm nhị phân là một kỹ thuật được sử dụng để tìm kiếm các tập dữ liệu được sắp xếp. Nó hoạt động bằng cách chọn phần tử ở giữa của tập dữ liệu, về cơ bản là trung vị và so sánh nó với giá trị đích. Nếu các giá trị khớp với nó sẽ trả về thành công. Nếu giá trị đích cao hơn giá trị của phần tử thăm dò, nó sẽ lấy nửa trên của tập dữ liệu và thực hiện thao tác tương tự với nó. Tương tự, nếu giá trị đích thấp hơn giá trị của phần tử thăm dò, nó sẽ thực hiện thao tác so với nửa dưới. Nó sẽ tiếp tục giảm một nửa tập dữ liệu với mỗi lần lặp cho đến khi giá trị được tìm thấy hoặc cho đến khi nó không thể phân tách tập dữ liệu nữa.
Loại thuật toán này được mô tả là O (log N). Việc lặp đi lặp lại một nửa các bộ dữ liệu được mô tả trong ví dụ tìm kiếm nhị phân tạo ra một đường cong tăng trưởng đạt cực đại ở đầu và từ từ làm phẳng khi kích thước của các tập dữ liệu tăng, ví dụ: một tập dữ liệu đầu vào chứa 10 mục cần một giây để hoàn thành, một tập dữ liệu chứa 100 mục mất hai giây và một bộ dữ liệu chứa 1000 mục sẽ mất ba giây. Nhân đôi kích thước của tập dữ liệu đầu vào ít ảnh hưởng đến sự tăng trưởng của nó vì sau một lần lặp thuật toán, tập dữ liệu sẽ bị giảm một nửa và do đó ngang bằng với một tập dữ liệu đầu vào bằng một nửa kích thước. Điều này làm cho các thuật toán như tìm kiếm nhị phân cực kỳ hiệu quả khi xử lý các tập dữ liệu lớn.
Đây là một lời giải thích rất đơn giản, nhưng tôi hy vọng nó bao gồm hầu hết các chi tiết quan trọng.
Giả sử thuật toán của bạn xử lý vấn đề phụ thuộc vào một số 'yếu tố', ví dụ: hãy biến nó thành N và X.
Tùy thuộc vào N và X, thuật toán của bạn sẽ yêu cầu một số thao tác, ví dụ như trong trường hợp LÀM VIỆC, đó là 3(N^2) + log(X)
hoạt động.
Vì Big-O không quan tâm quá nhiều đến yếu tố không đổi (hay còn gọi là 3), nên Big-O trong thuật toán của bạn là O(N^2 + log(X))
. Về cơ bản, nó dịch 'số lượng hoạt động mà thuật toán của bạn cần cho các trường hợp xấu nhất theo tỷ lệ này'.
thuật toán : thủ tục / công thức để giải quyết vấn đề
Làm thế nào để phân tích các thuật toán và làm thế nào chúng ta có thể so sánh các thuật toán với nhau?
ví dụ: bạn và một người bạn được yêu cầu tạo một hàm để tính tổng các số từ 0 đến N. Bạn đến với f (x) và bạn của bạn đưa ra g (x). Cả hai hàm đều có cùng kết quả, nhưng thuật toán khác nhau. Để so sánh khách quan hiệu quả của các thuật toán, chúng tôi sử dụng ký hiệu Big-O .
Ký hiệu Big-O: mô tả thời gian chạy sẽ tăng nhanh như thế nào so với đầu vào khi đầu vào trở nên lớn tùy ý.
3 điểm chính:
Độ phức tạp không gian: ngoài độ phức tạp thời gian, chúng tôi cũng quan tâm đến độ phức tạp của không gian (thuật toán sử dụng bao nhiêu bộ nhớ / không gian). Thay vì kiểm tra thời gian hoạt động, chúng tôi kiểm tra kích thước phân bổ bộ nhớ.