Стек, также известный как стек, представляет собой линейный список с ограниченными операциями. Его пределОперации вставки и удаления разрешены только на одной стороне таблицы..这一端被称为栈顶,相对地,把另一端称为栈底。
1. Реализуйте стек класса Stack
Исходя из характеристик стека, массив можно использовать как линейную таблицу для хранения.
инициализацияStack
Структура класса следующая:
function Stack(){
this.space = [];
}
Stack.prototype = {
constructor: Stack,
/* 接口code */
};
Далее на прототипе да入栈
,出栈
,清空栈
,读取栈顶
,读取整个栈数据
реализации этих интерфейсов.Stack
По умолчанию класс использует начало массива в качестве нижней части стека, а хвост — в качестве вершины стека.
1.1 Протолкнуть стекpush
Вставка в стек может использовать массив jspush
метод для отправки данных в конец массива.
Stack.prototype = {
push: function(value){
return this.space.push(value);
}
}
1.2 Вскрыть стекpop
Извлечение стека также использует массивы jspop
метод для отправки данных в конец массива.
Stack.prototype = {
pop: function(){
return this.space.pop();
}
}
1.3 Очистить стекclear
Очистить стек относительно просто, просто сбросьте массив, хранящий данные, в пустой массив.
Stack.prototype = {
clear: function(){
this.space = [];
}
}
1.4 Чтение вершины стекаreadTop
Прочитайте данные в верхней части стека и получите их, подписав массив. Одним из преимуществ является то, что когда нижний индекс превышает допустимый диапазон массива, возвращаемое значение равноundefined
.
Stack.prototype = {
readTop: function(){
return this.space[this.space.length - 1];
}
}
1.4 Чтение всего стекаread
Прочитайте все данные стека и верните текущий массив напрямую.
Stack.prototype = {
read: function(){
return this.space;
}
}
1.5 Агрегация
Наконец, после агрегирования всех функций, как показано ниже, создается структура данных стека.
function Stack(){
this.space = [];
}
Stack.prototype = {
constructor: Stack,
push: function(value){
return this.space.push(value);
},
pop: function(){
return this.space.pop();
},
clear: function(){
this.space = [];
},
readTop: function(){
return this.space[this.space.length - 1];
},
read: function(){
return this.space;
}
};
2. Настоящий бой
Цель изучения структур данных и алгоритмов состоит в том, чтобы лучше и эффективнее решать инженерные задачи. Здесь мы применяем то, что узнали, и приводим несколько реальных кейсов, чтобы испытать прелесть структур данных и алгоритмов :)
2.1 Массивыreverse
реализация
В данном случае стек будет использоваться для реализации обратной функции массива.
function reverse(arr){
var ArrStack = new Stack();
for(var i = arr.length - 1; i >= 0; i--){
ArrStack.push(arr[i]);
}
return ArrStack.read();
}
Как показано в коде, его можно разделить на следующие шаги:
- Создание стека для хранения данных
- Пройдите входящий массив в обратном порядке и поместите его в стек один за другим.
- последнее использование
read
интерфейс, выходные данные
Вроде бы все очень просто, не волнуйтесь, сложность будет позже :)
2.2 Преобразование десятичного числа в двоичное
Задача перевода чисел в основания — это небольшой тест для стеков.
Прежде чем объяснять метод преобразования, давайте рассмотрим небольшой пример:
Преобразование десятичного числа 13 в двоичное
2 | 13 1
 ̄ ̄ ̄
2 | 6 0
 ̄ ̄ ̄
2 | 3 1
 ̄ ̄ ̄ ̄
1 1
Как показано выше: двоичный код числа 131101
.
Преобразование ручного преобразования в хранилище стека, просто преобразованиеостаток 2Результаты последовательно помещаются в стек для сохранения, и, наконец, вывод может быть реверсирован.
function binary(number){
var tmp = number;
var ArrStack = new Stack();
if(number === 0){
return 0;
}
while(tmp){
ArrStack.push(tmp % 2);
tmp = parseInt(tmp / 2, 10);
}
return reverse(ArrStack.read()).join('');
}
binary(14); // 输出=> "1110"
binary(1024); // 输出=> "10000000000"
2.3 Оценка выражения
Этот случай на самом деле можно понимать как упрощенную версиюeval
метод.
Дело верно1+7*(4-2)
оценка .
Перед входом в тему необходимо разобраться со следующими математическими теориями:
- инфиксная нотация(или инфиксная нотация) — это общий способ выражения арифметических или логических формул, в которых операторы находятся в середине своих операндов в инфиксной форме (например: 3 + 4).
- Обратная польская запись(обратная польская нотация, RPN или обратная польская нотация) — это метод математического выражения, введенный польским математиком Яном Вукасевичем в 1920 г. В обратной польской нотации все операторы помещаются в операнды позади, и поэтому также называетсяпостфиксная нотация. Обратная польская нотация не требует круглых скобок для определения приоритета оператора. «3 - 4 + 5» в обычной инфиксной записи записывается «3 4 - 5 +» в обратной польской записи.
- Алгоритм планирования поля(Алгоритм сортировочной станции) — это классический алгоритм преобразования инфиксных выражений в постфиксные выражения, представленный Эзге Дейкстрой и названный в честь его работы, аналогичной сортировочной станции поездов.
Чтобы было ясно заранее, это просто упрощенная реализация. Итак, есть два правила:
- Числа должны быть целыми
- Не допускайте лишних пробелов в выражениях
Код реализации выглядит следующим образом:
function calculate(exp){
var valueStack = new Stack(); // 数值栈
var operatorStack = new Stack(); // 操作符栈
var expArr = exp.split(''); // 切割字符串表达式
var FIRST_OPERATOR = ['+', '-']; // 加减运算符
var SECOND_OPERATOR = ['*', '/']; // 乘除运算符
var SPECIAL_OPERATOR = ['(', ')']; // 括号
var tmp; // 临时存储当前处理的字符
var tmpOperator; // 临时存储当前的运算符
// 遍历表达式
for(var i = 0, len = expArr.length; i < len; i++){
tmp = expArr[i];
switch(tmp){
case '(':
operatorStack.push(tmp);
break;
case ')':
// 遇到右括号,先出栈括号内数据
while( (tmpOperator = operatorStack.pop()) !== '(' &&
typeof tmpOperator !== 'undefined' ){
valueStack.push(calculator(tmpOperator, valueStack.pop(), valueStack.pop()));
}
break;
case '+':
case '-':
while( typeof operatorStack.readTop() !== 'undefined' &&
SPECIAL_OPERATOR.indexOf(operatorStack.readTop()) === -1 &&
(SECOND_OPERATOR.indexOf(operatorStack.readTop()) !== -1 || tmp != operatorStack.readTop()) ){
// 栈顶为乘除或相同优先级运算,先出栈
valueStack.push(calculator(operatorStack.pop(), valueStack.pop(), valueStack.pop()));
}
operatorStack.push(tmp);
break;
case '*':
case '/':
while( typeof operatorStack.readTop() != 'undefined' &&
FIRST_OPERATOR.indexOf(operatorStack.readTop()) === -1 &&
SPECIAL_OPERATOR.indexOf(operatorStack.readTop()) === -1 &&
tmp != operatorStack.readTop()){
// 栈顶为相同优先级运算,先出栈
valueStack.push(calculator(operatorStack.pop(), valueStack.pop(), valueStack.pop()));
}
operatorStack.push(tmp);
break;
default:
valueStack.push(tmp);
}
}
// 处理栈内数据
while( typeof (tmpOperator = operatorStack.pop()) !== 'undefined' ){
valueStack.push(calculator(tmpOperator, valueStack.pop(), valueStack.pop()));
}
return valueStack.pop(); // 将计算结果推出
/*
@param operator 操作符
@param initiativeNum 主动值
@param passivityNum 被动值
*/
function calculator(operator, passivityNum, initiativeNum){
var result = 0;
initiativeNum = typeof initiativeNum === 'undefined' ? 0 : parseInt(initiativeNum, 10);
passivityNum = typeof passivityNum === 'undefined' ? 0 : parseInt(passivityNum, 10);
switch(operator){
case '+':
result = initiativeNum + passivityNum;
console.log(`${initiativeNum} + ${passivityNum} = ${result}`);
break;
case '-':
result = initiativeNum - passivityNum;
console.log(`${initiativeNum} - ${passivityNum} = ${result}`);
break;
case '*':
result = initiativeNum * passivityNum;
console.log(`${initiativeNum} * ${passivityNum} = ${result}`);
break;
case '/':
result = initiativeNum / passivityNum;
console.log(`${initiativeNum} / ${passivityNum} = ${result}`);
break;
default:;
}
return result;
}
}
Идеи реализации:
- использовать
调度场算法
, прочитать инфиксное выражение и выполнить разумную операцию над результатом. - принятие критической точки
operatorStack.readTop() !== 'undefined'
вынести приговор. В некоторых книгах используется#
Выполнение конечного знака немного громоздко лично. - использовать строковые выражения
split
Сделайте разделение, затем выполните обходное чтение, поместите в стек. Если есть результат, который нужно вычислить заранее, выполняется соответствующая всплывающая обработка. - Инкапсулировать метод вычисления частичных результатов в отдельный метод
calculator
. Так как числа до и после операторов умножения и деления различны в работе, их позиции не могут быть изменены по желанию.
2.4 Преобразование инфиксных выражений в постфиксные выражения (обратная польская нотация)
Обратная польская нотация, удобная для компьютера нотация, не требующая скобок.
Следующий случай является модификацией предыдущего случая и также использует调度场算法
, который преобразует инфиксное выражение в постфиксное выражение.
function rpn(exp){
var valueStack = new Stack(); // 数值栈
var operatorStack = new Stack(); // 操作符栈
var expArr = exp.split('');
var FIRST_OPERATOR = ['+', '-'];
var SECOND_OPERATOR = ['*', '/'];
var SPECIAL_OPERATOR = ['(', ')'];
var tmp;
var tmpOperator;
for(var i = 0, len = expArr.length; i < len; i++){
tmp = expArr[i];
switch(tmp){
case '(':
operatorStack.push(tmp);
break;
case ')':
// 遇到右括号,先出栈括号内数据
while( (tmpOperator = operatorStack.pop()) !== '(' &&
typeof tmpOperator !== 'undefined' ){
valueStack.push(translate(tmpOperator, valueStack.pop(), valueStack.pop()));
}
break;
case '+':
case '-':
while( typeof operatorStack.readTop() !== 'undefined' &&
SPECIAL_OPERATOR.indexOf(operatorStack.readTop()) === -1 &&
(SECOND_OPERATOR.indexOf(operatorStack.readTop()) !== -1 || tmp != operatorStack.readTop()) ){
// 栈顶为乘除或相同优先级运算,先出栈
valueStack.push(translate(operatorStack.pop(), valueStack.pop(), valueStack.pop()));
}
operatorStack.push(tmp);
break;
case '*':
case '/':
while( typeof operatorStack.readTop() != 'undefined' &&
FIRST_OPERATOR.indexOf(operatorStack.readTop()) === -1 &&
SPECIAL_OPERATOR.indexOf(operatorStack.readTop()) === -1 &&
tmp != operatorStack.readTop()){
// 栈顶为相同优先级运算,先出栈
valueStack.push(translate(operatorStack.pop(), valueStack.pop(), valueStack.pop()));
}
operatorStack.push(tmp);
break;
default:
valueStack.push(tmp);
}
}
while( typeof (tmpOperator = operatorStack.pop()) !== 'undefined' ){
valueStack.push(translate(tmpOperator, valueStack.pop(), valueStack.pop()));
}
return valueStack.pop(); // 将计算结果推出
/*
@param operator 操作符
@param initiativeNum 主动值
@param passivityNum 被动值
*/
function translate(operator, passivityNum, initiativeNum){
var result = '';
switch(operator){
case '+':
result = `${initiativeNum} ${passivityNum} +`;
console.log(`${initiativeNum} + ${passivityNum} = ${result}`);
break;
case '-':
result = `${initiativeNum} ${passivityNum} -`;
console.log(`${initiativeNum} - ${passivityNum} = ${result}`);
break;
case '*':
result = `${initiativeNum} ${passivityNum} *`;
console.log(`${initiativeNum} * ${passivityNum} = ${result}`);
break;
case '/':
result = `${initiativeNum} ${passivityNum} /`;
console.log(`${initiativeNum} / ${passivityNum} = ${result}`);
break;
default:;
}
return result;
}
}
rpn('1+7*(4-2)'); // 输出=> "1 7 4 2 - * +"
2.5 Ханойская башня
Ханойская башня (Гонконг и Тайвань: Ханойская башня) — математическая задача, сформированная согласно легенде:
Есть три полюса А, В, С. На стержне А имеется N (N>1) перфорированных дисков, и размер дисков уменьшается снизу вверх. Все диски необходимо перемещать на С-образную планку по следующим правилам:
- Одновременно можно перемещать только один диск;
- Большие тарелки нельзя ставить поверх маленьких тарелок.
Классический алгоритм применения стека, первая рекомендация汉诺塔
.
Для понимания алгоритма обратите внимание на следующие моменты:
- Не вникайте в каждое движение, понимайте его абстрактно
- Шаг 1. Все диски, не соответствующие требованиям, равномерно перемещаются из кэш-памяти Tower A в Tower B.
- Шаг 2. Переместите соответствующий диск в Tower C
- Шаг 3. Переместите все кэшированные диски из башни B в башню C.
Вот реализация кода:
var ATower = new Stack(); // A塔
var BTower = new Stack(); // B塔
var CTower = new Stack(); // C塔 (目标塔)
var TIER = 4; // 层数
for(var i = TIER; i > 0; i--){
ATower.push(i);
}
function Hanoi(n, from, to, buffer){
if(n > 0){
Hanoi(n - 1, from, buffer, to); // 所有不符合要求的盘(n-1),从A塔统一移到B塔缓存
to.push(from.pop()); // 将符合的盘(n)移动到C塔
Hanoi(n - 1, buffer, to, from); // 把B塔缓存的盘全部移动到C塔
}
}
Hanoi(ATower.read().length, ATower, CTower, BTower);
В центре внимания Ханойской башни является достижение этого путем рекурсии. Разбейте большую проблему на более мелкие с помощью рекурсии. Затем сосредоточьтесь на решении мелких проблем.
3. Резюме
Прежде чем я это понял, я написал слишком много ОРЗ.
Рекомендуется использовать справочные ссылки в следующих главах. Возможно, с этой статьей у вас будет более глубокое понимание.
Ссылаться на
[1] инфиксная нотация
[2] постфиксная нотация
[3] Алгоритм планирования поля
[4] Ханойская башня
Друзья, которым нравятся мои статьи, могут подписаться на меня следующими способами:
- "звезда"или"смотреть"мойGitHub blog
- RSS-канал моего личного блога:База мистера Вана