Как реализовать три стека с использованием одного массива
Я столкнулся с этой проблемой на веб-сайте интервью. Задача требует эффективного использования трех стеков в одном массиве, так что переполнение стека до тех пор, пока не останется свободного места во всем пространстве массива.
Для реализации 2 стеков в массиве это довольно очевидно: 1-й стек растет от LEFT до RIGHT, а второй стек растет от RIGHT до LEFT; и когда stackTopIndex пересекает, он сигнализирует о переполнении.
Заранее благодарим за ваш проницательный ответ.
Ответы
Ответ 1
Вы можете реализовать три стека с связанный список:
- Вам нужен указатель, указывающий на следующий свободный элемент. Еще три указателя возвращают последний элемент каждого стека (или null, если стек пуст).
- Когда стек получает еще один добавленный элемент, он должен использовать первый свободный элемент и устанавливать свободный указатель на следующий свободный элемент (или будет возникать ошибка переполнения). Его собственный указатель должен указывать на новый элемент, оттуда обратно к следующему элементу в стеке.
- Когда стек удаляет элемент, он вернет его обратно в список свободных элементов. Собственный указатель стека будет перенаправлен на следующий элемент в стеке.
Связанный список может быть реализован внутри массива.
Каким (пространственным) эффектом является это?
Нет ничего сложного в создании связанного списка, используя две ячейки массива для каждого элемента списка (значение + указатель). В зависимости от спецификации вы можете даже получить указатель и значение в один элемент массива (например, массив длинный, значение и указатель - только int).
Сравните это с решением kgiannakakis... где вы теряете до 50% (только в худшем случае). Но я думаю, что мое решение немного более чистое (и, возможно, более академичное, что не должно быть недостатком для вопроса интервью ^^).
Ответ 2
См. Кнут, искусство программирования, том 1, раздел 2.2.2. под названием "Последовательное распределение". Обсуждает выделение нескольких очередей/стеков в одном массиве с алгоритмами, связанными с переполнениями и т.д.
Ответ 3
Мы можем использовать длинный бит-массив, представляющий, к какому стек принадлежит ячейка i-го массива.
Мы можем принимать значения по модулю 3 (00 - пусто, 01 - A, 10 - B, 11 - C). Для N-массива потребуется N/2 бит или N/4 байта дополнительной памяти.
Например, для 1024 длинных элементов int (4096 байт) потребуется всего 256 байт или 6%.
Эта карта бит-массива может быть помещена в тот же массив в начале или в конце, просто уменьшая размер данного массива на константу 6%!
Ответ 4
Первый стек растет слева направо.
Второй стек растет справа налево.
Третий стек начинается с середины. Предположим, что для простоты используется массив нечетного размера. Затем третий стек растет следующим образом:
* * * * * * * * * * *
5 3 1 2 4
Первый и второй стеки могут увеличиваться максимум на половине размера массива. Третий стек может вырасти, чтобы заполнить весь массив максимум.
В худшем случае, когда один из первых двух массивов растет на 50% от массива. Тогда есть 50% отходов массива. Чтобы оптимизировать эффективность, нужно выбрать третий массив, который будет быстрее, чем два других.
Ответ 5
Это интересная головоломка, и у меня нет реального ответа, но я думаю немного вне коробки...
это может зависеть от того, из чего состоит каждый элемент в стеке. Если это три стека истинных/ложных флагов, вы можете использовать первые три бита целочисленных элементов. То есть. бит 0 - это значение для первого стека, бит 1 - это значение для второго стека, бит 2 - значение для третьего стека. Затем каждый стек может расти независимо, пока весь массив не будет заполнен для этого стека. Это еще лучше, так как другие стеки также могут продолжать расти даже тогда, когда первый стек заполнен.
Я знаю, что это немного обманывает и на самом деле не отвечает на вопрос, но он работает в очень конкретном случае, и никакие записи в стеке не используются. Я с интересом наблюдаю за тем, может ли кто-нибудь придумать правильный ответ, который работает для более общих элементов.
Ответ 6
Разделить массив в любых трех частях (независимо от того, разделите ли вы его последовательно или чередуйте). Если один стек растет больше 1/3 массива, вы начинаете заполнять концы покоя двумя стеками с конца.
aaa bbb ccc
1 2 3
145 2 3
145 2 6 3
145 2 6 3 7
145 286 3 7
145 286 397
Хуже случай, когда два стека растут до 1/3 границы, а затем у вас есть 30% космических отходов.
Ответ 7
Предполагая, что все позиции массива должны использоваться для хранения значений - я думаю, это зависит от вашего определения эффективности.
Если вы выполняете два решения стека, поместите третий стек где-нибудь посередине и проследите как его нижнюю, так и верхнюю часть, тогда большинство операций будут продолжать действовать эффективно, в случае штрафа за дорогостоящую операцию перемещения (третьего стека где бы ни находилось свободное пространство, перемещаясь до полуострова свободного пространства) всякий раз, когда происходит столкновение.
Это, безусловно, будет быстро закодировать и понять. Каковы наши целевые показатели эффективности?
Ответ 8
Довольно глупым, но эффективным решением может быть:
- Сохраните первые элементы стека в позиции
i*3
: 0,3,6,...
- Сохраняйте элементы второго стека в позиции
i*3+1
: 1,4,7...
- И третий элемент стека в позициях
i*3+2
.
Проблема с этим решением заключается в том, что используемая память всегда будет в три раза больше размера самого глубокого стека и что вы можете переполняться, даже если в массиве есть доступные позиции.
Ответ 9
-
Сделайте HashMap с ключами в начальном и конечном положениях, например. < "B1", 0 > , < "E1", n/3 >
-
для каждого Push (value) добавить условие, чтобы проверить, является ли положение Bx предшествующим Ex или есть другое "By" между ними. - позволяет называть это условие (2)
-
с учетом вышеуказанного состояния,
если выше (2) истинно//если B1 и E1 в порядке {if (S1.Push()), затем E1 ++; else//условие переполнения, {начать толкать в конце E2 или E3 (в зависимости от того, что имеет место) и обновить E1 до E2-- или E3--; } }
если выше (2) неверно {if (S1.Push()), то E1 -; else//условие переполнения, {начать толкать в конце E2 или E3 (в зависимости от того, что имеет место) и обновить E1 до E2-- или E3--; } }
Ответ 10
Предположим, что у вас есть только целочисленный индекс. если он обрабатывался с использованием FILO (First In Last Out) и не ссылался на человека, и только с использованием массива в качестве данных. Использование этого 6-го места в качестве ссылки на стопку должно помочь:
[ head-1, last-1, head-2, last-2, head-3, last-3, данные, данные,..., данные]
вы можете просто использовать 4 пробела, потому что head-1 = 0 и last-3 = длина массива. Если вы используете FIFO (First In First Out), вам необходимо переиндексировать.
nb: Im работает над улучшением моего английского языка.
Ответ 11
первый стек растет на 3n,
второй стек растет при 3n + 1,
третья растет при 3n + 2
для n = {0... N}
Ответ 12
Еще один подход (как дополнительный для связанного списка) заключается в использовании карты стеков. В этом случае вам понадобится использовать дополнительные биты log (3 ^ n)/log (2) для построения карты распределения данных в вашем массиве n-длины. Каждая из 3-значных частей карты говорит, какой стек принадлежит следующему элементу.
Ex. a.push(1); b.push(2); c.push(3); a.push(4); a.push(5);
предоставит вам изображение
aacba
54321
вычисляется соответствующее значение карты, а элементы помещаются в стек (со смещением содержимого массива)
map0 = any
map1 = map0*3 + 0
map2 = map1*3 + 1
map3 = map2*3 + 2
map4 = map3*3 + 0
map5 = map4*3 + 0 = any*3^5 + 45
и длина стеков 3,1,1
После того, как вы захотите сделать c.pop()
, вам придется реорганизовать свои элементы, найдя фактическую позицию c.top()
в исходном массиве, пройдя по карте ячеек (т.е. Разделите на 3, а mod на 3 - не на 2) и затем переместите все содержимое в массив назад, чтобы закрыть это отверстие. Прогуливаясь по карте ячеек, вам нужно будет сохранить все пройденное вами положение (mapX
), и после прохождения того, который указывает на стек "c", вам придется разделить на 3 еще раз и умножить его на 3 ^ (количество позиций передано-1) и добавьте mapX
, чтобы получить новое значение map-map.
Накладные расходы для этого фиксированного и зависят от размера элемента стека (bits_per_value
):
(log n)/(nlog (bits_per_value)) = log (3)/log (3 n)/log (2))/(nlog (bits_per_value)/log (2)) = журнал (bits_per_value)
Таким образом, для bits_per_value = 32
это будет 31,7% пространства над головой и с ростом bits_per_value
он будет распадаться (то есть для 64 бит это будет 26,4%).
Ответ 13
В этом подходе любой стек может расти до тех пор, пока в массиве есть свободное пространство.
Мы последовательно выделяем пространство стекам и связываем новые блоки с предыдущим блоком. Это означает, что любой новый элемент в стеке хранит указатель на предыдущий верхний элемент этого конкретного стека.
int stackSize = 300;
int indexUsed = 0;
int[] stackPointer = {-1,-1,-1};
StackNode[] buffer = new StackNode[stackSize * 3];
void push(int stackNum, int value) {
int lastIndex = stackPointer[stackNum];
stackPointer[stackNum] = indexUsed;
indexUsed++;
buffer[stackPointer[stackNum]]=new StackNode(lastIndex,value);
}
int pop(int stackNum) {
int value = buffer[stackPointer[stackNum]].value;
int lastIndex = stackPointer[stackNum];
stackPointer[stackNum] = buffer[stackPointer[stackNum]].previous;
buffer[lastIndex] = null;
indexUsed--;
return value;
}
int peek(int stack) { return buffer[stackPointer[stack]].value; }
boolean isEmpty(int stackNum) { return stackPointer[stackNum] == -1; }
class StackNode {
public int previous;
public int value;
public StackNode(int p, int v){
value = v;
previous = p;
}
}
Ответ 14
Этот код реализует 3 стека в одном массиве. Он заботится о пустых пространствах и заполняет пробелы между данными.
#include < stdio.h >
struct stacknode {
int value;
int prev;
};
struct stacknode stacklist [50];
int top [3] = {-1, -1, -1};
int freelist [50];
int stackindex = 0;
int freeindex = -1;
void push (int stackno, int value) {
int index;
if (freeindex >= 0) {
index = freelist [freeindex];
freeindex--;
} else {
index = stackindex;
stackindex ++;
}
stacklist [index].value = значение;
if (top [stackno-1]!= -1) {
stacklist [index].prev = top [stackno-1];
} else {
stacklist [index].prev = -1;
}
top [stackno-1] = index;
printf (" % d вставляется в стек% d at% d\n ", значение, stackno, index);
}
int pop (int stackno) {
int index, value;
if (top [stackno-1] == -1) {
printf (" Нет элементов в стеке% d\n ", value, stackno);
return -1;
}
index = top [stackno-1];
freeindex ++;
freelist [freeindex] = index;
value = stacklist [index].value;
top [stackno-1] = stacklist [index].prev;
printf (" % d выставляется из стека% d at% d\n ", value, stackno, index);
возвращаемое значение;
}
int main() {
толчок (1,1);
толчок (1,2);
толчок (3,3);
толчок (2,4);
поп (3);
поп (3);
толчок (3,3);
толчок (2,3);
}
Ответ 15
Другое решение в PYTHON, пожалуйста, дайте мне знать, работает ли это так, как вы думаете.
class Stack(object):
def __init__(self):
self.stack = list()
self.first_length = 0
self.second_length = 0
self.third_length = 0
self.first_pointer = 0
self.second_pointer = 1
def push(self, stack_num, item):
if stack_num == 1:
self.first_pointer += 1
self.second_pointer += 1
self.first_length += 1
self.stack.insert(0, item)
elif stack_num == 2:
self.second_length += 1
self.second_pointer += 1
self.stack.insert(self.first_pointer, item)
elif stack_num == 3:
self.third_length += 1
self.stack.insert(self.second_pointer - 1, item)
else:
raise Exception('Push failed, stack number %d is not allowd' % stack_num)
def pop(self, stack_num):
if stack_num == 1:
if self.first_length == 0:
raise Exception('No more element in first stack')
self.first_pointer -= 1
self.first_length -= 1
self.second_pointer -= 1
return self.stack.pop(0)
elif stack_num == 2:
if self.second_length == 0:
raise Exception('No more element in second stack')
self.second_length -= 1
self.second_pointer -= 1
return self.stack.pop(self.first_pointer)
elif stack_num == 3:
if self.third_length == 0:
raise Exception('No more element in third stack')
self.third_length -= 1
return self.stack.pop(self.second_pointer - 1)
def peek(self, stack_num):
if stack_num == 1:
return self.stack[0]
elif stack_num == 2:
return self.stack[self.first_pointer]
elif stack_num == 3:
return self.stack[self.second_pointer]
else:
raise Exception('Peek failed, stack number %d is not allowd' % stack_num)
def size(self):
return len(self.items)
s = Stack()
# push item into stack 1
s.push(1, '1st_stack_1')
s.push(1, '2nd_stack_1')
s.push(1, '3rd_stack_1')
#
## push item into stack 2
s.push(2, 'first_stack_2')
s.push(2, 'second_stack_2')
s.push(2, 'third_stack_2')
#
## push item into stack 3
s.push(3, 'FIRST_stack_3')
s.push(3, 'SECOND_stack_3')
s.push(3, 'THIRD_stack_3')
#
print 'Before pop out: '
for i, elm in enumerate(s.stack):
print '\t\t%d)' % i, elm
#
s.pop(1)
s.pop(1)
#s.pop(1)
s.pop(2)
s.pop(2)
#s.pop(2)
#s.pop(3)
s.pop(3)
s.pop(3)
#s.pop(3)
#
print 'After pop out: '
#
for i, elm in enumerate(s.stack):
print '\t\t%d)' % i, elm
Ответ 16
Может быть, это может немного помочь... Я написал это сам
:)
// by ashakiran bhatter
// compile: g++ -std=c++11 test.cpp
// run : ./a.out
// sample output as below
// adding: 1 2 3 4 5 6 7 8 9
// array contents: 9 8 7 6 5 4 3 2 1
// popping now...
// array contents: 8 7 6 5 4 3 2 1
#include <iostream>
#include <cstdint>
#define MAX_LEN 9
#define LOWER 0
#define UPPER 1
#define FULL -1
#define NOT_SET -1
class CStack
{
private:
int8_t array[MAX_LEN];
int8_t stack1_range[2];
int8_t stack2_range[2];
int8_t stack3_range[2];
int8_t stack1_size;
int8_t stack2_size;
int8_t stack3_size;
int8_t stack1_cursize;
int8_t stack2_cursize;
int8_t stack3_cursize;
int8_t stack1_curpos;
int8_t stack2_curpos;
int8_t stack3_curpos;
public:
CStack();
~CStack();
void push(int8_t data);
void pop();
void print();
};
CStack::CStack()
{
stack1_range[LOWER] = 0;
stack1_range[UPPER] = MAX_LEN/3 - 1;
stack2_range[LOWER] = MAX_LEN/3;
stack2_range[UPPER] = (2 * (MAX_LEN/3)) - 1;
stack3_range[LOWER] = 2 * (MAX_LEN/3);
stack3_range[UPPER] = MAX_LEN - 1;
stack1_size = stack1_range[UPPER] - stack1_range[LOWER];
stack2_size = stack2_range[UPPER] - stack2_range[LOWER];
stack3_size = stack3_range[UPPER] - stack3_range[LOWER];
stack1_cursize = stack1_size;
stack2_cursize = stack2_size;
stack3_cursize = stack3_size;
stack1_curpos = stack1_cursize;
stack2_curpos = stack2_cursize;
stack3_curpos = stack3_cursize;
}
CStack::~CStack()
{
}
void CStack::push(int8_t data)
{
if(stack3_cursize != FULL)
{
array[stack3_range[LOWER] + stack3_curpos--] = data;
stack3_cursize--;
} else if(stack2_cursize != FULL) {
array[stack2_range[LOWER] + stack2_curpos--] = data;
stack2_cursize--;
} else if(stack1_cursize != FULL) {
array[stack1_range[LOWER] + stack1_curpos--] = data;
stack1_cursize--;
} else {
std::cout<<"\tstack is full...!"<<std::endl;
}
}
void CStack::pop()
{
std::cout<<"popping now..."<<std::endl;
if(stack1_cursize < stack1_size)
{
array[stack1_range[LOWER] + ++stack1_curpos] = 0;
stack1_cursize++;
} else if(stack2_cursize < stack2_size) {
array[stack2_range[LOWER] + ++stack2_curpos] = 0;
stack2_cursize++;
} else if(stack3_cursize < stack3_size) {
array[stack3_range[LOWER] + ++stack3_curpos] = 0;
stack3_cursize++;
} else {
std::cout<<"\tstack is empty...!"<<std::endl;
}
}
void CStack::print()
{
std::cout<<"array contents: ";
for(int8_t i = stack1_range[LOWER] + stack1_curpos + 1; i <= stack1_range[UPPER]; i++)
std::cout<<" "<<static_cast<int>(array[i]);
for(int8_t i = stack2_range[LOWER] + stack2_curpos + 1; i <= stack2_range[UPPER]; i++)
std::cout<<" "<<static_cast<int>(array[i]);
for(int8_t i = stack3_range[LOWER] + stack3_curpos + 1; i <= stack3_range[UPPER]; i++)
std::cout<<" "<<static_cast<int>(array[i]);
std::cout<<"\n";
}
int main()
{
CStack stack;
std::cout<<"adding: ";
for(uint8_t i = 1; i < 10; i++)
{
std::cout<<" "<<static_cast<int>(i);
stack.push(i);
}
std::cout<<"\n";
stack.print();
stack.pop();
stack.print();
return 0;
}