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 t và s đ 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 t và s 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 đổifirstvàsđ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ỗi √n−−√ 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( nn−−√O(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