หนึ่งกองสองคิว


59

พื้นหลัง

หลายปีที่ผ่านมาเมื่อฉันเป็นนักศึกษาระดับปริญญาตรีเราได้รับการบ้านเกี่ยวกับการวิเคราะห์ค่าตัดจำหน่าย ฉันไม่สามารถแก้ไขปัญหาข้อใดข้อหนึ่งได้ ฉันถามมันด้วยทฤษฎีแต่ไม่มีผลลัพธ์ที่น่าพอใจ ฉันจำหลักสูตรที่ TA ยืนยันในสิ่งที่เขาไม่สามารถพิสูจน์ได้และบอกว่าเขาลืมหลักฐานและ ... [คุณรู้ว่า]

วันนี้ฉันจำปัญหาได้ ฉันยังอยากรู้ดังนั้นที่นี่มัน ...

คำถาม

เป็นไปได้หรือไม่ที่จะนำสแต็กมาใช้โดยใช้สองคิวเพื่อให้การดำเนินการทั้งPUSH และPOPทำงานในเวลาที่ตัดจำหน่าย O (1) ? ถ้าใช่คุณจะบอกฉันได้อย่างไร

หมายเหตุ: สถานการณ์นั้นค่อนข้างง่ายหากเราต้องการใช้คิวที่มีสองกอง (พร้อมการดำเนินการที่เกี่ยวข้องENQUEUE & DEQUEUE ) โปรดสังเกตความแตกต่าง

PS: ปัญหาข้างต้นไม่ใช่การบ้าน การบ้านไม่ต้องการขอบเขตที่ต่ำกว่า เพียงการดำเนินการและการวิเคราะห์เวลาทำงาน


2
ฉันเดาว่าคุณสามารถใช้พื้นที่ในจำนวน จำกัด นอกเหนือจากสองคิว (O (1) หรือ O (log n)) ฟังดูเป็นไปไม่ได้สำหรับฉันเพราะเราไม่มีวิธีใด ๆ ในการย้อนกลับลำดับของอินพุตสตรีมที่ยาว แต่แน่นอนว่านี่ไม่ใช่ข้อพิสูจน์เว้นแต่จะสามารถอ้างสิทธิ์ได้อย่างเข้มงวด….
Tsuyoshi Ito

@Tsuyoshi: ถูกต้องเกี่ยวกับข้อ จำกัด ของพื้นที่ และใช่นั่นคือสิ่งที่ฉันพูดกับมัน (ดื้อ) ตา แต่เขาปฏิเสธ: (
MS Dousti

2
@Tsuyoshi: ฉันไม่คิดว่าคุณจะต้องมีข้อ จำกัด เกี่ยวกับพื้นที่โดยทั่วไปคุณเพียงแค่สมมติว่าคุณไม่ได้รับอนุญาตให้เก็บวัตถุที่ถูกผลักและป๊อปจากสแต็คในที่อื่นนอกเหนือจากสองคิว (และอาจ จำนวนตัวแปรคงที่)
Kaveh

@SadeqDousti ในความคิดของฉันวิธีเดียวที่จะเป็นไปได้คือถ้าคุณใช้การดำเนินการเชื่อมโยงรายชื่อของคิวและใช้ตัวชี้บางอย่างชี้ไปที่ด้านบนของ "สแต็ก"
Charles Addis

2
ดูเหมือนว่า TA อาจต้องการพูดว่า "ใช้คิวโดยใช้สองสแต็ค" ซึ่งเป็นไปได้อย่างแน่นอนใน "O (1) เวลาตัดจำหน่าย"
โทมัส Ahle

คำตอบ:


45

ฉันไม่มีคำตอบจริง แต่นี่คือหลักฐานบางอย่างที่ว่าปัญหาเปิดอยู่:

  • มันไม่ได้กล่าวถึงใน Ming Li, Luc Longpréและ Paul MB Vitányi, "พลังแห่งคิว", โครงสร้าง 1986 ซึ่งพิจารณาอีกหลายเหตุการณ์ที่เกี่ยวข้องอย่างใกล้ชิด

  • มันไม่ได้กล่าวถึงใน Martin Hühne "Theor ด้วยพลังของหลาย ๆ คิว" Theor คอมพ์ วิทย์ 2536 กระดาษที่ตามมา

  • มันไม่ได้กล่าวถึงใน Holger Petersen "กองซ้อนกับ Deques", COCOON 2001

  • เบอร์ตันโรเซ็นเบิร์ก "การรู้จำ nondeterministic อย่างรวดเร็วของภาษาที่ไม่มีบริบทโดยใช้สองคิว" แจ้ง พร เลทท์ ปี 1998 ให้อัลกอริทึมสองคิว O (n log n) สำหรับการรับรู้ CFL ใด ๆ โดยใช้สองคิว แต่ออโตเมติกแบบกดลงแบบ nondeterministic สามารถรับรู้ CFL ได้ในเวลาเชิงเส้น ดังนั้นหากมีการจำลองสแต็กที่มีสองคิวเร็วกว่า O (บันทึก n) ต่อการดำเนินการ Rosenberg และผู้ตัดสินของเขาควรรู้เกี่ยวกับมัน


4
+1 สำหรับการอ้างอิงที่ดีเยี่ยม แม้ว่าจะมีเทคนิคบางอย่าง: เอกสารบางอย่างเช่นแรกไม่ได้พิจารณาปัญหาของการจำลองหนึ่งกองโดยใช้สองคิว (เท่าที่ฉันสามารถพูดได้จากนามธรรม) บางคนพิจารณาการวิเคราะห์ที่เลวร้ายที่สุดไม่ใช่ค่าตัดจำหน่าย
MS Dousti

13

คำตอบดังต่อไปนี้คือ 'โกง' ในขณะที่มันไม่ได้ใช้พื้นที่ใด ๆ ระหว่างการดำเนินการการดำเนินงานที่ตัวเองสามารถใช้มากกว่าพื้นที่ ดูที่อื่นในหัวข้อนี้สำหรับคำตอบที่ไม่มีปัญหานี้O(1)

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

ฉันนำเสนออัลกอริทึมสองอย่างสิ่งแรกคืออัลกอริธึมแบบง่ายโดยใช้เวลาสำหรับป๊อปและที่สองด้วยO ( √)O(n)เวลาที่ใช้สำหรับป๊อป ฉันอธิบายคนแรกส่วนใหญ่เป็นเพราะความเรียบง่ายเพื่อให้คนที่สองง่ายต่อการเข้าใจO(n)

หากต้องการให้รายละเอียดเพิ่มเติม: ครั้งแรกที่ไม่ใช้พื้นที่เพิ่มเติมมีกรณีที่เลวร้ายที่สุด (และตัดจำหน่าย) Push และO ( n )กรณีที่เลวร้ายที่สุด (และตัดจำหน่าย) ป๊อป แต่พฤติกรรมกรณีที่เลวร้ายที่สุดไม่ได้เรียกเสมอ เนื่องจากไม่ได้ใช้พื้นที่เพิ่มเติมนอกเหนือจากสองคิวจึง 'ดีกว่า' เล็กน้อยกว่าโซลูชันที่ Ross Snider นำเสนอO(1)O(n)

ที่สองใช้เขตข้อมูลจำนวนเต็มเดียว (ดังนั้นพื้นที่พิเศษ ), มีO ( 1 )กรณีที่เลวร้ายที่สุด (และตัดจำหน่าย) ผลักดันและO ( √)O(1)O(1)ป๊อปตัดจำหน่าย เวลาทำงานจึงดีกว่าวิธีการ 'เรียบง่าย' อย่างมาก แต่ก็ใช้พื้นที่เพิ่มเติมบางส่วนO(n)

อัลกอริทึมแรก

เรามีสองคิว: คิวและคิวs อีo n d ฉันr s Tจะผลักดันคิว 'ของเราในขณะที่อีo n dจะเป็นคิวที่มีอยู่แล้วในการสั่งซื้อสแต็ค'ผมRsเสื้อsอีโอndผมRsเสื้อsอีโอnd

  • ผลักดันที่จะทำโดยเพียงแค่ enqueueing พารามิเตอร์บน TผมRsเสื้อ
  • Popping ทำได้ดังนี้ หากเป็นที่ว่างเปล่าเราก็ dequeue s อีo n dและกลับผล มิฉะนั้นเราจะย้อนกลับฉันr s Tผนวกทั้งหมดของs อีo n dจะฉันr s Tและสลับฉันr s Tและs อีo n d จากนั้นเราจะ dequeue s อีoผมRsเสื้อsอีโอndผมRsเสื้อsอีโอndผมRsเสื้อผมRsเสื้อsอีโอndและส่งคืนผลลัพธ์ของ dequeuesอีโอnd

รหัส C # สำหรับอัลกอริทึมแรก

สิ่งนี้ควรอ่านได้แม้ว่าคุณจะไม่เคยเห็น C # มาก่อนก็ตาม หากคุณไม่ทราบว่าชื่อสามัญคืออะไรให้แทนที่ 'สตริง' ด้วย 'สตริง' ทั้งหมดในใจของคุณเพื่อเป็นชุดของสตริง

public class Stack<T> {
    private Queue<T> first = new Queue<T>();
    private Queue<T> second = new Queue<T>();
    public void Push(T value) {
        first.Enqueue(value);
    }
    public T Pop() {
        if (first.Count == 0) {
            if (second.Count > 0)
                return second.Dequeue();
            else
                throw new InvalidOperationException("Empty stack.");
        } else {
            int nrOfItemsInFirst = first.Count;
            T[] reverser = new T[nrOfItemsInFirst];

            // Reverse first
            for (int i = 0; i < nrOfItemsInFirst; i++)
                reverser[i] = first.Dequeue();    
            for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
                first.Enqueue(reverser[i]);

            // Append second to first
            while (second.Count > 0)
                first.Enqueue(second.Dequeue());

            // Swap first and second
            Queue<T> temp = first; first = second; second = temp;

            return second.Dequeue();
        }
    }
}

การวิเคราะห์

เห็นได้ชัดว่าผลักดันงานในเวลา ป๊อปอาจจะสัมผัสกับทุกอย่างภายในฉันr s Tและs อีo n dเป็นจำนวนเงินที่คงที่ของเวลาดังนั้นเราจึงมีO ( n )ในกรณีที่เลวร้ายที่สุด ขั้นตอนวิธีการแสดงพฤติกรรมนี้ (ตัวอย่างเช่น) หากผลักดันnองค์ประกอบบนสแต็คแล้วซ้ำแล้วซ้ำอีกจะดำเนินการผลักดันไหม้และการดำเนินงานที่ป๊อปเดียวในการสืบทอดO(1)ผมRsเสื้อsอีโอndO(n)n

อัลกอริทึมที่สอง

เรามีสองคิว: คิวและคิวs อีo n d ฉันr s Tจะผลักดันคิว 'ของเราในขณะที่อีo n dจะเป็นคิวที่มีอยู่แล้วในการสั่งซื้อสแต็ค'ผมRsเสื้อsอีโอndผมRsเสื้อsอีโอnd

นี้เป็นรุ่นที่ดัดแปลงของขั้นตอนวิธีแรกที่เราทำไม่ได้ทันที 'สับ' เนื้อหาของเข้าไปs อีo n d แต่ถ้าฉันr s ทีมีขนาดเล็กจำนวนเพียงพอขององค์ประกอบเมื่อเทียบกับs อีo n d (คือรากที่สองของจำนวนขององค์ประกอบในs อีo n d ) เราเพียงจัดระเบียบฉันr s Tในลำดับสแต็กและไม่รวมเข้าด้วยผมRsเสื้อsอีโอndผมRsเสื้อsอีโอndsอีโอndผมRsเสื้อ dsอีโอnd

  • ผลักดันจะทำยังโดยเพียงแค่ enqueueing พารามิเตอร์บน TผมRsเสื้อ
  • Popping ทำได้ดังนี้ หากเป็นที่ว่างเปล่าเราก็ dequeue s อีo n dและกลับผล มิฉะนั้นเราจัดระเบียบเนื้อหาของฉันr s Tเพื่อให้พวกเขาอยู่ในลำดับที่สแต็ค ถ้า| f i r s t | < ผมRsเสื้อsอีโอndผมRsเสื้อเราก็ dequeueฉันrsTและส่งกลับผล มิฉะนั้นเราผนวกsอีondบนฉันrsTสลับฉันrsTและsอีond, dequeuesอีondและกลับผล|ผมRsเสื้อ|<|sอีโอnd|ผมRsเสื้อsอีโอndผมRsเสื้อผมRsเสื้อsอีโอndsอีโอnd

รหัส C # สำหรับอัลกอริทึมแรก

สิ่งนี้ควรอ่านได้แม้ว่าคุณจะไม่เคยเห็น C # มาก่อนก็ตาม หากคุณไม่ทราบว่าชื่อสามัญคืออะไรให้แทนที่ 'สตริง' ด้วย 'สตริง' ทั้งหมดในใจของคุณเพื่อเป็นชุดของสตริง

public class Stack<T> {
    private Queue<T> first = new Queue<T>();
    private Queue<T> second = new Queue<T>();
    int unsortedPart = 0;
    public void Push(T value) {
        unsortedPart++;
        first.Enqueue(value);
    }
    public T Pop() {
        if (first.Count == 0) {
            if (second.Count > 0)
                return second.Dequeue();
            else
                throw new InvalidOperationException("Empty stack.");
        } else {
            int nrOfItemsInFirst = first.Count;
            T[] reverser = new T[nrOfItemsInFirst];

            for (int i = nrOfItemsInFirst - unsortedPart - 1; i >= 0; i--)
                reverser[i] = first.Dequeue();

            for (int i = nrOfItemsInFirst - unsortedPart; i < nrOfItemsInFirst; i++)
                reverser[i] = first.Dequeue();

            for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
                first.Enqueue(reverser[i]);

            unsortedPart = 0;
            if (first.Count * first.Count < second.Count)
                return first.Dequeue();
            else {
                while (second.Count > 0)
                    first.Enqueue(second.Dequeue());

                Queue<T> temp = first; first = second; second = temp;

                return second.Dequeue();
            }
        }
    }
}

การวิเคราะห์

เห็นได้ชัดว่าผลักดันงานในเวลาO(1)

ป๊อปทำงานในเวลาตัดจำหน่าย มีสองกรณี: ถ้า| first| <O(n)แล้วเราสับเปลี่ยนฉันrsTเป็นใบสั่งสแต็คในO(|ฉันrsT|)=O(|ผมRsเสื้อ|<|sอีโอnd|ผมRsเสื้อเวลา ถ้า| first| O(|ผมRsเสื้อ|)=O(n)แล้วเราจะต้องมีอย่างน้อย|ผมRsเสื้อ||sอีโอnd|เรียกร้องให้กด ดังนั้นเราสามารถตีคดีนี้ได้ทุกnโทรไปยัง Push and Pop เวลาทำงานจริงสำหรับกรณีนี้คือO(n)ดังนั้นเวลาที่ตัดจำหน่ายคือO( n)nO(n) )O(nn)=O(n)

หมายเหตุสุดท้าย

มันเป็นไปได้ที่จะกำจัดตัวแปรพิเศษด้วยค่าใช้จ่ายในการสร้าง Pop an การดำเนินงานโดยมีป๊อปจัดระเบียบฉันrsTทวงถามแทนที่จะต้องกดทุกทำทุกงานO(n)ผมRsเสื้อ


ฉันแก้ไขย่อหน้าแรกเพื่อให้คำตอบของฉันเป็นสูตรจริงสำหรับคำตอบของคำถาม
Alex ten Brink

6
คุณกำลังใช้อาร์เรย์ (reverser) สำหรับการย้อนกลับ! ฉันไม่คิดว่าคุณจะได้รับอนุญาตให้ทำเช่นนี้
Kaveh

จริงฉันใช้พื้นที่เพิ่มเติมในขณะที่ดำเนินการวิธีการ แต่ฉันคิดว่ามันจะได้รับอนุญาต: ถ้าคุณต้องการใช้คิวโดยใช้สองกองในทางตรงไปตรงมาคุณต้องย้อนกลับหนึ่งในกองที่จุดหนึ่งและเท่าที่ ฉันรู้ว่าคุณต้องการพื้นที่เพิ่มเติมเพื่อทำเช่นนั้นเนื่องจากคำถามนี้คล้ายกันฉันคิดว่าการใช้พื้นที่พิเศษในระหว่างการดำเนินการของวิธีการจะได้รับอนุญาตตราบใดที่คุณไม่ได้ใช้พื้นที่เพิ่มเติมระหว่างการเรียกใช้เมธอด
Alex สิบ Brink

6
"ถ้าคุณต้องการที่จะใช้คิวโดยใช้สองสแต็คในทางตรงไปตรงมาคุณจะต้องย้อนกลับสแต็คหนึ่งในจุดหนึ่งและเท่าที่ฉันรู้ว่าคุณต้องการพื้นที่พิเศษในการทำเช่นนั้น" --- คุณทำไม่ได้ มีวิธีในการรับค่า Enqueue ที่ตัดจำหน่ายเป็น 3 และค่า Dequeue ที่ตัดจำหน่ายเป็น 1 (เช่นทั้ง O (1)) พร้อมหน่วยความจำหนึ่งเซลล์และสองกอง ส่วนที่แข็งเป็นเครื่องพิสูจน์ไม่ใช่การออกแบบอัลกอริธึม
Aaron Sterling

หลังจากคิดถึงเรื่องนี้เพิ่มขึ้นฉันรู้ว่าฉันกำลังโกงจริง ๆ และความคิดเห็นก่อนหน้าของฉันนั้นผิดอย่างแน่นอน ฉันได้พบวิธีแก้ไขมัน: ฉันคิดอัลกอริธึมสองตัวที่มีเวลาทำงานเหมือนกับสองข้างต้น (แม้ว่า Push คือตอนนี้การดำเนินการใช้เวลานานและป๊อปก็เสร็จในเวลาคงที่) โดยไม่ต้องใช้พื้นที่เพิ่มเติมเลย ฉันจะโพสต์คำตอบใหม่เมื่อฉันเขียนมันทั้งหมดลงไป
Alex ten Brink

12

ตามความคิดเห็นบางคำตอบก่อนหน้านี้ของฉันมันชัดเจนสำหรับฉันว่าฉันโกงมากหรือน้อย: ฉันใช้พื้นที่พิเศษ ( พื้นที่พิเศษในอัลกอริธึมที่สอง) ระหว่างการดำเนินการตามวิธี Pop ของฉันO(n)

อัลกอริทึมต่อไปนี้ไม่ใช้ช่องว่างเพิ่มเติมระหว่างวิธีการและมีช่องว่างพิเศษระหว่างการดำเนินการ Push และ Pop ดันมีO ( O(1)เวลาในการตัดจำหน่ายและป๊อปมีO(1)กรณีที่เลวร้ายที่สุด (และตัดจำหน่าย) เวลาในการทำงานO(n)O(1)

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

อัลกอริทึม

เรามีสองคิว: คิวและคิวs อีo n d ฉันr s Tจะ 'แคช' ของเราในขณะที่อีo n dจะเป็นที่เก็บ 'หลักของเรา คิวทั้งสองจะอยู่ใน 'คำสั่งซื้อสแต็ก' เสมอ ฉันr s Tจะมีองค์ประกอบที่ด้านบนของสแต็คและที่s อีo n dจะมีองค์ประกอบที่ด้านล่างของสแต็ค ขนาดของf ฉันrผมRsเสื้อsอีโอndผมRsเสื้อsอีโอndผมRsเสื้อsอีโอndจะเป็นที่มากที่สุดรากที่สองของ s อีo n dผมRsเสื้อsอีโอnd

  • ผลักดันที่จะทำโดย 'แทรก' พารามิเตอร์ในช่วงเริ่มต้นของคิวดังต่อไปนี้เรา enqueue พารามิเตอร์ที่จะแล้ว dequeue และ re-enqueue องค์ประกอบอื่น ๆ ทั้งหมดในฉันr s T วิธีนี้พารามิเตอร์จะสิ้นสุดลงในช่วงเริ่มต้นของฉันr s TผมRsเสื้อผมRsเสื้อfirst
  • หากกลายเป็นขนาดใหญ่กว่ารากที่สองของs อีo n dเรา enqueue องค์ประกอบทั้งหมดของs อีo n dบนฉันr s ทีหนึ่งโดยหนึ่งแล้วสลับฉันr s Tและs อีo n d วิธีนี้องค์ประกอบของฉันr s T (บนสุดของสแต็ค) จบลงที่หัวของs อีfirstsecondsecondfirstfirstsecondfirst dsecond
  • ป๊อปจะกระทำโดย dequeueing และกลับมาผลหากฉันr s Tไม่ว่างเปล่าและอื่น ๆ โดย dequeueing s อีo n dและกลับมาผลfirsเสื้อfirsเสื้อsecond

รหัส C # สำหรับอัลกอริทึมแรก

รหัสนี้ควรอ่านได้แม้ว่าคุณจะไม่เคยเห็น C # มาก่อนก็ตาม หากคุณไม่ทราบว่าชื่อสามัญคืออะไรให้แทนที่ 'สตริง' ด้วย 'สตริง' ทั้งหมดในใจของคุณเพื่อเป็นชุดของสตริง

public class Stack<T> {
    private Queue<T> first = new Queue<T>();
    private Queue<T> second = new Queue<T>();
    public void Push(T value) {
        // I'll explain what's happening in these comments. Assume we pushed
        // integers onto the stack in increasing order: ie, we pushed 1 first,
        // then 2, then 3 and so on.

        // Suppose our queues look like this:
        // first: in 5 6 out
        // second: in 1 2 3 4 out
        // Note they are both in stack order and first contains the top of
        // the stack.

        // Suppose value == 7:
        first.Enqueue(value);
        // first: in 7 5 6 out
        // second: in 1 2 3 4 out

        // We restore the stack order in first:
        for (int i = 0; i < first.Count - 1; i++)
            first.Enqueue(first.Dequeue());
        // first.Enqueue(first.Dequeue()); is executed twice for this example, the 
        // following happens:
        // first: in 6 7 5 out
        // second: in 1 2 3 4 out
        // first: in 5 6 7 out
        // second: in 1 2 3 4 out

        // first exeeded its capacity, so we merge first and second.
        if (first.Count * first.Count > second.Count) {
            while (second.Count > 0)
                first.Enqueue(second.Dequeue());
            // first: in 4 5 6 7 out
            // second: in 1 2 3 out
            // first: in 3 4 5 6 7 out
            // second: in 1 2 out
            // first: in 2 3 4 5 6 7 out
            // second: in 1 out
            // first: in 1 2 3 4 5 6 7 out
            // second: in out

            Queue<T> temp = first; first = second; second = temp;
            // first: in out
            // second: in 1 2 3 4 5 6 7 out
        }
    }
    public T Pop() {
        if (first.Count == 0) {
            if (second.Count > 0)
                return second.Dequeue();
            else
                throw new InvalidOperationException("Empty stack.");
        } else
            return first.Dequeue();
    }
}

การวิเคราะห์

เห็นได้ชัดว่าป๊อปทำงานในเวลาในกรณีที่เลวร้ายที่สุดO(1)

พุชทำงานในเวลาตัดจำหน่าย มีสองกรณี: ถ้า| first| <O(n)จากนั้น Push ใช้เวลาO(|first|<|second|เวลา ถ้า| first| O(n)แล้วกดใช้เวลาO(n)เวลา แต่หลังจากการดำเนินการนี้ฉันrsTจะว่างเปล่า มันจะใช้O(|first||second|O(n)firstเวลาก่อนที่เราจะได้รับกรณีนี้อีกครั้งดังนั้นเวลาที่ตัดจำหน่ายคือO( nO(n)เวลาO(nn)=O(n)


เกี่ยวกับการลบคำตอบโปรดดูที่meta.cstheory.stackexchange.com/q/386/873
MS Dousti

first.Enqueue(first.Dequeue())ฉันไม่สามารถเข้าใจบรรทัด คุณพิมพ์ผิดบางอย่าง?
MS Dousti

ขอบคุณสำหรับลิงค์ฉันปรับปรุงคำตอบเดิมของฉันตาม ประการที่สองฉันได้เพิ่มความคิดเห็นจำนวนมากในรหัสของฉันเพื่ออธิบายสิ่งที่เกิดขึ้นในระหว่างการดำเนินการของอัลกอริทึมของฉันฉันหวังว่ามันจะกำจัดความสับสนใด ๆ
อเล็กซ์สิบบริงค์

สำหรับฉันอัลกอริทึมสามารถอ่านได้ง่ายขึ้นและเข้าใจง่ายขึ้นก่อนการแก้ไข
Kaveh

9

ฉันอ้างว่าเรามีค่าตัดจำหน่ายต่อการดำเนินงาน อัลกอริทึมของอเล็กซ์ให้ขอบเขตบน เพื่อพิสูจน์ขอบเขตล่างฉันให้ลำดับกรณีที่แย่ที่สุดของ PUSH และ POPΘ(ยังไม่มีข้อความ)

ลำดับเคสที่แย่ที่สุดประกอบด้วยการดำเนินการ PUSH ตามด้วยยังไม่มีข้อความการดำเนินงาน N PUSH และยังไม่มีข้อความการทำงานของ POP แบบ Nตามด้วยอีกครั้งยังไม่มีข้อความการดำเนินงาน N PUSH และยังไม่มีข้อความ POP เป็นต้นนั่นคือ:ยังไม่มีข้อความ

PยูSHยังไม่มีข้อความ(PยูSHยังไม่มีข้อความPOPยังไม่มีข้อความ)ยังไม่มีข้อความ

พิจารณาสถานการณ์หลังจากการดำเนินการ PUSH เริ่มต้น ไม่ว่าอัลกอริทึมจะทำงานอย่างไรอย่างน้อยหนึ่งในคิวต้องมีอย่างน้อยN / 2รายการยังไม่มีข้อความยังไม่มีข้อความ/2

ตอนนี้ให้พิจารณางานที่ต้องจัดการกับ (ชุดแรก) PUSH และ POP การดำเนินงาน ชั้นเชิงอัลกอริทึมใด ๆ จะต้องตกอยู่ในหนึ่งในสองกรณี:ยังไม่มีข้อความ

ในกรณีแรกอัลกอริทึมจะใช้ทั้งสองคิว คิวที่ใหญ่กว่านี้มีอย่างน้อยรายการในนั้นดังนั้นเราต้องเสียค่าใช้จ่ายอย่างน้อยการดำเนินการคิวN / 2เพื่อที่จะดึงข้อมูลได้ในที่สุดแม้แต่องค์ประกอบเดียวที่เรา ENQUEUE และต่อมาจำเป็นต้อง DEQUEUE จากคิวที่ใหญ่กว่านี้ยังไม่มีข้อความ/2ยังไม่มีข้อความ/2

ในกรณีที่สองอัลกอริทึมไม่ได้ใช้ทั้งสองคิว สิ่งนี้ช่วยลดปัญหาในการจำลองสแต็กด้วยคิวเดียว แม้ว่าคิวนี้จะว่างเปล่าในตอนแรกเราไม่สามารถทำได้ดีกว่าการใช้คิวเป็นรายการแบบวงกลมที่มีการเข้าถึงตามลำดับและปรากฏตรงไปตรงมาว่าเราต้องใช้อย่างน้อยการดำเนินการคิว N /2โดยเฉลี่ยสำหรับแต่ละ2ยังไม่มีข้อความ/2การดำเนินการสแต็ก2ยังไม่มีข้อความ

ในทั้งสองกรณีเราต้องใช้เวลาอย่างน้อย (การทำงานคิว) เพื่อจัดการ2 ยังไม่มีข้อความ/2การดำเนินการสแต็ก เพราะเราสามารถทำซ้ำขั้นตอนนี้2ยังไม่มีข้อความคูณเราต้องการNยังไม่มีข้อความในการประมวลผลการดำเนินการทั้งหมด3Nสแต็กทำให้ขอบเขตต่ำกว่าΩ(ยังไม่มีข้อความยังไม่มีข้อความ/23ยังไม่มีข้อความเวลาตัดจำหน่ายต่อการดำเนินงานΩ(ยังไม่มีข้อความ)


แจ็คแก้ไขสิ่งนี้ดังนั้นจำนวนรอบ (เลขชี้กำลังในวงเล็บ) คือแทนตามที่ฉันมี เพราะนี่คือสิ่งที่ฉันได้ก่อนที่จะแสดงให้คุณไม่สามารถหักกลบลบล้างกว่าทั้งลำดับคือ "overkill" และคุณสามารถเห็นได้จากเพียงยังไม่มีข้อความวนซ้ำ ขอบคุณแจ็ค! ยังไม่มีข้อความ
Shaun Harker

สิ่งที่เกี่ยวกับการผสมผสานของสองกรณีนี้? ตัวอย่างเช่นเรากดnQ1ยังไม่มีข้อความ/2Q22nn4:1+2+...+n+n2n .
hengxin

เห็นได้ชัดว่าคำตอบของปีเตอร์ขัดแย้งกับขอบเขตล่างนี้หรือไม่?
Joe

PยูSHยังไม่มีข้อความPOPยังไม่มีข้อความO(ยังไม่มีข้อความ)
Shaun Harker

O(ยังไม่มีข้อความ)

6

O(LGn)พียูsชั่วโมงพีโอพีพีโอพีO(LGn)พีโอพี มีการร้องขอและเอาต์พุตคิวว่างคุณดำเนินการลำดับการสับแบบสมบูรณ์เพื่อย้อนกลับอินพุตคิวและเก็บไว้ในเอาต์พุตคิว

O(1)

เท่าที่ฉันรู้นี่เป็นความคิดใหม่ ...



โอ๊ะ! ฉันควรจะมองหาคำถามที่ปรับปรุงหรือเกี่ยวข้อง เอกสารที่คุณเชื่อมโยงในคำตอบก่อนหน้าของคุณมีความสัมพันธ์ระหว่าง k กองและ k + 1 กอง เคล็ดลับนี้จบลงด้วยการเพิ่มพลังของคิวคิวระหว่าง k และ k + 1 สแต็คหรือไม่? ถ้าเป็นเช่นนั้นมันเป็นไซด์โนทที่เรียบร้อย ไม่ว่าจะด้วยวิธีใดขอบคุณที่เชื่อมโยงฉันเข้ากับคำตอบของคุณดังนั้นฉันจึงไม่เสียเวลามากนักในการเขียนเรื่องนี้สำหรับสถานที่อื่น
Peter Boothe

1

โดยไม่ต้องใช้พื้นที่เพิ่มเติมอาจใช้คิวจัดลำดับความสำคัญและบังคับให้กดใหม่แต่ละครั้งเพื่อให้มีลำดับความสำคัญที่ใหญ่กว่าก่อนหน้านี้หรือไม่ แม้ว่าจะไม่เป็น O (1)


0

ฉันไม่สามารถรับคิวเพื่อใช้สแต็กในเวลาคงที่ที่ถูกตัดจำหน่าย อย่างไรก็ตามฉันสามารถคิดถึงวิธีรับสองคิวเพื่อใช้สแต็กในเวลาเชิงเส้นที่เลวร้ายที่สุด

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

แน่นอนว่าเราสามารถเพิ่มสถานะภายนอกอีกเล็กน้อยซึ่งบอกเราว่าการดำเนินการครั้งสุดท้ายเป็นการผลักหรือป๊อป เราสามารถชะลอการย้ายทุกอย่างจากคิวหนึ่งไปอีกคิวหนึ่งจนกระทั่งเราได้รับการดำเนินการพุชสองครั้งติดต่อกัน สิ่งนี้ยังทำให้การดำเนินการของป๊อปซับซ้อนขึ้นเล็กน้อย สิ่งนี้ทำให้เรามี O (1) ค่าตัดจำหน่ายที่ซับซ้อนสำหรับการดำเนินงานป๊อป น่าเสียดายที่การกดยังคงเป็นเส้นตรง

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

หากคุณต้องการได้รับการดำเนินการเวลาคงที่ตัดจำหน่ายคุณอาจจะต้องทำสิ่งที่ฉลาดกว่า


4
แน่นอนฉันสามารถใช้คิวเดี่ยวที่มีความซับซ้อนของเวลาและเวลาที่เลวร้ายกว่าเดิมโดยไม่มีความยุ่งยากโดยถือว่าคิวเป็นรายการแบบวงกลมโดยมีองค์ประกอบคิวเพิ่มเติมที่แสดงด้านบนสุดของสแต็ก
Dave Clarke

ดูเหมือนว่าคุณสามารถ! อย่างไรก็ตามดูเหมือนว่าจำเป็นต้องมีคิวคลาสสิกมากกว่าหนึ่งคิวในการจำลองสแต็กด้วยวิธีนี้
Ross Snider

0

มีวิธีแก้ปัญหาเล็กน้อยหากคิวของคุณอนุญาตให้โหลดแบบด้านหน้าซึ่งต้องใช้เพียงหนึ่งคิวเท่านั้น (หรือโดยเฉพาะเจาะจงมากยิ่งขึ้น) บางทีนี่อาจเป็นประเภทของคิวที่หลักสูตร TA ในคำถามเดิมมีอยู่ในใจหรือไม่

นี่เป็นอีกทางเลือกหนึ่ง:

อัลกอริทึมนี้ต้องใช้สองคิวและสองพอยน์เตอร์เราจะเรียกพวกเขาว่า Q1, Q2, หลักและรองตามลำดับ เมื่อเริ่มต้น Q1 และ Q2 จะว่างเปล่าคะแนนหลักไปที่ Q1 และคะแนนรองถึง Q2

การดำเนินการผลักดันเป็นเรื่องเล็กน้อยประกอบด้วยเพียง:

*primary.enqueue(value);

การดำเนินการกับ POP มีส่วนเกี่ยวข้องมากกว่าเล็กน้อย มันต้องการการสปูลทั้งหมด แต่รายการสุดท้ายของคิวหลักไปที่คิวรองสลับพอยน์เตอร์และส่งคืนรายการสุดท้ายที่เหลือจากคิวเดิม:

while(*primary.size() > 1)
{
    *secondary.enqueue(*primary.dequeue());
}

swap(primary, secondary);
return(*secondary.dequeue());

ไม่มีการตรวจสอบขอบเขตและไม่มี O (1)

ขณะที่ฉันพิมพ์สิ่งนี้ฉันเห็นว่าสิ่งนี้สามารถทำได้โดยใช้คิวเดียวโดยใช้ for for loop แทนที่จะเป็นลูปชั่วขณะเหมือน Alex ที่ทำไปแล้ว ทั้งสองวิธีการดำเนินการ PUSH คือ O (1) และการดำเนินการ POP กลายเป็น O (n)


นี่คือโซลูชันอื่นที่ใช้สองคิวและตัวชี้หนึ่งเรียกว่า Q1, Q2 และ que_p ตามลำดับ:

เมื่อเริ่มต้น Q1 และ Q2 จะว่างเปล่าและ queue_p ชี้ไปที่ Q1

อีกครั้งการดำเนินการ PUSH เป็นเรื่องเล็กน้อย แต่ไม่ต้องการขั้นตอนเพิ่มเติมอีกหนึ่งขั้นตอนในการชี้ Que_p ที่คิวอื่น:

*queue_p.enqueue(value);
queue_p = (queue_p == &Q1) ? &Q2 : &Q1;

การดำเนินการ POP คล้ายกับก่อนหน้านี้ แต่ตอนนี้มีรายการ n / 2 ที่ต้องหมุนผ่านคิว:

queue_p = (queue_p == &Q1) ? &Q2 : &Q1;
for(i=0, i<(*queue_p.size()-1, i++)
{
    *queue_p.enqueue(*queue_p.dequeue());
}
return(*queue_p.dequeue());

การดำเนินการ PUSH ยังคงเป็น O (1) แต่ตอนนี้การดำเนินการ POP คือ O (n / 2)

โดยส่วนตัวแล้วสำหรับปัญหานี้ฉันชอบความคิดที่จะนำคิวเดี่ยวและคู่ (deque) ไปใช้และเรียกมันว่าสแต็กเมื่อเราต้องการ


อัลกอริทึมที่สองของคุณมีประโยชน์ในการทำความเข้าใจกับอเล็กซ์ที่เกี่ยวข้องมากขึ้น
hengxin

0

kΘ(n1/k)

k
n
O(1)

ผมΘ(nผม/k)Θ(nผม/k)O(1)ผม+1O(n1/k)ผม-1Θ(n1/k)

ม.ม.Ω(ม.n1/k)โอ(n1/k)Ω(n1/k)โอ(n2/k)kโอ(n)

Θ(เข้าสู่ระบบn)


-3

สแต็กอาจถูกนำมาใช้โดยใช้สองคิวโดยใช้คิวที่สองเป็น ab uffer เมื่อไอเท็มถูกส่งไปยังสแต็กจะถูกเพิ่มเข้ากับท้ายคิว แต่ละครั้งที่ไอเท็มถูกดึงองค์ประกอบ n - 1 ของคิวแรกจะต้องย้ายไปที่สองในขณะที่ไอเท็มที่เหลือจะถูกส่งคืน QueueStack ระดับสาธารณะใช้ ts IStack {IQueue ส่วนตัว q1 = คิวใหม่ (); IQueue ส่วนตัว q2 = คิวใหม่ (); โมฆะสาธารณะแบบพุช (E e) {q1.enqueue (e) // O (1)} ป๊อปอีสาธารณะ (E e) {ในขณะที่ (1 <q1.size ()) // O (n) {q2.enqueue ( q1.dequeue ()); } sw apQueues (); ส่งคืน q2.dequeue (); } p ระว่างโมฆะ swapQueues () {IQueue Q = q2; q2 = q1; q1 = Q; }}


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