Một ngăn xếp, hai hàng đợi


59

lý lịch

Cách đây vài năm, khi tôi còn là sinh viên, chúng tôi được giao bài tập về phân tích khấu hao. Tôi đã không thể giải quyết một trong những vấn đề. Tôi đã hỏi nó trong comp.theory , nhưng không có kết quả khả quan nào được đưa ra. Tôi nhớ khóa học TA khăng khăng về điều gì đó mà anh ta không thể chứng minh, và nói rằng anh ta đã quên bằng chứng, và ... [bạn biết gì].

Hôm nay, tôi nhớ lại vấn đề. Tôi vẫn háo hức muốn biết, vì vậy đây là ...

Câu hỏi

Có thể thực hiện một ngăn xếp bằng cách sử dụng hai hàng đợi để cả hai hoạt động PUSHPOP chạy trong thời gian khấu hao O (1) không? Nếu có, bạn có thể cho tôi biết làm thế nào?

Lưu ý: Tình huống khá dễ dàng nếu chúng ta muốn thực hiện một hàng đợi với hai ngăn xếp (với các thao tác tương ứng ENITEUE & DEITEUE ). Hãy quan sát sự khác biệt.

PS: Vấn đề trên không phải là bài tập về nhà. Bài tập về nhà không yêu cầu bất kỳ giới hạn dưới; chỉ là một thực hiện và phân tích thời gian chạy.


2
Tôi đoán rằng bạn chỉ có thể sử dụng một lượng không gian hạn chế ngoài hai hàng đợi (O (1) hoặc O (log n)). Âm thanh là không thể đối với tôi, bởi vì chúng tôi không có cách nào để đảo ngược thứ tự của một luồng đầu vào dài. Nhưng tất nhiên đây không phải là bằng chứng trừ khi có thể đưa ra yêu sách khắt khe.
Tsuyoshi Ito

@Tsuyoshi: Bạn nói đúng về giả định không gian hạn chế. Và vâng, đó là những gì tôi đã nói với TA (bướng bỉnh) đó, nhưng anh ấy đã từ chối :(
MS Dousti

2
@Tsuyoshi: Tôi không nghĩ rằng bạn cần phải giả định về một không gian nói chung, bạn chỉ cần giả định rằng bạn không được phép lưu trữ các vật thể được đẩy và bật ra khỏi ngăn xếp ở bất kỳ nơi nào khác ngoài hai hàng đợi (và có lẽ một số lượng biến không đổi).
Kaveh

@SadeqDousti Theo tôi, cách duy nhất có thể xảy ra là nếu bạn sử dụng triển khai danh sách liên kết của hàng đợi và sử dụng một số con trỏ để luôn luôn chỉ đến đỉnh của "ngăn xếp"
Charles Addis

2
Nghe có vẻ như TA thực sự đã muốn nói "Thực hiện một hàng đợi bằng cách sử dụng hai ngăn xếp", điều này thực sự có thể chính xác trong "thời gian khấu hao O (1)".
Thomas Ahle

Câu trả lời:


45

Tôi không có câu trả lời thực sự, nhưng đây là một số bằng chứng cho thấy vấn đề đang mở:

  • Nó không được đề cập trong Ming Li, Luc Longpré và Paul MB Vitányi, "Sức mạnh của hàng đợi", Structures 1986, xem xét một số mô phỏng liên quan chặt chẽ khác

  • Nó không được đề cập trong Martin Hühne, "Về sức mạnh của một số hàng đợi", Theor. Comp. Khoa học. 1993, một bài báo tiếp theo.

  • Nó không được đề cập trong Holger Petersen, "Stacks vs. Deques", COCOON 2001.

  • Burton Rosenberg, "Nhận biết nhanh các ngôn ngữ không ngữ cảnh bằng cách sử dụng hai hàng đợi", Thông báo. Proc. Lett. 1998, đưa ra thuật toán hai hàng đợi O (n log n) để nhận ra bất kỳ CFL nào bằng cách sử dụng hai hàng đợi. Nhưng một máy tự động đẩy xuống không điều kiện có thể nhận ra CFL trong thời gian tuyến tính. Vì vậy, nếu có một mô phỏng của một ngăn xếp có hai hàng đợi nhanh hơn O (log n) cho mỗi thao tác, Rosenberg và các trọng tài của anh ta nên biết về nó.


4
+1 để tham khảo tuyệt vời. Mặc dù vậy, có một số kỹ thuật: Một số bài báo, như bài đầu tiên, không xem xét vấn đề mô phỏng một ngăn xếp bằng hai hàng đợi (nhiều như tôi có thể nói từ bản tóm tắt). Những người khác xem xét phân tích trường hợp xấu nhất, không khấu hao chi phí.
MS Dousti

13

Câu trả lời dưới đây là 'gian lận', trong khi nó không sử dụng bất kỳ khoảng trống nào giữa các hoạt động, bản thân các hoạt động có thể sử dụng nhiều hơn không gian . Xem những nơi khác trong chủ đề này để biết câu trả lời không có vấn đề này.O(1)

Trong khi tôi không có một câu trả lời cho câu hỏi chính xác của bạn, tôi đã tìm thấy một thuật toán làm việc tại thời gian thay vìO(n). Tôi tin rằng điều này là chặt chẽ, mặc dù tôi không có bằng chứng. Nếu bất cứ điều gì, thuật toán cho thấy rằng cố gắng chứng minh giới hạn dưới củaO(n)là vô ích, vì vậy nó có thể giúp trả lời câu hỏi của bạn.O(n)O(n)O(n)

Tôi trình bày hai thuật toán, đầu tiên là một thuật toán đơn giản với một thời gian chạy cho Pop và lần thứ hai với một O ( O(n)thời gian chạy cho Pop. Tôi mô tả cái đầu tiên chủ yếu vì sự đơn giản của nó để cái thứ hai dễ hiểu hơn.O(n)

Để được cung cấp thêm chi tiết: người đầu tiên sử dụng không gian bổ sung, có trường hợp xấu nhất (và khấu hao) Đẩy và một O ( n ) trường hợp xấu nhất (và khấu hao) Pop, nhưng hành vi trường hợp xấu nhất không phải luôn luôn được kích hoạt. Vì nó không sử dụng bất kỳ không gian bổ sung nào ngoài hai hàng đợi, nên nó "tốt hơn" một chút so với giải pháp được cung cấp bởi Ross Snider.O(1)O(n)

Thứ hai sử dụng một trường số nguyên đơn (do đó thêm không gian), có một O ( 1 ) trường hợp xấu nhất (và khấu hao) Đẩy và O ( O(1)O(1)khấu hao Pop. Do đó, thời gian chạy tốt hơn đáng kể so với phương pháp 'đơn giản', nhưng nó sử dụng thêm một số không gian.O(n)

Thuật toán đầu tiên

Chúng tôi có hai hàng đợi: queue và queue s e c o n d . f i r s t sẽ là 'hàng đợi đẩy' của chúng tôi, trong khi s e c o n d sẽ là hàng đợi đã có trong 'thứ tự ngăn xếp'.firstsecondfirstsecond

  • Việc đẩy được thực hiện bằng cách đơn giản là ghi lại tham số vào .first
  • Popping được thực hiện như sau. Nếu trống, chúng ta chỉ cần dequeue s e c o n d và trả về kết quả. Nếu không, chúng ta đảo ngược f i r s t , thêm tất cả các s đ c o n d để f i r s t và hoán đổi f i r s ts đ c o n d . Chúng tôi sau đó dequeue s e c ofirstsecondfirstsecondfirstfirstsecond và trả về kết quả của dequeue.second

Mã C # cho thuật toán đầu tiên

Điều này có thể khá dễ đọc, ngay cả khi bạn chưa bao giờ thấy C # trước đây. Nếu bạn không biết khái quát là gì, chỉ cần thay thế tất cả các trường hợp 'T' bằng 'chuỗi' trong tâm trí của bạn, cho một chuỗi các chuỗi.

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();
        }
    }
}

Phân tích

Rõ ràng Push hoạt động trong thời gian . Pop có thể chạm vào mọi thứ bên trong f i r s ts e c o n d một số lần không đổi, vì vậy chúng ta có O ( n ) trong trường hợp xấu nhất. Thuật toán thể hiện hành vi này (ví dụ) nếu một người đẩy n phần tử lên ngăn xếp và sau đó liên tục thực hiện một lần đẩy đơn lẻ và một thao tác Pop liên tiếp.O(1)firstsecondO(n)n

Thuật toán thứ hai

Chúng tôi có hai hàng đợi: queue và queue s e c o n d . f i r s t sẽ là 'hàng đợi đẩy' của chúng tôi, trong khi s e c o n d sẽ là hàng đợi đã có trong 'thứ tự ngăn xếp'.firstsecondfirstsecond

Đây là phiên bản phù hợp của thuật toán đầu tiên, trong đó chúng tôi không ngay lập tức 'xáo trộn' nội dung của thành s e c o n d . Thay vào đó, nếu f i r s t chứa một số lượng phần tử đủ nhỏ so với s e c o n d (cụ thể là căn bậc hai của số phần tử trong s e c o n d ), chúng tôi chỉ tổ chức lại f i r s t vào thứ tự ngăn xếp và không hợp nhất nó vớifirstsecondfirstsecondsecondfirst .second

  • Việc đẩy vẫn được thực hiện bằng cách đơn giản là nhập thông số vào .first
  • Popping được thực hiện như sau. Nếu trống, chúng ta chỉ cần dequeue s e c o n d và trả về kết quả. Mặt khác, chúng tôi sắp xếp lại nội dung của f i r s t để chúng theo thứ tự ngăn xếp. Nếu | f i r s t | < firstsecondfirstchúng tôi chỉ đơn giản là dequeuefirstvà trả về kết quả. Nếu không, chúng ta thêmsđcondvàofirst, trao đổifirstsđcond, dequeuesđcondvà trả kết quả.|first|<|second|firstsecondfirstfirstsecondsecond

Mã C # cho thuật toán đầu tiên

Điều này có thể khá dễ đọc, ngay cả khi bạn chưa bao giờ thấy C # trước đây. Nếu bạn không biết khái quát là gì, chỉ cần thay thế tất cả các trường hợp 'T' bằng 'chuỗi' trong tâm trí của bạn, cho một chuỗi các chuỗi.

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();
            }
        }
    }
}

Phân tích

Rõ ràng Push hoạt động trong thời gian .O(1)

Pop hoạt động trong thời gian khấu hao. Có hai trường hợp: nếu| first| <O(n), Sau đó chúng tôi xáo trộnfirstvào ngăn xếp trật tự trongO(|first|)=O(|first|<|second|firstthời gian. Nếu| first| O(|first|)=O(n), Sau đó chúng ta phải có ít nhất|first||second| gọi cho Push. Do đó, chúng tôi chỉ có thể bắn trúng trường hợp này mỗin gọi tới Push và Pop. Thời gian chạy thực tế cho trường hợp này làO(n), vì vậy thời gian khấu hao làO( nnO(n).O(nn)=O(n)

Lưu ý cuối cùng

Nó có thể để loại bỏ các biến thêm với chi phí làm Pop một hoạt động, bằng cách tổ chức lại Popfirsttại mỗi cuộc gọi thay vì phải đẩy làm tất cả công việc.O(n)first


Tôi đã chỉnh sửa các đoạn đầu tiên để câu trả lời của tôi được coi là một câu trả lời thực sự cho câu hỏi.
Alex ten Brink

6
Bạn đang sử dụng một mảng (đảo ngược) để đảo ngược! Tôi không nghĩ bạn được phép làm điều này.
Kaveh

Đúng, tôi sử dụng thêm không gian trong khi thực hiện các phương thức, nhưng tôi nghĩ rằng điều đó sẽ được cho phép: nếu bạn muốn thực hiện một hàng đợi bằng cách sử dụng hai ngăn xếp theo cách đơn giản, bạn phải đảo ngược một trong các ngăn xếp tại một điểm và theo như Tôi biết bạn cần thêm không gian để làm điều đó, vì vậy câu hỏi này tương tự tôi đã hình dung sử dụng thêm không gian trong khi thực hiện phương thức sẽ được cho phép, miễn là bạn không sử dụng thêm không gian giữa các lệnh gọi phương thức.
Alex ten Brink

6
"Nếu bạn muốn thực hiện một hàng đợi bằng cách sử dụng hai ngăn xếp theo cách đơn giản, bạn phải đảo ngược một trong các ngăn xếp tại một điểm và theo như tôi biết bạn cần thêm không gian để làm điều đó" --- Bạn không. Có một cách để làm cho chi phí khấu hao của Enqueue là 3 và chi phí khấu hao của Dequeue là 1 (tức là cả O (1)) với một ô nhớ và hai ngăn xếp. Phần khó thực sự là bằng chứng, không phải là thiết kế của thuật toán.
Aaron Sterling

Sau khi suy nghĩ về nó nhiều hơn, tôi nhận ra tôi thực sự gian lận và nhận xét trước đây của tôi thực sự sai. Tôi đã tìm ra cách khắc phục nó: Tôi đã nghĩ ra hai thuật toán có cùng thời gian chạy như hai thuật toán trên (mặc dù Push hiện đang hoạt động mất nhiều thời gian và Pop hiện được thực hiện trong thời gian không đổi) mà không cần sử dụng thêm dung lượng. Tôi sẽ đăng một câu trả lời mới khi tôi đã viết tất cả.
Alex ten Brink

12

Sau một số ý kiến về câu trả lời trước đây của tôi, nó trở nên rõ ràng với tôi rằng tôi đã được nhiều hay ít gian lận: Tôi sử dụng thêm không gian ( không gian thừa trong thuật toán thứ hai) trong khi thực hiện phương thức Pop của tôi.O(n)

Thuật toán sau không sử dụng bất kỳ khoảng trắng bổ sung nào giữa các phương thức và chỉ có không gian thừa trong quá trình thực hiện Push và Pop. Đẩy có O ( O(1)thời gian chạy được khấu hao và Pop có thời gian chạyO(1)tồi tệ nhất (và khấu hao).O(n)O(1)

Lưu ý cho người điều hành: Tôi không hoàn toàn chắc chắn liệu quyết định của mình đưa ra câu trả lời riêng biệt này có đúng không. Tôi nghĩ rằng tôi không nên xóa câu trả lời ban đầu của mình vì nó vẫn có thể liên quan đến câu hỏi.

Thuật toán

Chúng tôi có hai hàng đợi: queue và queue s e c o n d . f i r s t sẽ là 'cache' của chúng tôi, trong khi s e c o n d sẽ là 'lưu trữ' chính của chúng tôi. Cả hai hàng đợi sẽ luôn ở trong 'thứ tự ngăn xếp'. f i r s t sẽ chứa các phần tử ở đầu ngăn xếp và s e c o n d sẽ chứa các phần tử ở dưới cùng của ngăn xếp. Kích thước của f i rfirstsecondfirstsecondfirstsecond sẽ luôn luôn là tối đa căn bậc hai của s e c o n d .firstsecond

  • Việc đẩy được thực hiện bằng cách 'chèn' tham số khi bắt đầu hàng đợi như sau: chúng ta liệt kê tham số vào , sau đó dequeue và tái liệt kê tất cả các phần tử khác trong f i r s t . Bằng cách này, tham số kết thúc khi bắt đầu f i r s t .firstfirstfirst
  • Nếu trở nên lớn hơn căn bậc hai của s e c o n d , chúng ta sẽ liệt kê tất cả các phần tử của s e c o n d lên f i r s t từng cái một và sau đó hoán đổi f i r s ts e c o n d . Bằng cách này, các phần tử của f i r s t (đỉnh của ngăn xếp) kết thúc ở đầu s efirstsecondsecondfirstfirstsecondfirst .second
  • Pop được thực hiện bằng cách dequeueing và trả về kết quả nếu f i r s t không trống, và nếu không bằng cách dequeueing s e c o n d và trả về kết quả.firstfirstsecond

Mã C # cho thuật toán đầu tiên

Mã này khá dễ đọc, ngay cả khi bạn chưa bao giờ thấy C # trước đây. Nếu bạn không biết khái quát là gì, chỉ cần thay thế tất cả các trường hợp 'T' bằng 'chuỗi' trong tâm trí của bạn, cho một chuỗi các chuỗi.

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();
    }
}

Phân tích

Rõ ràng Pop hoạt động trong thời gian trong trường hợp xấu nhất.O(1)

Đẩy hoạt động trong O(n)|first|<|second|O(n)|first||second|O(n)firstO(n)O(nn)=O(n)


về việc xóa câu trả lời, vui lòng xem qua meta.cstheory.stackexchange.com/q/386/873 .
MS Dousti

Tôi không thể hiểu được dòng first.Enqueue(first.Dequeue()). Bạn đã nhầm lẫn một cái gì đó?
MS Dousti

Cảm ơn các liên kết, tôi cập nhật câu trả lời ban đầu của tôi cho phù hợp. Thứ hai, tôi đã thêm rất nhiều ý kiến ​​vào mã của tôi mô tả những gì đang diễn ra trong quá trình thực thi thuật toán của tôi, tôi hy vọng nó sẽ xóa đi mọi sự nhầm lẫn.
Alex ten Brink

Đối với tôi, thuật toán dễ đọc hơn và dễ hiểu hơn trước khi chỉnh sửa.
Kaveh

9

Θ(N)

NNNNN

PUSHN(PUSHNPOPN)N

NN/2

N

N/2N/2

N/22N

N/22NNNN/23NΩ(N)


NN

nQ1N/2Q22nn4:1+2++n+n2n

Rõ ràng câu trả lời của Peter mâu thuẫn với giới hạn dưới này?
Joe

PUSHNPOPNO(N)

O(N)

6

O(lgn)pushpoppopO(lgn)pop được yêu cầu và hàng đợi đầu ra trống, bạn thực hiện một chuỗi các xáo trộn hoàn hảo để đảo ngược hàng đợi đầu vào và lưu trữ nó trong hàng đợi đầu ra.

O(1)

Theo tôi biết, đây là một ý tưởng mới ...



Argh! Tôi nên tìm kiếm một câu hỏi cập nhật hoặc liên quan. Các giấy tờ bạn liên kết đến trong câu trả lời trước đó của bạn đặt ra mối quan hệ giữa ngăn xếp k và ngăn xếp k + 1. Liệu thủ thuật này cuối cùng có đặt sức mạnh của k hàng đợi giữa ngăn xếp k và k + 1 không? Nếu vậy, đó là một sidenote gọn gàng. Dù bằng cách nào, cảm ơn vì đã liên kết tôi với câu trả lời của bạn để tôi không lãng phí quá nhiều thời gian để viết bài này cho một địa điểm khác.
Peter Boothe

1

Không sử dụng thêm không gian, có thể sử dụng hàng đợi ưu tiên và buộc mỗi lần đẩy mới để ưu tiên lớn hơn lần trước? Vẫn sẽ không phải là O (1).


0

Tôi không thể có được hàng đợi để thực hiện một ngăn xếp trong thời gian liên tục được khấu hao. Tuy nhiên, tôi có thể nghĩ ra một cách để có được hai hàng đợi để thực hiện một ngăn xếp trong trường hợp tuyến tính tồi tệ nhất.

  • AB
  • Mỗi lần có một thao tác đẩy, lật bit và chèn phần tử vào hàng đợi bit bây giờ phân định ranh giới. Pop mọi thứ từ hàng đợi khác và đẩy nó vào hàng đợi hiện tại.
  • Một hoạt động pop sẽ tắt phía trước hàng đợi hiện tại và không chạm vào bit trạng thái bên ngoài.

Tất nhiên, chúng ta có thể thêm một chút trạng thái bên ngoài cho chúng ta biết hoạt động cuối cùng là đẩy hay bật. Chúng ta có thể trì hoãn việc di chuyển mọi thứ từ hàng này sang hàng khác cho đến khi chúng ta có hai thao tác đẩy liên tiếp. Điều này cũng làm cho hoạt động pop phức tạp hơn một chút. Điều này mang lại cho chúng tôi độ phức tạp khấu hao O (1) cho hoạt động pop. Thật không may đẩy vẫn tuyến tính.

Tất cả điều này hoạt động bởi vì mỗi lần thực hiện thao tác đẩy, phần tử mới được đặt ở đầu hàng đợi trống và hàng đợi đầy đủ được thêm vào đầu đuôi của nó, đảo ngược các phần tử một cách hiệu quả.

Nếu bạn muốn được khấu hao các hoạt động thời gian liên tục, có lẽ bạn sẽ phải làm điều gì đó thông minh hơn.


4
Chắc chắn, tôi có thể sử dụng một hàng đợi với độ phức tạp thời gian trường hợp tệ hơn và không có sự phức tạp, về cơ bản coi hàng đợi là một danh sách tròn với một thành phần hàng đợi bổ sung đại diện cho đỉnh của ngăn xếp.
Dave Clarke

Hình như bạn có thể! Tuy nhiên, có vẻ như nhiều hơn một hàng đợi cổ điển là cần thiết để mô phỏng một ngăn xếp theo cách này.
Ross Snider

0

Có một giải pháp tầm thường, nếu hàng đợi của bạn cho phép tải trước, chỉ yêu cầu một hàng đợi (hoặc cụ thể hơn là deque.) Có lẽ đây là loại hàng đợi mà khóa học TA trong câu hỏi ban đầu có trong đầu?

Không cho phép tải trước, đây là một giải pháp khác:

Thuật toán này yêu cầu hai hàng đợi và hai con trỏ, chúng ta sẽ gọi chúng lần lượt là Q1, Q2, sơ cấp và thứ cấp. Khi bắt đầu Q1 và Q2 trống, điểm chính đến Q1 và điểm phụ đến Q2.

Hoạt động PUSH là tầm thường, nó chỉ bao gồm:

*primary.enqueue(value);

Hoạt động POP có liên quan nhiều hơn một chút; nó yêu cầu spooling tất cả trừ mục cuối cùng của hàng đợi chính vào hàng đợi thứ cấp, hoán đổi các con trỏ và trả về mục cuối cùng còn lại từ hàng đợi ban đầu:

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

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

Không có giới hạn kiểm tra nào được thực hiện và đó không phải là O (1).

Khi tôi gõ cái này, tôi thấy rằng điều này có thể được thực hiện với một hàng đợi bằng cách sử dụng vòng lặp for thay cho vòng lặp while, giống như Alex đã làm. Dù bằng cách nào, hoạt động PUSH là O (1) và hoạt động POP trở thành O (n).


Đây là một giải pháp khác sử dụng hai hàng đợi và một con trỏ, được gọi là Q1, Q2 và queue_p, tương ứng:

Khi khởi tạo, Q1 và Q2 trống và queue_p trỏ đến Q1.

Một lần nữa, hoạt động PUSH là tầm thường, nhưng yêu cầu thêm một bước chỉ trỏ queue_p ở hàng đợi khác:

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

Hoạt động hoạt động POP tương tự như trước đây, nhưng bây giờ có n / 2 mục cần được xoay qua hàng đợi:

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());

Hoạt động PUSH vẫn là O (1), nhưng bây giờ hoạt động POP là O (n / 2).

Cá nhân, đối với vấn đề này, tôi thích ý tưởng thực hiện một hàng đợi hai đầu (deque) và gọi nó là một ngăn xếp khi chúng ta muốn.


Thuật toán thứ hai của bạn rất hữu ích để hiểu thêm về Alex.
hengxin

0

kΘ(n1/k)

k
n
O(1)

iΘ(viết sai rồiTôi/k)Θ(viết sai rồiTôi/k)Ôi(1)Tôi+1Ôi(viết sai rồi1/k)Tôi-1Θ(viết sai rồi1/k) (hoặc nếu chúng tôi chỉ có một chồng và cho phép thời gian pop khấu hao).

mmΩ(mviết sai rồi1/k)o(viết sai rồi1/k)Ω(viết sai rồi1/k)o(viết sai rồi2/k)ko(viết sai rồi)

Θ(đăng nhậpviết sai rồi)


-3

Một ngăn xếp có thể được thực hiện bằng cách sử dụng hai hàng đợi bằng cách sử dụng hàng đợi thứ hai là ab uffer. Khi các mục được đẩy lên ngăn xếp, chúng được thêm vào cuối hàng đợi. Mỗi khi một mục được bật lên, n - 1 phần tử của hàng đầu tiên phải được chuyển sang phần thứ hai, trong khi mục còn lại được trả về. lớp công khai QueueStack triển khai ts IStack {private IQueue q1 = new Queue (); IQueue riêng q2 = Hàng đợi mới (); công khai void đẩy (E e) {q1.enqueue (e) // O (1)} công khai E pop (E e) {while (1 <q1.size ()) // O (n) {q2.enqueue ( q1.dequeue ()); } sw apQueues (); trả về q2.dequeue (); } p đối thủ void exchangeQueues () {IQueue Q = q2; q2 = q1; q1 = Q; }}


2
Bạn có bỏ lỡ phần nào trong câu hỏi về thời gian khấu hao O (1) không?
David Eppstein
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.