คนส่วนใหญ่ที่มีปริญญาใน CS จะรู้แน่นอนว่าBig O หมายถึงอะไร มันช่วยให้เราวัดว่าอัลกอริทึมปรับขนาดได้ดีแค่ไหน
แต่ฉันอยากรู้คุณจะคำนวณหรือประมาณความซับซ้อนของอัลกอริทึมของคุณได้อย่างไร
คนส่วนใหญ่ที่มีปริญญาใน CS จะรู้แน่นอนว่าBig O หมายถึงอะไร มันช่วยให้เราวัดว่าอัลกอริทึมปรับขนาดได้ดีแค่ไหน
แต่ฉันอยากรู้คุณจะคำนวณหรือประมาณความซับซ้อนของอัลกอริทึมของคุณได้อย่างไร
คำตอบ:
ฉันจะพยายามอย่างเต็มที่ที่จะอธิบายให้ฟังโดยใช้คำง่าย ๆ แต่ได้รับการเตือนว่าหัวข้อนี้ต้องใช้เวลาสองสามเดือนที่นักเรียนของฉันจะเข้าใจ คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับบทที่ 2 ของโครงสร้างข้อมูลและอัลกอริทึมในหนังสือJava
ไม่มีกระบวนการทางกลที่สามารถใช้เพื่อรับ BigOh
ในฐานะ "ตำรา" ในการรับBigOhจากโค้ดบางส่วนคุณต้องรู้ก่อนว่าคุณกำลังสร้างสูตรคณิตศาสตร์เพื่อนับจำนวนขั้นตอนการคำนวณที่ได้รับจากการป้อนข้อมูลในบางขนาด
จุดประสงค์คือง่าย ๆ : เพื่อเปรียบเทียบอัลกอริธึมจากมุมมองเชิงทฤษฎีโดยไม่จำเป็นต้องรันโค้ด ยิ่งจำนวนขั้นตอนน้อยลงเท่าใด
ตัวอย่างเช่นสมมติว่าคุณมีรหัสชิ้นนี้:
int sum(int* data, int N) {
int result = 0; // 1
for (int i = 0; i < N; i++) { // 2
result += data[i]; // 3
}
return result; // 4
}
ฟังก์ชันนี้ส่งคืนผลรวมขององค์ประกอบทั้งหมดของอาร์เรย์และเราต้องการสร้างสูตรเพื่อนับความซับซ้อนในการคำนวณของฟังก์ชันนั้น:
Number_Of_Steps = f(N)
ดังนั้นเราจึงมีf(N)
ฟังก์ชั่นเพื่อนับจำนวนขั้นตอนการคำนวณ อินพุตของฟังก์ชันคือขนาดของโครงสร้างที่จะประมวลผล มันหมายความว่าฟังก์ชั่นนี้เรียกว่าเช่น:
Number_Of_Steps = f(data.length)
พารามิเตอร์N
รับdata.length
ค่า f()
ตอนนี้เราต้องนิยามที่แท้จริงของฟังก์ชั่น สิ่งนี้ทำจากซอร์สโค้ดซึ่งแต่ละบรรทัดที่น่าสนใจจะมีหมายเลขตั้งแต่ 1 ถึง 4
มีหลายวิธีในการคำนวณ BigOh จากจุดนี้ไปข้างหน้าเราจะสมมติว่าทุกประโยคที่ไม่ได้ขึ้นอยู่กับขนาดของข้อมูลที่ป้อนจะมีC
ขั้นตอนการคำนวณจำนวนคงที่
เราจะเพิ่มจำนวนแต่ละขั้นตอนของฟังก์ชั่นและการประกาศตัวแปรท้องถิ่นหรือคำสั่งกลับขึ้นอยู่กับขนาดของdata
อาร์เรย์
นั่นหมายความว่าบรรทัดที่ 1 และ 4 ใช้จำนวน C ของแต่ละขั้นตอนและฟังก์ชั่นจะเป็นดังนี้:
f(N) = C + ??? + C
ส่วนถัดไปคือการกำหนดค่าของfor
คำสั่ง จำไว้ว่าเรากำลังนับจำนวนขั้นตอนการคำนวณซึ่งหมายความว่าเนื้อหาของfor
คำสั่งนั้นได้รับการดำเนินการN
ครั้ง นั่นเป็นเหมือนการเพิ่มC
, N
ครั้ง:
f(N) = C + (C + C + ... + C) + C = C + N * C + C
ไม่มีกฎเชิงกลไกในการนับจำนวนครั้งที่ร่างกายfor
ถูกเรียกใช้งานคุณต้องนับมันโดยดูที่รหัสทำอะไร เพื่อให้การคำนวณง่ายขึ้นเราจะเพิกเฉยต่อการเริ่มต้นตัวแปรเงื่อนไขและส่วนที่เพิ่มขึ้นของfor
คำสั่ง
เพื่อให้ได้ BigOh ที่แท้จริงเราจำเป็นต้องมีการวิเคราะห์ Asymptoticของฟังก์ชั่น สิ่งนี้ทำคร่าวๆดังนี้:
C
ค่าคงที่ทั้งหมดf()
ได้รับpolynomiumstandard form
ในของมันN
infinity
เราf()
มีสองเทอม:
f(N) = 2 * C * N ^ 0 + 1 * C * N ^ 1
กำจัดC
ค่าคงที่และส่วนที่ซ้ำซ้อนทั้งหมดออกไป:
f(N) = 1 + N ^ 1
เนื่องจากระยะที่ผ่านมาเป็นหนึ่งในที่เติบโตใหญ่เมื่อf()
เข้าใกล้อินฟินิตี้ (คิดว่าในข้อ จำกัด ) นี้เป็นอาร์กิวเมนต์ BigOh และsum()
ฟังก์ชั่นมี BigOh ของ:
O(N)
มีเทคนิคเล็กน้อยในการแก้ปัญหาที่ยุ่งยาก: ใช้การสรุปเมื่อใดก็ตามที่คุณทำได้
เป็นตัวอย่างรหัสนี้สามารถแก้ไขได้อย่างง่ายดายโดยใช้การสรุป:
for (i = 0; i < 2*n; i += 2) { // 1
for (j=n; j > i; j--) { // 2
foo(); // 3
}
}
foo()
สิ่งแรกที่คุณจะต้องถามว่าเป็นคำสั่งของการดำเนินการของ ในขณะที่ปกติจะเป็นO(1)
คุณต้องถามอาจารย์ของคุณเกี่ยวกับเรื่องนี้ O(1)
หมายความว่า (เกือบส่วนใหญ่) คงเป็นอิสระจากขนาดC
N
for
คำชี้แจงเกี่ยวกับจำนวนหนึ่งประโยคเป็นเรื่องยุ่งยาก ในขณะที่ดัชนีสิ้นสุดที่2 * N
การเพิ่มขึ้นจะกระทำโดยสอง นั่นหมายความว่าครั้งแรกที่for
ได้รับการดำเนินการเพียงN
ขั้นตอนและเราต้องหารจำนวนสอง
f(N) = Summation(i from 1 to 2 * N / 2)( ... ) =
= Summation(i from 1 to N)( ... )
จำนวนประโยคสองคือแม้ trickier i
เพราะมันขึ้นอยู่กับมูลค่าของ ลองดู: ดัชนีที่ฉันใช้ค่า: 0, 2, 4, 6, 8, ... , 2 * N, และที่สองfor
ได้รับการดำเนินการ: N ครั้งแรกที่หนึ่ง, N - 2 ที่สอง, N - 4 ขั้นตอนที่สาม ... จนถึงระยะ N / 2 ซึ่งครั้งที่สองfor
ไม่เคยถูกประหารชีวิต
ในสูตรนั่นหมายถึง:
f(N) = Summation(i from 1 to N)( Summation(j = ???)( ) )
อีกครั้งเรามีการนับจำนวนของขั้นตอนที่ และตามคำนิยามการสรุปทุกครั้งควรเริ่มต้นที่หนึ่งเสมอและจบด้วยตัวเลขที่ใหญ่กว่าหรือเท่ากับหนึ่ง
f(N) = Summation(i from 1 to N)( Summation(j = 1 to (N - (i - 1) * 2)( C ) )
(เราสมมติว่าfoo()
เป็นO(1)
และดำเนินการC
ตามขั้นตอน)
เรามีปัญหาที่นี่: เมื่อi
นำค่าN / 2 + 1
ขึ้นด้านบนการรวมภายในสิ้นสุดลงด้วยจำนวนลบ! นั่นเป็นไปไม่ได้และผิด เราจำเป็นต้องแยกบวกในสองเป็นจุดที่สำคัญช่วงเวลาที่ต้องใช้เวลาi
N / 2 + 1
f(N) = Summation(i from 1 to N / 2)( Summation(j = 1 to (N - (i - 1) * 2)) * ( C ) ) + Summation(i from 1 to N / 2) * ( C )
ตั้งแต่ช่วงการพิจาณาi > N / 2
ด้านในfor
จะไม่ได้รับการประหารชีวิตและเรากำลังสมมติความซับซ้อนในการประมวลผล C คงที่ในร่างกายของมัน
ตอนนี้การสรุปสามารถทำให้ง่ายขึ้นโดยใช้กฎเอกลักษณ์:
w
)ใช้พีชคณิตบางส่วน:
f(N) = Summation(i from 1 to N / 2)( (N - (i - 1) * 2) * ( C ) ) + (N / 2)( C )
f(N) = C * Summation(i from 1 to N / 2)( (N - (i - 1) * 2)) + (N / 2)( C )
f(N) = C * (Summation(i from 1 to N / 2)( N ) - Summation(i from 1 to N / 2)( (i - 1) * 2)) + (N / 2)( C )
f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2)( i - 1 )) + (N / 2)( C )
=> Summation(i from 1 to N / 2)( i - 1 ) = Summation(i from 1 to N / 2 - 1)( i )
f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2 - 1)( i )) + (N / 2)( C )
f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N / 2 - 1) * (N / 2 - 1 + 1) / 2) ) + (N / 2)( C )
=> (N / 2 - 1) * (N / 2 - 1 + 1) / 2 =
(N / 2 - 1) * (N / 2) / 2 =
((N ^ 2 / 4) - (N / 2)) / 2 =
(N ^ 2 / 8) - (N / 4)
f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N ^ 2 / 8) - (N / 4) )) + (N / 2)( C )
f(N) = C * (( N ^ 2 / 2 ) - ( (N ^ 2 / 4) - (N / 2) )) + (N / 2)( C )
f(N) = C * (( N ^ 2 / 2 ) - (N ^ 2 / 4) + (N / 2)) + (N / 2)( C )
f(N) = C * ( N ^ 2 / 4 ) + C * (N / 2) + C * (N / 2)
f(N) = C * ( N ^ 2 / 4 ) + 2 * C * (N / 2)
f(N) = C * ( N ^ 2 / 4 ) + C * N
f(N) = C * 1/4 * N ^ 2 + C * N
และ BigOh คือ:
O(N²)
O(n)
ที่n
เป็นจำนวนขององค์ประกอบหรือO(x*y)
ที่x
และy
มีขนาดของอาร์เรย์ Big-oh คือ "สัมพันธ์กับอินพุต" ดังนั้นจึงขึ้นอยู่กับอินพุตของคุณ
Big O ให้ขอบเขตความซับซ้อนของเวลาสำหรับอัลกอริทึม โดยปกติจะใช้ร่วมกับชุดข้อมูลการประมวลผล (รายการ) แต่สามารถใช้ได้ที่อื่น
ตัวอย่างบางส่วนของวิธีการใช้งานในรหัส C
สมมติว่าเรามีองค์ประกอบ n จำนวนมากมาย
int array[n];
ถ้าเราต้องการเข้าถึงองค์ประกอบแรกของอาเรย์นี้จะเป็น O (1) เนื่องจากมันไม่สำคัญว่าอาเรย์จะใหญ่แค่ไหนมันต้องใช้เวลาคงที่เท่ากันเสมอในการรับไอเท็มชิ้นแรก
x = array[0];
หากเราต้องการค้นหาหมายเลขในรายการ:
for(int i = 0; i < n; i++){
if(array[i] == numToFind){ return i; }
}
นี่จะเป็น O (n) เนื่องจากอย่างมากเราจะต้องตรวจสอบรายการทั้งหมดเพื่อค้นหาหมายเลขของเรา Big-O ยังคงเป็น O (n) แม้ว่าเราอาจพบว่าหมายเลขของเราเป็นครั้งแรกที่พยายามแล้ววิ่งผ่านลูปหนึ่งครั้งเพราะ Big-O อธิบายขอบเขตบนของอัลกอริทึม (โอเมก้าสำหรับขอบเขตที่ต่ำกว่า .
เมื่อเราไปถึงลูปซ้อนกัน:
for(int i = 0; i < n; i++){
for(int j = i; j < n; j++){
array[j] += 2;
}
}
นี่คือ O (n ^ 2) เนื่องจากแต่ละรอบของวงนอก (O (n)) เราต้องผ่านรายการทั้งหมดอีกครั้งดังนั้น n จึงคูณเราด้วย n กำลังสอง
นี่เป็นรอยขีดข่วนพื้นผิวแทบ แต่เมื่อคุณได้รับการวิเคราะห์อัลกอริทึมที่ซับซ้อนมากขึ้นคณิตศาสตร์ที่ซับซ้อนที่เกี่ยวข้องกับการพิสูจน์เข้ามาเล่น หวังว่าสิ่งนี้จะทำให้คุณคุ้นเคยกับพื้นฐานอย่างน้อย
O(1)
ทำงานด้วยตนเอง ในมาตรฐาน API C เช่นbsearch
เป็นอย่างโดยเนื้อแท้O(log n)
, strlen
เป็นO(n)
และqsort
เป็นO(n log n)
(ในทางเทคนิคมันไม่มีการค้ำประกันและ quicksort ตัวเองมีความซับซ้อนกรณีที่เลวร้ายที่สุดของO(n²)
แต่สมมติว่าคุณlibc
เขียนจะไม่ปัญญาอ่อนซับซ้อนกรณีเฉลี่ยอยู่O(n log n)
และจะใช้ กลยุทธ์การเลือกเดือยที่ช่วยลดโอกาสในการตีO(n²)
กรณี) และทั้งคู่bsearch
และqsort
อาจแย่ลงถ้าฟังก์ชั่นเปรียบเทียบเป็นพยาธิวิทยา
ในขณะที่การรู้วิธีการคิดเวลา Big O สำหรับปัญหาเฉพาะของคุณนั้นมีประโยชน์ แต่การรู้กรณีทั่วไปบางอย่างอาจช่วยให้คุณตัดสินใจในอัลกอริทึมได้นานขึ้น
นี่คือบางกรณีที่พบบ่อยที่สุดยกมาจากhttp://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions :
O (1) - การพิจารณาว่าตัวเลขเป็นเลขคู่หรือคี่ ใช้ตารางการค้นหาขนาดคงที่หรือตารางแฮช
O (logn) - การค้นหารายการในอาร์เรย์ที่เรียงลำดับด้วยการค้นหาแบบไบนารี
O (n) - ค้นหารายการในรายการที่ไม่เรียงลำดับ เพิ่มตัวเลขสองหลัก n
O (n 2 ) - การคูณตัวเลขสองหลักด้วยอัลกอริทึมแบบง่าย การเพิ่มเมทริกซ์สอง n n n; เรียงลำดับฟองหรือเรียงแทรก
O (n 3 ) - การคูณเมทริกซ์สอง n × n ด้วยอัลกอริทึมแบบง่าย
O (c n ) - ค้นหาโซลูชัน (แน่นอน) สำหรับปัญหาพนักงานขายเดินทางโดยใช้การเขียนโปรแกรมแบบไดนามิก; การพิจารณาว่าสองงบเชิงตรรกะจะเทียบเท่าโดยใช้กำลังดุร้าย
O (n!) - การแก้ไขปัญหาพนักงานขายที่เดินทางผ่านการค้นหาแบบไร้กำลัง
O (n n ) - มักใช้แทน O (n!) เพื่อให้ได้สูตรที่ง่ายขึ้นสำหรับความซับซ้อนเชิงซีมโทติค
x&1==1
เพื่อตรวจสอบความแปลก?
x & 1
จะเพียงพอไม่จำเป็นต้องตรวจสอบ== 1
ใน C x&1==1
ได้รับการประเมินว่าเป็นx&(1==1)
เพราะความสำคัญของผู้ดำเนินการดังนั้นจึงเหมือนกับการทดสอบจริง ๆx&1
) ฉันคิดว่าคุณเข้าใจผิดคำตอบว่า; มีเครื่องหมายจุดคู่อยู่ที่นั่นไม่ใช่เครื่องหมายจุลภาค มันไม่ได้บอกว่าคุณต้องใช้ตารางการค้นหาสำหรับการทดสอบแบบสม่ำเสมอ/ คี่การพูดทั้งการทดสอบแบบสม่ำเสมอและแบบทดสอบ O(1)
การแจ้งเตือนเล็กน้อย: big O
สัญกรณ์ใช้เพื่อแสดงถึงความซับซ้อนเชิงซีมโทติค (นั่นคือเมื่อขนาดของปัญหาเพิ่มขึ้นเป็นอนันต์) และจะซ่อนค่าคงที่
ซึ่งหมายความว่าระหว่างอัลกอริทึมใน O (n) และหนึ่งใน O (n 2 ) ที่เร็วที่สุดไม่ได้เป็นคนแรกเสมอ (แม้ว่าจะมีค่าเสมอของ n เช่นสำหรับปัญหาขนาด> n อัลกอริทึมแรกคือ ที่เร็วที่สุด).
โปรดทราบว่าค่าคงที่ที่ซ่อนอยู่นั้นขึ้นอยู่กับการนำไปใช้
นอกจากนี้ในบางกรณีรันไทม์ไม่ใช่ฟังก์ชันที่กำหนดค่าไว้สำหรับขนาด n ของอินพุต ทำการเรียงลำดับโดยใช้การเรียงลำดับอย่างรวดเร็ว: เวลาที่ใช้ในการเรียงลำดับอาร์เรย์ขององค์ประกอบ n ไม่ใช่ค่าคงที่ แต่ขึ้นอยู่กับการกำหนดค่าเริ่มต้นของอาร์เรย์
มีความซับซ้อนของเวลาที่แตกต่างกัน:
กรณีเฉลี่ย (โดยทั่วไปจะยากกว่าที่จะคิดออก ... )
...
การแนะนำที่ดีคือการแนะนำการวิเคราะห์อัลกอริทึมโดย R. Sedgewick และ P. Flajolet
ดังที่คุณพูดpremature optimisation is the root of all evil
และควรใช้การทำโปรไฟล์ (ถ้าเป็นไปได้) เสมอเมื่อปรับรหัสให้เหมาะสม มันยังสามารถช่วยคุณกำหนดความซับซ้อนของอัลกอริทึมของคุณ
เห็นคำตอบที่นี่ฉันคิดว่าเราสามารถสรุปได้ว่าพวกเราส่วนใหญ่ทำตามคำสั่งของอัลกอริทึมโดยดูจากมันและใช้สามัญสำนึกแทนการคำนวณด้วยตัวอย่างเช่นวิธีการต้นแบบที่เราคิดที่มหาวิทยาลัย ด้วยที่กล่าวว่าฉันต้องเพิ่มว่าแม้อาจารย์ให้กำลังใจเรา (ในภายหลัง) จริงคิดเกี่ยวกับมันแทนที่จะเพียงแค่คำนวณ
นอกจากนี้ฉันต้องการเพิ่มว่ามันจะทำอย่างไรสำหรับฟังก์ชั่นซ้ำ :
สมมติว่าเรามีฟังก์ชั่นเช่น ( รหัสโครงร่าง ):
(define (fac n)
(if (= n 0)
1
(* n (fac (- n 1)))))
ซึ่งคำนวณแฟกทอเรียลแบบวนซ้ำตามจำนวนที่กำหนด
ขั้นตอนแรกคือการลองและกำหนดลักษณะการทำงานสำหรับร่างกายของฟังก์ชันเฉพาะในกรณีนี้ไม่มีสิ่งใดที่พิเศษทำในร่างกายเพียงแค่การคูณ (หรือการกลับมาของค่า 1)
ดังนั้นประสิทธิภาพสำหรับร่างกายคือ: O (1) (ค่าคงที่)
ลองถัดไปและกำหนดนี้สำหรับจำนวนของสาย recursive ในกรณีนี้เรามีการโทรซ้ำแบบ n-1
ดังนั้นประสิทธิภาพสำหรับการโทรแบบเรียกซ้ำคือ: O (n-1) (คำสั่งซื้อคือ n เมื่อเราทิ้งส่วนที่ไม่สำคัญออกไป)
จากนั้นนำสองสิ่งนี้มารวมกันและคุณจะได้รับฟังก์ชั่นการเรียกซ้ำทั้งหมด:
1 * (n-1) = O (n)
ปีเตอร์เพื่อตอบคำถามที่คุณหยิบยกขึ้นมา วิธีที่ฉันอธิบายที่นี่จริง ๆ แล้วมันค่อนข้างดี แต่โปรดจำไว้ว่านี่ยังคงเป็นการประมาณและไม่ใช่คำตอบที่ถูกต้องทางคณิตศาสตร์ทั้งหมด วิธีที่อธิบายไว้ที่นี่เป็นหนึ่งในวิธีการที่เราสอนที่มหาวิทยาลัยและถ้าฉันจำได้ว่าถูกต้องถูกใช้สำหรับอัลกอริทึมขั้นสูงกว่าแฟคทอเรียลที่ฉันใช้ในตัวอย่างนี้
แน่นอนว่าทุกอย่างขึ้นอยู่กับว่าคุณสามารถประเมินเวลาการทำงานของร่างกายและจำนวนการโทรซ้ำได้อย่างไร แต่นั่นก็เป็นจริงสำหรับวิธีอื่น ๆ
หากค่าใช้จ่ายของคุณเป็นพหุนามเพียงแค่รักษาคำที่มีลำดับสูงสุดโดยไม่มีตัวคูณ เช่น:
O ((n / 2 + 1) * (n / 2)) = O (n 2 /4 + n / 2) = O (n 2 /4) = O (n 2 )
สิ่งนี้ใช้ไม่ได้กับซีรี่ย์ที่ไม่มีที่สิ้นสุด ไม่มีสูตรอาหารเดียวสำหรับกรณีทั่วไปแม้ว่าสำหรับบางกรณีทั่วไปจะใช้ความไม่เท่าเทียมกันดังต่อไปนี้:
O (บันทึกN ) <O ( N ) <O ( NบันทึกN ) <O ( N 2 ) <O ( N k ) <O (e n ) <O ( n !)
ฉันคิดในแง่ของข้อมูล ปัญหาใด ๆ ประกอบด้วยการเรียนรู้บิตจำนวนหนึ่ง
เครื่องมือพื้นฐานของคุณคือแนวคิดของคะแนนการตัดสินใจและเอนโทรปี เอนโทรปีของจุดตัดสินใจคือข้อมูลโดยเฉลี่ยที่จะให้คุณ ตัวอย่างเช่นหากโปรแกรมมีจุดตัดสินใจที่มีสองสาขามันคือเอนโทรปีคือผลรวมของความน่าจะเป็นของแต่ละสาขาคูณล็อก2ของความน่าจะเป็นกลับกันของสาขานั้น นั่นคือจำนวนเงินที่คุณเรียนรู้โดยดำเนินการตัดสินใจนั้น
ตัวอย่างเช่นif
คำสั่งที่มีสองสาขาซึ่งมีแนวโน้มเท่ากันทั้งสองมีค่าเอนโทรปีของ 1/2 * log (2/1) + 1/2 * log (2/1) = 1/2 * 1 + 1/2 * 1 = 1 ดังนั้นเอนโทรปีของมันคือ 1 บิต
สมมติว่าคุณกำลังค้นหาตารางรายการ N เช่น N = 1024 นั่นเป็นปัญหา 10 บิตเนื่องจาก log (1024) = 10 bits ดังนั้นหากคุณสามารถค้นหาโดยใช้คำสั่ง IF ที่มีแนวโน้มผลลัพธ์เท่ากันก็ควรทำการตัดสินใจ 10 ข้อ
นั่นคือสิ่งที่คุณจะได้รับจากการค้นหาแบบไบนารี
สมมติว่าคุณกำลังค้นหาเชิงเส้น คุณดูที่องค์ประกอบแรกและถามว่ามันเป็นองค์ประกอบที่คุณต้องการ ความน่าจะเป็น 1/1024 ที่เป็นจริงและ 1023/1024 ที่ไม่ใช่ เอนโทรปีของการตัดสินใจนั้นคือ 1/1024 * log (1024/1) + 1023/1024 * log (1024/1023) = 1/1024 * 10 + 1023/1024 * ประมาณ 0 = ประมาณ. 01 บิต คุณได้เรียนรู้น้อยมาก! การตัดสินใจครั้งที่สองนั้นไม่ค่อยดีนัก นั่นคือเหตุผลที่การค้นหาเชิงเส้นช้ามาก ในความเป็นจริงมันเป็นเลขชี้กำลังเป็นจำนวนบิตที่คุณต้องเรียนรู้
สมมติว่าคุณกำลังทำดัชนี สมมติว่าตารางนั้นถูกจัดเรียงไว้ล่วงหน้าในถังขยะจำนวนมากและคุณใช้บิตทั้งหมดในคีย์เพื่อทำดัชนีโดยตรงไปยังรายการตาราง หากมี 1024 ถังขยะเอนโทรปีคือ 1/1024 * บันทึก (1024) + 1/1024 * บันทึก (1024) + ... สำหรับผลลัพธ์ที่เป็นไปได้ทั้งหมด 1024 รายการ นี่คือ 1/1024 * 10 เท่าของผลลัพธ์ 1024 รายการหรือเอนโทรปี 10 บิตสำหรับการดำเนินการสร้างดัชนีหนึ่งรายการ นั่นคือเหตุผลที่การค้นหาการจัดทำดัชนีนั้นรวดเร็ว
ตอนนี้คิดเกี่ยวกับการจัดเรียง คุณมีรายการ N รายการและคุณมีรายการ สำหรับแต่ละรายการคุณต้องค้นหาว่ารายการนั้นไปที่ใดในรายการจากนั้นเพิ่มเข้าไปในรายการ ดังนั้นการเรียงลำดับจะใช้เวลาประมาณ N คูณจำนวนขั้นตอนของการค้นหาที่สำคัญ
ดังนั้นการเรียงลำดับขึ้นอยู่กับการตัดสินใจแบบไบนารีที่มีผลลัพธ์ที่น่าจะเป็นไปได้อย่างเท่าเทียมกันทุกขั้นตอนเกี่ยวกับ O (N log N) อัลกอริทึมการเรียงลำดับ O (N) เป็นไปได้ถ้ามันขึ้นอยู่กับการค้นหาการจัดทำดัชนี
ฉันพบว่าเกือบทุกปัญหาเกี่ยวกับประสิทธิภาพของอัลกอริทึมสามารถดูได้ด้วยวิธีนี้
ให้เริ่มต้นจากจุดเริ่มต้น.
ก่อนอื่นยอมรับหลักการที่ว่าการดำเนินการอย่างง่าย ๆ กับข้อมูลสามารถทำได้ในO(1)
เวลานั่นคือในเวลาที่ไม่ขึ้นกับขนาดของอินพุต การดำเนินงานดั้งเดิมเหล่านี้ใน C ประกอบด้วย
เหตุผลสำหรับหลักการนี้ต้องมีการศึกษารายละเอียดของคำแนะนำเครื่อง (ขั้นตอนดั้งเดิม) ของคอมพิวเตอร์ทั่วไป แต่ละการดำเนินการที่อธิบายไว้สามารถทำได้ด้วยคำสั่งเครื่องจำนวนเล็กน้อย บ่อยครั้งที่ต้องการเพียงหนึ่งหรือสองคำแนะนำ เป็นผลให้งบหลายชนิดใน C สามารถดำเนินการในO(1)
เวลานั่นคือในบางช่วงเวลาคงที่เป็นอิสระจากการป้อนข้อมูล ง่าย ๆ เหล่านี้ ได้แก่
ใน C วงฟอร์ลูปจำนวนมากถูกสร้างขึ้นโดยเริ่มต้นตัวแปรดัชนีให้มีค่าบางค่าและเพิ่มค่าตัวแปรนั้น 1 ครั้งในแต่ละรอบลูป for-loop สิ้นสุดลงเมื่อดัชนีถึงขีด จำกัด ตัวอย่างเช่น for-loop
for (i = 0; i < n-1; i++)
{
small = i;
for (j = i+1; j < n; j++)
if (A[j] < A[small])
small = j;
temp = A[small];
A[small] = A[i];
A[i] = temp;
}
ใช้ตัวแปรดัชนีฉัน เพิ่มขึ้นทีละ 1 ทุกรอบลูปและการวนซ้ำจะหยุดเมื่อถึง n - 1
อย่างไรก็ตามในขณะนี้ให้มุ่งเน้นไปที่รูปแบบเรียบง่ายของ for-loop โดยที่ความแตกต่างระหว่างค่าสุดท้ายและค่าเริ่มต้นหารด้วยจำนวนที่ตัวแปรดัชนีเพิ่มขึ้นจะบอกเราว่าเราไปวนรอบกี่ครั้ง การนับนั้นเป็นสิ่งที่ถูกต้องเว้นแต่จะมีวิธีการออกจากลูปผ่านคำสั่งข้าม มันเป็นขอบเขตสูงสุดของจำนวนการวนซ้ำในกรณีใด ๆ
ยกตัวอย่างเช่น for-loop iterates ((n − 1) − 0)/1 = n − 1 times
เนื่องจาก 0 เป็นค่าเริ่มต้นของ i, n - 1 คือค่าสูงสุดที่เข้าถึงได้โดย i (เช่นเมื่อ i ถึง n − 1, loop จะหยุดและไม่มีการวนซ้ำเกิดขึ้นกับ i = n− 1) และ 1 ถูกเพิ่มเข้ากับ i ในการวนซ้ำแต่ละรอบ
ในกรณีที่ง่ายที่สุดที่เวลาที่ใช้ในร่างกายห่วงเป็นเหมือนกันสำหรับแต่ละย้ำเราสามารถคูณใหญ่โอ้ผูกไว้บนร่างกายด้วยจำนวนครั้งรอบวง พูดอย่างเคร่งครัดเราจะต้องเพิ่มเวลา O (1) เพื่อเริ่มต้นดัชนีลูปและเวลา O (1) สำหรับการเปรียบเทียบครั้งแรกของดัชนีลูปกับขีด จำกัดเพราะเราทดสอบอีกครั้งกว่าที่เราไปรอบ ๆ ลูป อย่างไรก็ตามเว้นแต่เป็นไปได้ที่จะดำเนินการวนรอบเป็นศูนย์เวลาที่จะเริ่มต้นการวนซ้ำและทดสอบขีด จำกัด หนึ่งครั้งเป็นคำสั่งต่ำที่สามารถลดลงโดยกฎการรวม
ลองพิจารณาตัวอย่างนี้:
(1) for (j = 0; j < n; j++)
(2) A[i][j] = 0;
เรารู้ว่าบรรทัด (1)ต้องใช้O(1)
เวลา เห็นได้ชัดว่าเราไปวนรอบ n ครั้งตามที่เราสามารถกำหนดได้โดยการลบขีด จำกัด ล่างจากขีด จำกัด บนที่พบในบรรทัด (1) แล้วเพิ่ม 1 เนื่องจากร่างกายเส้น (2) ใช้เวลา O (1) เราสามารถละเลยเวลาในการเพิ่ม j และเวลาเพื่อเปรียบเทียบ j กับ n ซึ่งทั้งสองเป็น O (1) ดังนั้นเวลาทำงานของเส้น (1) และ (2) เป็นผลิตภัณฑ์ของ n และ O (1)O(n)
ซึ่งเป็น
ในทำนองเดียวกันเราสามารถผูกเวลาทำงานของวงรอบนอกซึ่งประกอบด้วยเส้น (2) ถึง (4) ซึ่งก็คือ
(2) for (i = 0; i < n; i++)
(3) for (j = 0; j < n; j++)
(4) A[i][j] = 0;
เราได้กำหนดไว้แล้วว่าการวนรอบของบรรทัด (3) และ (4) ต้องใช้เวลา O (n) ดังนั้นเราสามารถละเลย O (1) เวลาในการเพิ่มค่า i และทดสอบว่า i <n ในการวนซ้ำแต่ละครั้งหรือไม่โดยสรุปว่าการวนซ้ำแต่ละรอบนอกใช้เวลา O (n)
การกำหนดค่าเริ่มต้น i = 0 ของวงนอกและการทดสอบ (n + 1) st ของเงื่อนไข i <n เช่นเดียวกันใช้เวลา O (1) เวลาและสามารถถูกละเลยได้ ในที่สุดเราสังเกตเห็นว่าเราไปรอบ ๆ วงรอบนอก n ครั้งใช้เวลา O (n) สำหรับการวนซ้ำแต่ละครั้งให้O(n^2)
เวลาทำงานทั้งหมด
ตัวอย่างการปฏิบัติมากขึ้น
หากคุณต้องการประเมินลำดับของรหัสของคุณเชิงประจักษ์มากกว่าการวิเคราะห์รหัสคุณสามารถติดกับชุดของค่าที่เพิ่มขึ้นของ n และเวลารหัสของคุณ พล็อตการกำหนดเวลาของคุณบนบันทึกการทำงาน หากรหัสคือ O (x ^ n) ค่าควรอยู่ในบรรทัดที่มีความชัน n
สิ่งนี้มีข้อดีมากกว่าเพียงแค่ศึกษารหัส สำหรับสิ่งหนึ่งคุณสามารถดูได้ว่าคุณอยู่ในช่วงที่เวลาวิ่งเข้าใกล้ลำดับซีมโทติคหรือไม่ นอกจากนี้คุณอาจพบว่าบางรหัสที่คุณคิดว่าเป็นคำสั่ง O (x) นั้นเป็นคำสั่ง O (x ^ 2) จริง ๆ เช่นเนื่องจากเวลาที่ใช้ในการโทรในไลบรารี
โดยทั่วไปสิ่งที่ปลูกพืชขึ้น 90% ของเวลาเป็นเพียงการวิเคราะห์ลูป คุณมีลูปซ้อนกันสองชั้นสามเท่าหรือไม่? คุณมีเวลาทำงาน O (n), O (n ^ 2), O (n ^ 3)
ไม่ค่อยมาก (เว้นแต่คุณกำลังเขียนแพลตฟอร์มที่มีไลบรารี่ฐานกว้างขวาง (เช่น. NET BCL หรือ C ++ ของ STL) คุณจะพบกับสิ่งที่ยากกว่าการดูลูปของคุณ (สำหรับคำสั่งในขณะที่ข้ามไป ฯลฯ ... )
สัญลักษณ์ Big O มีประโยชน์เพราะง่ายต่อการทำงานและซ่อนภาวะแทรกซ้อนและรายละเอียดที่ไม่จำเป็น (สำหรับคำจำกัดความที่ไม่จำเป็น) วิธีหนึ่งที่ดีในการจัดการกับความซับซ้อนของการหารและพิชิตอัลกอริธึมคือวิธีต้นไม้ สมมติว่าคุณมีเวอร์ชันของ quicksort ด้วยค่ามัธยฐานดังนั้นคุณจึงแบ่งอาร์เรย์เป็น subarrays ที่สมดุลอย่างสมบูรณ์ทุกครั้ง
ตอนนี้สร้างต้นไม้ที่สอดคล้องกับอาร์เรย์ทั้งหมดที่คุณทำงานด้วย ที่รูทคุณมีอาเรย์ดั้งเดิมรูทมีลูกสองคนซึ่งเป็น subarrays ทำซ้ำจนกว่าคุณจะมีอาร์เรย์องค์ประกอบเดียวที่ด้านล่าง
เนื่องจากเราสามารถหาค่ามัธยฐานในเวลา O (n) และแบ่งอาร์เรย์เป็นสองส่วนในเวลา O (n) งานที่ทำในแต่ละโหนดคือ O (k) โดยที่ k คือขนาดของอาร์เรย์ แต่ละระดับของทรีมี (มากที่สุด) อาเรย์ทั้งหมดดังนั้นการทำงานต่อระดับคือ O (n) (ขนาดของ subarrays เพิ่มขึ้นถึง n และเนื่องจากเรามี O (k) ต่อระดับเราจึงสามารถเพิ่มสิ่งนี้ได้) . มีเพียงระดับ log (n) ในแผนผังตั้งแต่แต่ละครั้งที่เราลดการป้อนข้อมูลลงครึ่งหนึ่ง
ดังนั้นเราสามารถ จำกัด ปริมาณงานโดย O (n * log (n))
อย่างไรก็ตาม Big O ซ่อนรายละเอียดบางอย่างซึ่งบางครั้งเราไม่สามารถเพิกเฉยได้ ลองคำนวณลำดับฟีโบนักชีด้วย
a=0;
b=1;
for (i = 0; i <n; i++) {
tmp = b;
b = a + b;
a = tmp;
}
และให้สมมติว่า a และ b เป็น BigIntegers ใน Java หรือสิ่งที่สามารถจัดการกับตัวเลขขนาดใหญ่โดยพลการ คนส่วนใหญ่จะบอกว่านี่เป็นอัลกอริทึม O (n) โดยไม่ต้องสะดุ้ง เหตุผลก็คือคุณมีการวนซ้ำในห่วง for และ O (1) ทำงานข้างลูป
แต่ตัวเลขฟีโบนักชีมีขนาดใหญ่จำนวนฟีโบนักชีหมายเลขที่ n นั้นมีเลขชี้กำลังเป็นเลขชี้กำลังใน n ดังนั้นเพียงแค่เก็บมันไว้ในลำดับ n ไบต์ การเพิ่มด้วยจำนวนเต็มขนาดใหญ่จะใช้จำนวน O (n) จำนวน ดังนั้นปริมาณงานทั้งหมดที่ทำในขั้นตอนนี้จึงเป็น
1 + 2 + 3 + ... + n = n (n-1) / 2 = O (n ^ 2)
ดังนั้นอัลกอริธึมนี้จึงทำงานในเวลากำลังสอง!
โดยทั่วไปแล้วฉันคิดว่ามีประโยชน์น้อยกว่า แต่เพื่อความครบถ้วนมีBig Omega Ωซึ่งกำหนดขอบเขตล่างบนความซับซ้อนของอัลกอริทึมและBig Theta Θซึ่งกำหนดทั้งขอบเขตบนและล่าง
แยกอัลกอริทึมเป็นชิ้น ๆ ที่คุณรู้จักสัญลักษณ์ O ขนาดใหญ่และรวมเข้ากับโอเปอเรเตอร์ขนาดใหญ่ นั่นเป็นวิธีเดียวที่ฉันรู้
สำหรับข้อมูลเพิ่มเติมตรวจสอบหน้า Wikipediaในหัวข้อ
ความคุ้นเคยกับอัลกอริทึม / โครงสร้างข้อมูลที่ฉันใช้และ / หรือการวิเคราะห์อย่างรวดเร็วของการทำซ้ำการซ้อน ปัญหาคือเมื่อคุณเรียกใช้ฟังก์ชันไลบรารีอาจจะหลายครั้งคุณอาจไม่แน่ใจว่าคุณกำลังเรียกใช้ฟังก์ชันในบางครั้งโดยไม่จำเป็นหรือการใช้งานแบบใด บางทีการทำงานห้องสมุดควรมีมาตรการซับซ้อน / ประสิทธิภาพไม่ว่าจะเป็น Big O หรืออื่น ๆ ตัวชี้วัดที่มีอยู่ในเอกสารหรือแม้กระทั่งIntelliSense
ในฐานะที่เป็น "วิธีการทำคุณคำนวณ" บิ๊กโอนี้เป็นส่วนหนึ่งของทฤษฎีความซับซ้อนการคำนวณ สำหรับกรณีพิเศษ (หลาย ๆ กรณี) คุณอาจจะมาพร้อมกับฮิวริสติกแบบง่าย ๆ (เช่นการนับการวนซ้ำสำหรับลูปซ้อนกัน) โดยเฉพาะ เมื่อทั้งหมดที่คุณต้องการคือการประมาณขอบเขตบนและคุณไม่คิดว่ามันเป็นแง่ร้ายเกินไป - ซึ่งฉันเดาว่าอาจเป็นคำถามของคุณเกี่ยวกับ
หากคุณต้องการที่จะตอบคำถามของคุณสำหรับอัลกอริทึมใด ๆ ที่ดีที่สุดที่คุณสามารถทำได้คือการใช้ทฤษฎี นอกจากการวิเคราะห์แบบ "กรณีที่แย่ที่สุด" แบบง่ายๆฉันพบว่าการวิเคราะห์ค่าตัดจำหน่ายมีประโยชน์อย่างมากในทางปฏิบัติ
สำหรับกรณีที่ 1 ภายในวงจะถูกดำเนินการn-i
ครั้งดังนั้นจำนวนรวมของการประหารชีวิตคือผลรวมสำหรับi
ไปจาก0
ไปn-1
(เพราะต่ำกว่าไม่ต่ำกว่าหรือเท่ากับ) n-i
ของ คุณได้ในที่สุดn*(n + 1) / 2
ดังนั้นO(n²/2) = O(n²)
ดังนั้น
สำหรับลูปที่ 2 นั้นi
อยู่ระหว่าง0
และn
รวมไว้สำหรับลูปภายนอก จากนั้นวงภายในจะถูกดำเนินการเมื่อj
มีค่ามากกว่าn
ซึ่งเป็นไปไม่ได้อย่างเคร่งครัด
นอกเหนือจากการใช้วิธีการหลัก (หรือหนึ่งในความเชี่ยวชาญเฉพาะด้าน) ฉันจะทดสอบอัลกอริทึมของฉันแบบทดลอง นี่ไม่สามารถพิสูจน์ได้ว่าคลาสความซับซ้อนใด ๆ เกิดขึ้นได้ แต่สามารถรับรองได้ว่าการวิเคราะห์ทางคณิตศาสตร์นั้นเหมาะสม เพื่อช่วยในเรื่องความมั่นใจนี้ฉันใช้เครื่องมือครอบคลุมรหัสร่วมกับการทดสอบของฉันเพื่อให้แน่ใจว่าฉันใช้ทุกกรณี
เป็นตัวอย่างง่ายๆที่บอกว่าคุณต้องการตรวจสติด้วยความเร็วของการเรียงลำดับรายการของ. NET Framework คุณสามารถเขียนสิ่งต่อไปนี้แล้ววิเคราะห์ผลลัพธ์ใน Excel เพื่อให้แน่ใจว่าไม่เกินเส้นโค้ง n * log (n)
ในตัวอย่างนี้ฉันวัดจำนวนการเปรียบเทียบ แต่ก็ควรระมัดระวังที่จะตรวจสอบเวลาที่แท้จริงสำหรับแต่ละขนาดตัวอย่าง อย่างไรก็ตามคุณต้องระวังให้มากขึ้นว่าคุณกำลังวัดอัลกอริทึมและไม่รวมสิ่งประดิษฐ์จากโครงสร้างพื้นฐานการทดสอบของคุณ
int nCmp = 0;
System.Random rnd = new System.Random();
// measure the time required to sort a list of n integers
void DoTest(int n)
{
List<int> lst = new List<int>(n);
for( int i=0; i<n; i++ )
lst[i] = rnd.Next(0,1000);
// as we sort, keep track of the number of comparisons performed!
nCmp = 0;
lst.Sort( delegate( int a, int b ) { nCmp++; return (a<b)?-1:((a>b)?1:0)); }
System.Console.Writeline( "{0},{1}", n, nCmp );
}
// Perform measurement for a variety of sample sizes.
// It would be prudent to check multiple random samples of each size, but this is OK for a quick sanity check
for( int n = 0; n<1000; n++ )
DoTest(n);
อย่าลืมที่จะอนุญาตให้มีความซับซ้อนของพื้นที่ที่อาจเป็นสาเหตุของความกังวลหากมีทรัพยากรหน่วยความจำ จำกัด ตัวอย่างเช่นคุณอาจได้ยินคนที่ต้องการอัลกอริธึมพื้นที่คงที่ซึ่งโดยทั่วไปแล้วเป็นวิธีการบอกว่าปริมาณพื้นที่ที่อัลกอริทึมนั้นไม่ได้ขึ้นอยู่กับปัจจัยภายในโค้ด
บางครั้งความซับซ้อนอาจมาจากสิ่งที่เรียกว่ากี่ครั้งความถี่จะถูกเรียกใช้งานลูปบ่อยแค่ไหนมีการจัดสรรหน่วยความจำบ่อยแค่ไหนและเป็นอีกส่วนหนึ่งที่จะตอบคำถามนี้
สุดท้าย O ขนาดใหญ่สามารถใช้สำหรับกรณีที่เลวร้ายที่สุดกรณีที่ดีที่สุดและกรณีค่าตัดจำหน่ายซึ่งโดยทั่วไปจะเป็นกรณีที่เลวร้ายที่สุดที่ใช้สำหรับการอธิบายว่าอัลกอริทึมที่ไม่ดีอาจเป็นอย่างไร
สิ่งที่มักถูกมองข้ามคือพฤติกรรมที่คาดหวังของอัลกอริทึมของคุณ ไม่เปลี่ยน Big-O ของอัลกอริทึมของคุณแต่จะเกี่ยวข้องกับคำสั่ง "การปรับให้เหมาะสมก่อนวัยอันควร.."
พฤติกรรมที่คาดหวังของอัลกอริทึมของคุณคือ - โง่มาก - ความเร็วที่คุณคาดหวังว่าอัลกอริทึมของคุณจะทำงานกับข้อมูลที่คุณน่าจะเห็นมากที่สุด
ตัวอย่างเช่นหากคุณกำลังค้นหาค่าในรายการมันเป็น O (n) แต่ถ้าคุณรู้ว่ารายการส่วนใหญ่ที่คุณเห็นจะมีมูลค่าของคุณอยู่ข้างหน้าพฤติกรรมทั่วไปของอัลกอริทึมของคุณจะเร็วขึ้น
คุณต้องอธิบายการกระจายความน่าจะเป็นของ "อินพุทพื้นที่" ของคุณ (ถ้าคุณต้องการเรียงลำดับรายการบ่อยแค่ไหนที่รายการนั้นจะถูกเรียงลำดับแล้วบ่อยแค่ไหนมันกลับรายการทั้งหมดอย่างไร บ่อยครั้งที่มันถูกจัดเรียงเป็นส่วนใหญ่?) มันไม่ได้เป็นไปได้เสมอที่คุณรู้ว่า แต่บางครั้งคุณทำ
คำถามที่ดี!
คำเตือน: คำตอบนี้มีคำเท็จดูความคิดเห็นด้านล่าง
หากคุณใช้ Big O คุณกำลังพูดถึงกรณีที่แย่กว่านั้น (เพิ่มเติมเกี่ยวกับความหมายในภายหลัง) นอกจากนี้ยังมีทุน theta สำหรับกรณีทั่วไปและโอเมก้าขนาดใหญ่สำหรับกรณีที่ดีที่สุด
ลองชมไซต์นี้เพื่อดูคำจำกัดความที่เป็นทางการของ Big O: https://xlinux.nist.gov/dads/HTML/bigOnotation.html
f (n) = O (g (n)) หมายถึงมีค่าคงที่เป็นบวก c และ k เช่นที่ 0 ≤ f (n) ≤ cg (n) สำหรับทุก n ≥ k ค่าของ c และ k ต้องได้รับการแก้ไขสำหรับฟังก์ชัน f และต้องไม่ขึ้นอยู่กับ n
ตกลงดังนั้นตอนนี้เราหมายถึงอะไรโดยความซับซ้อน "กรณีที่ดีที่สุด" และ "กรณีที่เลวร้ายที่สุด"?
นี่อาจเป็นตัวอย่างที่ชัดเจนที่สุดผ่านตัวอย่าง ตัวอย่างเช่นหากเราใช้การค้นหาเชิงเส้นเพื่อค้นหาตัวเลขในอาร์เรย์ที่เรียงลำดับแล้วกรณีที่แย่ที่สุดคือเมื่อเราตัดสินใจที่จะค้นหาองค์ประกอบสุดท้ายของอาร์เรย์เนื่องจากจะต้องดำเนินการหลายขั้นตอนเนื่องจากมีรายการอยู่ในอาร์เรย์ กรณีที่ดีที่สุดจะเป็นตอนที่เราค้นหาสำหรับองค์ประกอบแรกนับตั้งแต่ที่เราจะทำหลังจากการตรวจสอบครั้งแรก
ประเด็นของความซับซ้อน - คำคุณศัพท์ที่ซับซ้อนเหล่านี้คือเรากำลังมองหาวิธีกราฟระยะเวลาที่โปรแกรมสมมุติทำงานให้เสร็จสมบูรณ์ในแง่ของขนาดของตัวแปรเฉพาะ อย่างไรก็ตามสำหรับอัลกอริธึมหลายอย่างคุณสามารถยืนยันได้ว่าไม่มีการป้อนข้อมูลขนาดเดียว ขอให้สังเกตว่าสิ่งนี้ขัดแย้งกับความต้องการพื้นฐานของฟังก์ชั่นอินพุตใด ๆ ควรมีเอาต์พุตไม่เกินหนึ่งตัว ดังนั้นเราจึงมีหลายฟังก์ชั่นเพื่ออธิบายความซับซ้อนของอัลกอริทึม ตอนนี้แม้ว่าการค้นหาอาร์เรย์ที่มีขนาด n อาจใช้เวลาแตกต่างกันไปขึ้นอยู่กับสิ่งที่คุณกำลังมองหาในอาร์เรย์และขึ้นอยู่กับ n ตามสัดส่วนเราสามารถสร้างคำอธิบายที่เป็นประโยชน์ของอัลกอริทึมโดยใช้ตัวพิมพ์เล็กและใหญ่ และคลาสกรณีที่เลวร้ายที่สุด
ขออภัยที่เขียนไม่ดีและขาดข้อมูลทางเทคนิคมาก แต่หวังว่ามันจะทำให้คลาสซับซ้อนกว่าเวลาที่คิดง่ายขึ้น เมื่อคุณคุ้นเคยกับสิ่งเหล่านี้มันจะกลายเป็นเรื่องง่ายในการแยกวิเคราะห์โปรแกรมของคุณและมองหาสิ่งต่าง ๆ เช่น for-loops ที่ขึ้นอยู่กับขนาดของอาเรย์และการใช้เหตุผลตามโครงสร้างข้อมูลของคุณว่าอินพุตชนิดใด ในกรณีที่เลวร้ายที่สุด
ฉันไม่รู้วิธีแก้ปัญหานี้โดยทางโปรแกรม แต่สิ่งแรกที่คนทำคือเราสุ่มตัวอย่างอัลกอริทึมสำหรับจำนวนรูปแบบการดำเนินการเสร็จแล้วพูด 4n ^ 2 + 2n +1 เรามีกฎ 2 ข้อ:
หากเราลดความซับซ้อนของ f (x) โดยที่ f (x) เป็นสูตรสำหรับการดำเนินการที่ทำได้ (4n ^ 2 + 2n + 1 อธิบายไว้ด้านบน) เราจะได้รับค่า big-O [O (n ^ 2) ในสิ่งนี้ กรณี]. แต่สิ่งนี้จะต้องคำนึงถึงการแก้ไข Lagrange ในโปรแกรมซึ่งอาจใช้งานยาก และถ้าค่า big-O ที่แท้จริงคือ O (2 ^ n) และเราอาจมีบางอย่างเช่น O (x ^ n) ดังนั้นอัลกอริทึมนี้อาจจะไม่สามารถตั้งโปรแกรมได้ แต่ถ้ามีคนพิสูจน์ฉันผิดให้รหัสฉัน . . .
สำหรับรหัส A วงรอบนอกจะดำเนินการเป็นn+1
ครั้ง '1' หมายถึงกระบวนการที่ตรวจสอบว่าฉันยังคงเป็นไปตามข้อกำหนดหรือไม่ และห่วงภายในวิ่งn
ครั้งn-2
ครั้ง .... 0+2+..+(n-2)+n= (0+n)(n+1)/2= O(n²)
ดังนั้น
สำหรับรหัส B แม้ว่าวงในจะไม่เข้ามาและเรียกใช้งาน foo () วงในก็จะถูกดำเนินการสำหรับ n ครั้งขึ้นอยู่กับเวลาในการดำเนินการลูปภายนอกซึ่งเป็น O (n)
ฉันต้องการอธิบาย Big-O ในมุมมองที่ต่างออกไปเล็กน้อย
Big-O เป็นเพียงการเปรียบเทียบความซับซ้อนของโปรแกรมซึ่งหมายถึงความเร็วที่เพิ่มขึ้นเมื่ออินพุทเพิ่มขึ้นไม่ใช่เวลาที่แน่นอนที่ใช้ในการดำเนินการ
IMHO ในสูตรบิ๊กโอคุณไม่ควรใช้สมการที่ซับซ้อนมากขึ้น (คุณอาจติดกับสูตรในกราฟต่อไปนี้) อย่างไรก็ตามคุณยังอาจใช้สูตรที่แม่นยำกว่านี้ (เช่น 3 ^ n, n ^ 3, .. .) แต่ยิ่งไปกว่านั้นบางครั้งก็อาจทำให้เข้าใจผิด! ดังนั้นดีกว่าที่จะให้มันง่ายที่สุด
ฉันต้องการเน้นอีกครั้งว่าที่นี่เราไม่ต้องการรับสูตรที่แน่นอนสำหรับอัลกอริทึมของเรา เราต้องการแสดงให้เห็นว่ามันจะเติบโตได้อย่างไรเมื่ออินพุตมีการเติบโตและเปรียบเทียบกับอัลกอริธึมอื่น ๆ ในแง่นั้น มิฉะนั้นคุณควรใช้วิธีการต่าง ๆ เช่นการทำเครื่องหมายบนม้านั่ง