Задачи на реализацию стеков с очередями
Как известно, слишком высокая стопка тарелок может развалиться. Следовательно, в реальной жизни, когда высота стопки превысила бы некоторое пороговое значение, мы начали бы складывать тарелки в новую стопку. Реализуйте структуру данных SetofStacks, имитирующую реальную ситуацию.
15К открытий15К показов
Для начала немного теории:
Стек — это коллекция, элементы которой получают по принципу «последний вошел, первый вышел» (Last-In-First-Out или LIFO). Это значит, что мы будем иметь доступ только к последнему добавленному элементу.
Очереди очень похожи на стеки. Они так же не дают доступа к произвольному элементу, но, в отличие от стека, элементы кладутся (enqueue) и забираются (dequeue) с разных концов. Такой метод называется «первый вошел, первый вышел» (First-In-First-Out или FIFO). То есть забирать элементы из очереди мы будем в том же порядке, в котором и клали. Как реальная очередь или конвейер.
Подробнее об этих структурах данных вы можете прочесть в нашей статье.
Условие задачи
Как известно, слишком высокая стопка тарелок может развалиться. Следовательно, в реальной жизни, когда высота стопки превысила бы некоторое пороговое значение, мы начали бы складывать тарелки в новую стопку. Реализуйте структуру данных SetofStacks
, имитирующую реальную ситуацию. Структура SetofStacks
должна состоять из нескольких стеков, новый стек создается, как только предыдущий достигнет порогового значения. Методы SetofStacks.push()
и SetofStacks.pop()
должны вести себя так же, как при работе с одним стеком (то есть метод pop()
должен возвращать те же значения, которые бы он возвращал при использовании одного большого стека). Реализуйте функцию popAt(int index)
, которая осуществляет операцию pop c заданным внутренним стеком.
Дополнительная задача:
Напишите класс MyQueue, который реализует очередь с использованием двух стеков.
Решение
В соответствии с формулировкой задачи структура данных будет иметь следующий
вид:
Известно, что push()
должен работать так же, как для одиночного стека; это означает, что его реализация должна вызывать push()
для последнего стека из массива стеков. При этом нужно действовать аккуратно: если последний стек заполнен, нужно создать новый стек. Код будет выглядеть примерно так:
Что должен делать метод рор()
? Он, как и push()
, должен работать с последним стеком. Если последний стек после удаления элемента методом рор()
оказывается пустым, его нужно удалить из списка стеков:
Реализация popAt(int index)
Извлечение из внутреннего стека реализуется несколько сложнее. Представьте себе систему с «переносом»: если требуется извлечь элемент из стека 1, то из стека 2 нужно удалить НИЖНИЙ элемент и занести его в стек 1. Затем аналогичная операция должна быть проделана со стеками 3 и 2, 4 и 3 и т. д. На это можно возразить, что вместо «переноса» можно смириться с тем, что некоторые стеки заполнены не на всю емкость. Этот компромисс улучшит временную сложность (притом заметно, при большом количестве элементов), но может создать проблемы потом, если пользователь предположит, что все стеки (кроме последнего) работают на полной емкости.
Концептуально задача не сложна, но ее полная реализация потребует написания довольно объемного кода. Хорошая стратегия в подобных задачах – разделение кода на методы. Например, в нашем коде присутствует метод leftShift
, который вызывается в методе popAt
. Этот прием сделает код более прозрачным и понятным.
Решение дополнительной задачи
С учетом главного различия между очередью и стеком (FIFO против LIFO) нужно изменить методы peek()
и рор()
так, чтобы они работали в обратном порядке. Для обращения порядка элементов можно воспользоваться вторым стеком (выталкиваем sl и помещаем все элементы в s2). В такой реализации каждая операция peek()
или рор()
приводит к извлечению всех элементов из sl в s2, после чего выполняется операция peek/pop
, а затем все возвращается обратно (с помощью push).
Этот алгоритм будет работать, но при последовательном выполнении операций pop/peek будут происходить лишние перемещения элементов. Можно реализовать «отложенный» подход, при котором элементы хранятся в s2 до того момента, пока
нам не понадобится их инвертировать.
В этом случае в stackNewest
помещаются самые новые элементы (на вершину), а в stackOldest
– самые старые элементы (тоже на вершину). Когда мы исключаем элемент из очереди, необходимо сначала удалить самый старый элемент, то есть вывести его из stackOldest
. Если stackOldest
пуст, то следует передать в этот стек все элементы из stackNewest
в обратном порядке. При вставке элементы заносятся
в stackNewest
, так как новые элементы всегда находятся на вершине.
Приведенный ниже код реализует данный алгоритм:
Если вы решили эту задачу, подумайте над тем, как можно использовать один одномерный массив для реализации трех стеков. Решение разобрано на нашем сайте.
15К открытий15К показов