นี่เป็น "กฎ" ที่เหมาะสมสำหรับการระบุสัญลักษณ์ "บิ๊กโอ" ของอัลกอริทึมหรือไม่?


29

ฉันได้เรียนรู้เพิ่มเติมเกี่ยวกับ Big O Notation และวิธีการคำนวณตามวิธีการเขียนอัลกอริทึม ฉันเจอชุด "กฎ" ที่น่าสนใจสำหรับการคำนวณอัลกอริธึมสัญลักษณ์ Big O และฉันต้องการดูว่าฉันอยู่ในเส้นทางที่ถูกต้องหรือไม่

สัญลักษณ์ Big O: N

function(n) {
    For(var a = 0; i <= n; i++) { // It's N because it's just a single loop
        // Do stuff
    }
}

สัญลักษณ์ Big O: N 2

function(n, b) {
    For(var a = 0; a <= n; a++) {
        For(var c = 0; i <= b; c++) { // It's N squared because it's two nested loops
            // Do stuff
        }
    }
}

Big O Notation: 2N

function(n, b) {
    For(var a = 0; a <= n; a++) {
        // Do stuff
    }
    For(var c = 0; i <= b; c++) { // It's 2N the loops are outside each other
        // Do stuff
    }
}

สัญลักษณ์ Big O: NLogN

function(n) {
    n.sort(); // The NLogN comes from the sort?
    For(var a = 0; i <= n; i++) {
        // Do stuff
    }
}

ตัวอย่างและสัญกรณ์ที่ตามมาของฉันถูกต้องหรือไม่? มีสิ่งเพิ่มเติมที่ฉันควรทราบหรือไม่?


3
เรียกว่ากฎง่ายๆแทนที่จะเป็นสูตรและคุณน่าจะถูก แน่นอนมันขึ้นอยู่กับสิ่งที่ "ทำสิ่ง" อย่างสมบูรณ์ โดยทั่วไปแล้ว Log (N) มาจากอัลกอริธึมที่แบ่งพาร์ติชั่นแบบไบนารี / แบบต้นไม้ นี่คือโพสต์บล็อกที่ยอดเยี่ยมในหัวข้อ
Daniel B

15
ไม่มีสิ่งใด2Nในสัญกรณ์ใหญ่
vartec

15
@ JörgWMittagเพราะ O (2n) = O (n) โดยคำจำกัดความของ Big O
ratchet freak

3
@ JörgWMittag: นี่ไม่ใช่สถานที่สำหรับหลอกล่อ
vartec

3
@vartec - ฉันไม่เชื่อว่าJörgWMittagตั้งใจหลอกหลอน ในการวิจัยล่าสุดของฉันฉันได้สังเกตเห็นความสับสนมากมายระหว่างสัญกรณ์ Big-O ที่เข้มงวดและ "ภาษาพื้นถิ่นทั่วไป" ซึ่งผสม Big-O, Theta และอนุพันธ์อื่น ๆ ฉันไม่ได้บอกว่าการใช้งานทั่วไปนั้นถูกต้อง แค่ว่ามันจะเกิดขึ้นมากมาย

คำตอบ:


26

อย่างเป็นทางการสัญลักษณ์บิ๊กโออธิบายระดับของความซับซ้อน

ในการคำนวณสัญกรณ์ใหญ่ O:

  1. ระบุสูตรสำหรับความซับซ้อนของอัลกอริทึม ตัวอย่างเช่นสมมติว่ามีสองลูปโดยมีอีกหนึ่งซ้อนอยู่ภายในและอีกสามลูปไม่ซ้อนกัน:2N² + 3N
  2. ลบทุกอย่างยกเว้นคำสูงสุด: 2N²
  3. ลบค่าคงที่ทั้งหมด:

กล่าวอีกนัยหนึ่งคือสองลูปและอีกหนึ่งซ้อนอยู่ข้างในจากนั้นอีกสามลูปที่ไม่ซ้อนกันคือO (N²)

แน่นอนว่าสิ่งที่คุณมีในลูปนั้นเป็นคำแนะนำง่ายๆ หากคุณมีตัวอย่างsort()ในวงคุณจะต้องคูณความซับซ้อนของวงโดยความซับซ้อนของการsort()ใช้ภาษา / ไลบรารีพื้นฐานของคุณใช้


อย่างเคร่งครัดพูด "เอาค่าคงที่ทุกคน" จะเปิดเข้าไป2N³ N"ลบค่าคงที่แบบเพิ่มและแบบคูณทั้งหมด" จะใกล้เคียงกับความจริง
โจอาคิมซาวเออร์

@JoachimSauer: N² = N * N ไม่มีค่าคงที่
vartec

@vartec: 2N = N+Nตามอาร์กิวเมนต์เดียวกัน
Joachim Sauer

2
@ โจอาคิมซาวเออร์ "พูดอย่างเคร่งครัด" ของคุณอย่างไม่ธรรมดา ดูen.wikipedia.org/wiki/Constant_(mathematics) เมื่อพูดถึงพหุนามคำว่า "คงที่" หมายถึงค่าสัมประสิทธิ์เสมอไม่ใช่ค่าเลขชี้กำลัง
เบ็นลี

1
@vartec ดูความคิดเห็นของฉันด้านบน การใช้ "ค่าคงที่" ของคุณที่นี่ถูกต้องและไม่เป็นทางการ
เบ็นลี

6

หากคุณต้องการวิเคราะห์อัลกอริธึมเหล่านี้คุณต้องกำหนด // dostuff ตามที่สามารถเปลี่ยนแปลงผลลัพธ์ได้ สมมติว่า dostuff ต้องการการดำเนินการ O (1) จำนวนคงที่

นี่คือตัวอย่างบางส่วนที่มีสัญลักษณ์ใหม่นี้:

สำหรับตัวอย่างแรกของคุณการเคลื่อนที่เชิงเส้น: นี่ถูกต้อง!

บน):

for (int i = 0; i < myArray.length; i++) {
    myArray[i] += 1;
}

ทำไมมันเป็นเส้นตรง (O (n)) ในขณะที่เราเพิ่มองค์ประกอบเพิ่มเติมให้กับอินพุท (อาเรย์) จำนวนการดำเนินการที่เกิดขึ้นจะเพิ่มขึ้นตามสัดส่วนของจำนวนองค์ประกอบที่เราเพิ่ม

ดังนั้นหากใช้การดำเนินการหนึ่งเพื่อเพิ่มจำนวนเต็มในหน่วยความจำเราสามารถสร้างแบบจำลองการทำงานของลูปด้วย f (x) = 5x = 5 การดำเนินการเพิ่มเติม สำหรับองค์ประกอบเพิ่มเติม 20 รายการเราดำเนินการเพิ่มเติม 20 รายการ ผ่านเดียวของอาร์เรย์มีแนวโน้มที่จะเป็นเส้นตรง ดังนั้นอัลกอริทึมเช่นการจัดเรียงถังข้อมูลซึ่งสามารถใช้ประโยชน์จากโครงสร้างของข้อมูลเพื่อทำการเรียงลำดับในหนึ่งรอบการส่งผ่านของอาร์เรย์

ตัวอย่างที่สองของคุณจะถูกต้องและมีลักษณะเช่นนี้:

O (n ^ 2):

for (int i = 0; i < myArray.length; i++) {
    for (int j = 0; j < myArray.length; j++) {
        myArray[i][j] += 1;
    }
}

ในกรณีนี้สำหรับทุกองค์ประกอบเพิ่มเติมในอาร์เรย์แรกฉันต้องประมวลผลทั้งหมดของ j การเพิ่ม 1 ถึง i เพิ่ม (ความยาวของ j) เป็น j ดังนั้นคุณถูกต้อง! รูปแบบนี้เป็น O (n ^ 2) หรือในตัวอย่างของเรามันเป็นจริง O (i * j) (หรือ n ^ 2 ถ้า i == j ซึ่งมักจะเป็นกรณีที่มีการดำเนินการเมทริกซ์หรือโครงสร้างข้อมูลสแควร์

ตัวอย่างที่สามของคุณคือสิ่งที่เปลี่ยนไปขึ้นอยู่กับ dostuff; ถ้ารหัสเขียนและทำสิ่งต่าง ๆ เป็นค่าคงที่จริงๆแล้วมันเป็นเพียง O (n) เพราะเรามี 2 ขนาดของอาร์เรย์ที่ผ่าน n และ 2n จะลดลงเป็น n การวนรอบอยู่ด้านนอกซึ่งกันและกันไม่ใช่ปัจจัยสำคัญที่สามารถสร้างรหัส 2 ^ n; นี่คือตัวอย่างของฟังก์ชั่นซึ่งเป็น 2 ^ n:

var fibonacci = function (n) {
    if (n == 1 || n == 2) {
        return 1;
    }

    else {
        return (fibonacci(n-2) + fibonacci(n-1));
    }
}

ฟังก์ชั่นนี้คือ 2 ^ n เพราะการเรียกฟังก์ชั่นแต่ละครั้งจะทำให้เกิดการเรียกสองฟังก์ชันเพิ่มเติม (Fibonacci) ทุกครั้งที่เราเรียกฟังก์ชั่นปริมาณงานที่เราต้องทำเพิ่มเป็นสองเท่า! สิ่งนี้เติบโตอย่างรวดเร็วสุดขีดเช่นการตัดหัวไฮดราและมีการงอกใหม่สองครั้งในแต่ละครั้ง!

สำหรับตัวอย่างสุดท้ายของคุณหากคุณใช้การเรียงลำดับ nlgn เช่นการเรียงแบบผสานคุณถูกต้องแล้วว่ารหัสนี้จะเป็น O (nlgn) อย่างไรก็ตามคุณสามารถใช้ประโยชน์จากโครงสร้างของข้อมูลเพื่อพัฒนาเรียงลำดับได้เร็วขึ้นในสถานการณ์ที่เฉพาะเจาะจง (เช่นช่วงค่าที่รู้จัก จำกัด ในช่วงตั้งแต่ 1-100) คุณถูกต้องในการคิดอย่างไรก็ตามรหัสการสั่งซื้อสูงสุดครอง ดังนั้นหากการเรียงลำดับ O (nlgn) อยู่ถัดจากการดำเนินการใด ๆ ที่ใช้เวลาน้อยกว่า O (nlgn) ความซับซ้อนของเวลาทั้งหมดจะเป็น O (nlgn)

ใน JavaScript (ใน Firefox อย่างน้อย) การเรียงลำดับเริ่มต้นใน Array.prototype.sort () เป็น MergeSort ดังนั้นคุณจึงดู O (nlgn) สำหรับสถานการณ์สุดท้ายของคุณ


Fibonacci ของคุณเป็นตัวอย่างจริงหรือไม่ ฉันรู้ว่าสิ่งนี้ไม่ได้ขัดแย้งกับจุดที่คุณพยายามทำ แต่ชื่ออาจทำให้ผู้อื่นเข้าใจผิดและดังนั้นจึงทำให้เสียสมาธิถ้าไม่ใช่ฟีโบนักชีจริงๆ
Paul Nikonowicz

1

ตัวอย่างที่สองของคุณ (วงนอกจาก 0 ถึงn , วงในจาก 0 ถึงb ) จะเป็น O ( nb ) ไม่ใช่ O ( n 2 ) กฎคือว่าคุณมีบางสิ่งบางอย่างคอมพิวเตอร์nครั้งและสำหรับแต่ละคนที่คุณจะคำนวณอย่างอื่นครั้งจึงเจริญเติบโตของฟังก์ชั่นนี้ขึ้นอยู่เพียงผู้เดียวในการเจริญเติบโตของn * ข

ตัวอย่างที่สามของคุณเป็นเพียง O ( n ) - คุณสามารถลบค่าคงที่ทั้งหมดเนื่องจากพวกมันไม่เติบโตด้วยnและการเติบโตคือสิ่งที่สัญลักษณ์ Big-O เป็นเรื่องเกี่ยวกับ

สำหรับตัวอย่างสุดท้ายของคุณใช่สัญกรณ์ Big-O ของคุณจะมาจากวิธีการเรียงลำดับซึ่งจะเป็นอย่างนั้นถ้าเป็นแบบเปรียบเทียบ (ตามปกติคือกรณี) ในรูปแบบที่มีประสิทธิภาพมากที่สุด O ( n * logn ) .


0

จำได้ว่านี่เป็นตัวแทนของเวลาทำงานโดยประมาณ "กฎของหัวแม่มือ" เป็นค่าประมาณเนื่องจากไม่แม่นยำ แต่ให้การประมาณคำสั่งแรกที่ดีสำหรับการประเมินผล

รันไทม์ที่แท้จริงจะขึ้นอยู่กับจำนวนฮีปพื้นที่หน่วยประมวลผลชุดคำสั่งการใช้คำนำหน้าหรือตัวดำเนินการเพิ่มหลังการแก้ไข ฯลฯ yadda การวิเคราะห์เวลาทำงานที่เหมาะสมจะช่วยให้การตัดสินใจยอมรับ แต่การมีความรู้พื้นฐานช่วยให้คุณสามารถตั้งโปรแกรมได้ตั้งแต่ต้น

ฉันเห็นด้วยว่าคุณกำลังอยู่ในเส้นทางที่ถูกต้องในการทำความเข้าใจว่า Big-O ได้รับการหาเหตุผลเข้าข้างตนเองจากตำราไปจนถึงแอปพลิเคชันที่ใช้งานได้จริง นั่นอาจเป็นอุปสรรคที่ยากที่จะเอาชนะ

อัตราการเติบโตแบบไม่แสดงอาการมีความสำคัญสำหรับชุดข้อมูลขนาดใหญ่และโปรแกรมขนาดใหญ่ดังนั้นสำหรับตัวอย่างทั่วไปที่คุณแสดงให้เห็นว่ามันไม่สำคัญต่อไวยากรณ์และตรรกะที่เหมาะสม


-1

ใหญ่โอ้โดยนิยามหมายถึง: สำหรับฟังก์ชั่น f (t) มีฟังก์ชั่น c * g (t) โดยที่ c คือค่าคงที่ตามอำเภอใจเช่นนั้น (f) <= c * g (t) สำหรับ t> n โดยที่ n เป็นค่าคงที่โดยพลการจากนั้น f (t) มีอยู่ใน O (g (t)) นี่คือสัญลักษณ์ทางคณิตศาสตร์ที่ใช้ในวิทยาศาสตร์คอมพิวเตอร์เพื่อวิเคราะห์อัลกอริทึม หากคุณสับสนฉันขอแนะนำ lookin ในความสัมพันธ์แบบปิดด้วยวิธีนี้คุณจะเห็นในมุมมองที่ละเอียดยิ่งขึ้นว่าอัลกอริธึมเหล่านี้ได้รับคุณค่าที่ยิ่งใหญ่เหล่านี้อย่างไร

ผลที่ตามมาบางส่วนของคำนิยามนี้: O (n) สอดคล้องกับ O (2n) อย่างแท้จริง

นอกจากนี้ยังมีอัลกอริทึมการเรียงลำดับที่แตกต่างกันมากมาย ค่า Big-Oh ต่ำสุดสำหรับการเปรียบเทียบคือ O (nlogn) แต่มีมากมายหลายประเภทที่มี big-oh ที่แย่กว่า ตัวอย่างเช่นการเรียงลำดับการเลือกมี O (n ^ 2) การเรียงลำดับที่ไม่ใช่การเปรียบเทียบบางอย่างอาจมีค่าที่ยิ่งใหญ่กว่ามาก การจัดเรียงที่ฝากข้อมูลตัวอย่างเช่นมี O (n)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.