Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Stek nima
- 2.1 LIFO va uch amal
- 2.2 Massiv bilan stek
- 2.3 Linked list bilan stek
- 3. Qavslar muvozanati
- 3.1 Sanash yetmaydi
- 3.2 Algoritm
- 4. Bekor qilish (undo)
- 5. Ifodani hisoblash
- 5.1 Postfiks yozuv
- 5.2 Infiksdan postfiksga: saralash stansiyasi
- 6. Rekursiyani stek bilan almashtirish
- 6.1 Muammo
- 6.2 Yechim: o'z stekimiz
- 6.3 Stekni qanday tanish mumkin
- 7. Chegaraviy holatlar
- 8. Ko'p uchraydigan xatolar
- 8.1 Massivning boshini stek tepasi deb ishlatish
- 8.2 Operandlar tartibini almashtirish
- 8.3 Undo'da eski holatni o'zgartirib yuborish
- 8.4 Oxirida stekni tekshirmaslik
- 9. Mashqlar
- 1-mashq (oson): Qo'lda
- 2-mashq (o'rta): Undo va redo
- 3-mashq (qiyin): Kalkulyator va MinStack testlari
- 4-mashq: Amaliy tajriba — murakkablik jadvali
- 10. Real ishda
- Xulosa
- Manbalar
Stack (LIFO): push, pop, qavslar muvozanati va ifodani hisoblash
Qisqacha: Stack (stek) — oxirgi qo'yilgan element birinchi olinadigan tuzilma: LIFO (last in, first out). Uchta asosiy amal bor:
push— tepaga qo'yish,pop— tepadan olish,peek— tepaga qarash. Uchalasi ham O(1). JavaScript massivi tayyor stek. Stek qavslar to'g'ri yopilganini tekshiradi, "bekor qilish" (undo) tarixini saqlaydi, matematik ifodani hisoblaydi va chuqur rekursiyani xavfsiz siklga aylantiradi.
Bu darsda
- Stekni massiv bilan yoza olasiz va uning amallari nega O(1) ekanini tushuntira olasiz.
- Qavslar muvozanatini stek bilan tekshirasiz va "sanash nega yetmaydi"ni ko'rsatasiz.
- "Bekor qilish" (undo) tarixini stek bilan qurasiz.
(35 + 28) * 2 - 5kabi ifodani postfiks yozuvga o'girib, stek bilan hisoblaysiz.- Rekursiyani o'z stekingiz bilan almashtirib,
Maximum call stack size exceededdan qutulasiz.
Oldin bilishingiz kerak: Linked list: tuzilishi va asosiy amallar, Execution context va call stack, Private # maydon va metodlar, Xotira murakkabligi.
1. Nega bu kerak?
«Bahor» oshxonasida yuvilgan likopchalar ustma-ust taxlanadi. Yangi yuvilgani tepaga qo'yiladi. Ofitsiant esa likopchani doim tepadan oladi. Pastdagi likopchani olish uchun ustidagilarning hammasini olish kerak. Oxirgi qo'yilgan — birinchi olinadi.
Dasturda ham shunday vaziyatlar ko'p. Sardor buyurtma tahrirlash ekraniga "Bekor qilish" tugmasini qo'shmoqchi: oxirgi o'zgarish birinchi bekor bo'ladi. Kassadagi narx formulalarida qavslar to'g'ri yopilganini tekshirish kerak. Jasur aka esa "35 + 28 ni 2 ga ko'paytirib, 5 ayir" kabi hisobni kassaning o'zi bajarishini xohlaydi. Uchala masalaning javobi — stek.
Siz stek bilan allaqachon tanishsiz. Bu — chaqiruvlar steki: har funksiya chaqiruvi tepaga qo'yiladi va tugaganda olinadi. O'tgan Xotira murakkabligi darsida chuqur rekursiya shu stekni to'ldirib yuborganini ko'rdik va va'da berdik: "kutubxonalar rekursiya o'rniga o'z stekini ishlatadi". Bugun shu va'dani bajaramiz.
2. Stek nima
2.1 LIFO va uch amal
Stek (stack) — elementlarga faqat bir uchidan (tepa, top) kiriladigan tuzilma. Tamoyili — LIFO (last in, first out): "oxirgi kirgan — birinchi chiqadi". Amallar:
| Amal | Nima qiladi | Narxi |
|---|---|---|
push(x) |
x ni tepaga qo'yadi | O(1) amort. |
pop() |
tepadagini olib, qaytaradi | O(1) |
peek() |
tepadagini qaytaradi, olmaydi | O(1) |
isEmpty() / size |
bo'shmi, nechta | O(1) |
Stekda "o'rtadan olish" yoki "i-elementni ko'rish" amali yo'q — ataylab. Cheklov tuzilmani sodda va tez qiladi.
2.2 Massiv bilan stek
JavaScript massivining push va pop metodlari aynan stek amallari. Ular massiv oxiri bilan ishlaydi — hech kim surilmaydi. push amortizatsiyalangan O(1), pop — O(1). Shuning uchun massiv tayyor stek. Lekin massivda shift, splice, indeks ham bor — ularni tasodifan ishlatib, "stek qoidasini" buzish oson. Klass bilan o'rasak, tashqariga faqat stek amallari chiqadi:
class Stack {
#items = []; // tashqaridan ko'rinmaydi
push(value) {
this.#items.push(value);
}
pop() {
return this.#items.pop(); // bo'sh bo'lsa — undefined
}
peek() {
return this.#items.at(-1);
}
isEmpty() {
return this.#items.length === 0;
}
get size() {
return this.#items.length;
}
}
const plates = new Stack();
plates.push("1-likopcha");
plates.push("2-likopcha");
plates.push("3-likopcha");
console.log(plates.pop()); // 3-likopcha
console.log(plates.peek()); // 2-likopcha
console.log(plates.size); // 2#items — private maydon: plates.#items ni klass tashqarisidan o'qib bo'lmaydi. Foydalanuvchi faqat push, pop, peek ni ko'radi. Bo'sh stekdan pop — undefined (massiv xulqi). Ba'zi loyihalarda bu holatda xato tashlash afzal ko'riladi — "bo'sh stekdan olish" ko'pincha mantiqiy xato belgisi.
2.3 Linked list bilan stek
Linked list darsidagi LinkedList ham tayyor stek: prepend — push, removeFirst — pop, head — tepa. Ikkalasi ham O(1), hech qanday amortizatsiyasiz. Qaysi biri tezroq? O'lchadik: n ta push, keyin n ta pop.
| Elementlar | Massiv stek | Linked list stek |
|---|---|---|
| 1 mln | ≈ 13 ms | ≈ 17 ms |
| 2 mln | ≈ 28 ms | ≈ 24 ms |
| 4 mln | ≈ 51 ms | ≈ 64 ms |
| 8 mln | ≈ 115 ms | ≈ 152 ms |
Ikkalasida ham n ikki baravar — vaqt taxminan ikki baravar: O(n) ish, bitta amal O(1). Massiv biroz tezroq va ancha barqaror. Ro'yxatda har push yangi ListNode obyektini yaratadi, axlat yig'uvchi tez-tez ishlaydi — 8 mln da bir xil o'lchov 62 ms dan 248 ms gacha sakradi. Massivda esa 100–137 ms oralig'ida. Xulosa: JavaScript'da stek uchun massiv — eng yaxshi tanlov.
Tekshirib ko'ring: Stekka ketma-ket
push(1),push(2),pop(),push(3),push(4),pop(),pop()qilindi. Harpopnimani qaytaradi va oxirida stekda nima qoladi?
Javob
Birinchi pop — 2 (1 ustida 2 turgan edi). Keyin 3 va 4 qo'yiladi: stek [1, 3, 4], tepada 4. Ikkinchi pop — 4, uchinchisi — 3. Stekda faqat 1 qoladi. Har pop doim eng oxirgi qo'yilganini oladi.
3. Qavslar muvozanati
3.1 Sanash yetmaydi
Kassa dasturida narx formulalari bor: {[(1+2)*3]}. Qavslar to'g'ri yopilganmi? Birinchi xayol — sanash: ochiluvchilar soni yopiluvchilarga teng bo'lsa — to'g'ri. Lekin ([)] da ham ikkita ochiq, ikkita yopiq qavs bor. Shunga qaramay, u noto'g'ri: ( ichida ochilgan [ dan oldin yopildi.
Qoida aslida shunday: oxirgi ochilgan qavs birinchi yopilishi kerak. Bu — LIFO. Demak, stek kerak.
3.2 Algoritm
Matnni chapdan o'qiymiz. Ochiluvchi qavsni stekka qo'yamiz. Yopiluvchi kelsa — stek tepasini olib, juftmi deb tekshiramiz. Oxirida stek bo'sh bo'lishi kerak (ochiq qolgan qavs yo'q):
pairs obyekti har yopiluvchiga uning juftini beradi: pairs[")"] — "(". ch in pairs — "bu belgi yopiluvchimi?" degan savol (in operatori). Bo'sh stekdan pop — undefined, u hech qaysi qavsga teng emas. Shuning uchun )( kabi matn ham to'g'ri rad etiladi.
Murakkablik: har belgi bir marta, har biriga O(1) amal — O(n) vaqt. Stekda ko'pi bilan n ta qavs — O(n) xotira (eng yomon holat: hammasi ochiluvchi). O'lchov (urug'li generator bilan yasalgan to'g'ri qavsli matn):
| Belgilar | Vaqt | Nisbat |
|---|---|---|
| 1 mln | ≈ 28 ms | — |
| 2 mln | ≈ 49 ms | ×1,7 |
| 4 mln | ≈ 99 ms | ×2,0 |
| 8 mln | ≈ 192 ms | ×1,9 |
n ikki baravar — vaqt taxminan ikki baravar: O(n).
Tekshirib ko'ring:
isBalanced("((")nima qaytaradi va funksiyaning qaysi qatori buni hal qiladi?
Javob
false. Ikkala ( stekka tushadi, yopiluvchi kelmaydi, sikl tugaydi. Oxirgi qator return stack.length === 0 — stekda 2 ta qoldi, demak false. Bu qator bo'lmasa, ochiq qolgan qavslar sezilmay qolardi.
4. Bekor qilish (undo)
Buyurtma tahrirlash ekranida har o'zgarishdan oldin eski holatni stekka qo'yamiz. "Bekor qilish" bosilganda — oxirgi saqlangan holatni stekdan olib, qaytaramiz:
const undoStack = [];
let order = [];
function change(newOrder) {
undoStack.push(order); // eski holatni saqlaymiz
order = newOrder;
}
function undo() {
if (undoStack.length === 0) return; // bekor qiladigan narsa yo'q
order = undoStack.pop();
}
change([...order, "osh"]);
change([...order, "manti"]);
change(order.filter((dish) => dish !== "osh"));
console.log(order); // [ 'manti' ]
undo();
console.log(order); // [ 'osh', 'manti' ]
undo();
console.log(order); // [ 'osh' ]Har o'zgarish yangi massiv yaratadi (spread, filter), eskisini o'zgartirmaydi. Shuning uchun stekdagi holatlar buzilmaydi — o'zgarmaslik shu yerda juda foydali. Agar order.push("osh") qilsak, stekdagi "eski" holat ham o'zgarib ketardi: ikkalasi bitta massiv bo'lardi.
Matn muharrirlari, Figma, VS Code — hammasida undo shunday ishlaydi. "Qaytarish" (redo) uchun ikkinchi stek kerak — buni 2-mashqda qurasiz. Xotira: har holat saqlanadi, demak O(k × holat o'lchami). Katta hujjatlarda butun holat emas, faqat o'zgarish (nima qo'shildi, nima o'chdi) saqlanadi.
Tekshirib ko'ring: Undo stekida 50 ta holat bor. Foydalanuvchi 3 marta "Bekor qilish" bosdi. Stekda nechta holat qoldi va
orderqaysi holatga qaytdi?
Javob
Stekda 47 ta holat qoldi. order — oxiridan uchinchi saqlangan holat: har undo tepadagi (eng yangi) holatni oladi. Ya'ni foydalanuvchi oxirgi uchta o'zgarishdan oldingi ko'rinishni ko'radi. Ko'p dasturlar stekni cheklaydi (masalan, oxirgi 100 ta) — aks holda xotira cheksiz o'sadi.
5. Ifodani hisoblash
5.1 Postfiks yozuv
Biz ifodani (35 + 28) * 2 - 5 deb yozamiz — amal sonlar orasida. Bu infiks yozuv. Uni hisoblash uchun ustuvorlik (avval *, keyin +) va qavslarni hisobga olish kerak — kompyuter uchun noqulay.
Polyak matematigi Jan Lukasiewicz qavssiz yozuv taklif qilgan. Uning teskari varianti — postfiks yozuv yoki RPN (Reverse Polish Notation, teskari polyak yozuvi): amal o'z sonlaridan keyin keladi. (35 + 28) * 2 - 5 RPN'da: 35 28 + 2 * 5 -. Qavs ham, ustuvorlik ham kerak emas — tartibning o'zi hammasini aytadi. Uni stek bilan hisoblash juda sodda:
b birinchi olinadi — u o'ng operand. Ayirish va bo'lishda bu muhim: a - b, b - a emas. Har token bir marta — O(n) vaqt, stekda ko'pi bilan n ta son — O(n) xotira.
5.2 Infiksdan postfiksga: saralash stansiyasi
Kassa esa odamlar yozadigan (35 + 28) * 2 - 5 ni oladi. Uni RPN'ga o'girishning klassik usuli — Edsger Dijkstra'ning "saralash stansiyasi" (shunting-yard) algoritmi. Nomi temir yo'l stansiyasidan: vagonlar (sonlar) to'g'ri chiqishga ketadi, lokomotivlar (amallar) esa yon yo'lda — stekda — navbat kutadi.
Qoidalar: son — darhol chiqishga. ( — stekka. ) — ( gacha hamma amallarni stekdan chiqishga. Amal kelsa — stek tepasidagi kuchliroq yoki teng ustuvorlikdagi amallarni avval chiqaramiz, keyin o'zini stekka qo'yamiz. Oxirida stekda qolganlarni chiqaramiz.
function toRPN(expr) {
const prec = { "+": 1, "-": 1, "*": 2, "/": 2 };
const output = [];
const ops = []; // amallar steki
for (const t of expr.match(/\d+|[-+*/()]/g)) {
if (/\d/.test(t)) {
output.push(t); // son — darhol chiqishga
} else if (t === "(") {
ops.push(t);
} else if (t === ")") {
while (ops.at(-1) !== "(") output.push(ops.pop());
ops.pop(); // "(" ni tashlaymiz
} else {
// stek tepasidagi kuchliroq (yoki teng) amallar oldin chiqadi
while (ops.length > 0 && prec[ops.at(-1)] >= prec[t]) {
output.push(ops.pop());
}
ops.push(t);
}
}
while (ops.length > 0) output.push(ops.pop());
return output;
}
console.log(toRPN("(35 + 28) * 2 - 5").join(" "));
console.log(toRPN("35 + 28 * 2").join(" "));Konsolda:
35 28 + 2 * 5 -
35 28 2 * +expr.match(/\d+|[-+*/()]/g) matnni tokenlarga bo'ladi: sonlar va belgilar (match va g flagi). Ikkinchi misolga qarang: * + dan kuchli, shuning uchun RPN'da 28 2 * avval keladi. prec["("] — undefined, undefined >= 1 esa false. Shuning uchun ( amallarni "to'sib" turadi. Bu ikki funksiya — toRPN va evalRPN — birga oddiy kalkulyator bo'ladi (3-mashqda yig'asiz).
Diqqat: Bu soddalashtirilgan versiya: manfiy son (
-5), kasr (2.5) va darajani (**) tanimaydi. Haqiqiy kalkulyatorda ular ham, xato kirish (2 + * 3) ham hisobga olinadi. Foydalanuvchi matninievalbilan hisoblash esa xavfli — u istalgan JavaScript kodini bajaradi (evalxavfi). Shuning uchun ham o'z tahlilchingiz kerak.
6. Rekursiyani stek bilan almashtirish
6.1 Muammo
Menyu kategoriyalari ichma-ich: "Menyu" ichida "Issiq taomlar", uning ichida "Sho'rvalar"... Hamma narxlar yig'indisini rekursiya bilan topish tabiiy tuyuladi. Lekin bir kuni import xatosi tufayli kategoriyalar 100 000 qavat ichma-ich bo'lib qoldi. Rekursiya RangeError bilan yiqildi — chaqiruvlar steki to'ldi (Xotira murakkabligi).
6.2 Yechim: o'z stekimiz
Chaqiruvlar steki — kichik va o'zgarmas. Massiv esa heap'da yashaydi va millionlab elementga o'sa oladi. Demak, "hali ko'rilmagan kategoriyalar"ni o'zimiz stekda ushlasak, rekursiya kerak bo'lmaydi:
function sumRecursive(category) {
let total = 0;
for (const price of category.prices) total += price;
for (const child of category.children) total += sumRecursive(child);
return total;
}
function sumWithStack(root) {
let total = 0;
const stack = [root]; // "hali ko'rilmagan" kategoriyalar
while (stack.length > 0) {
const category = stack.pop();
for (const price of category.prices) total += price;
for (const child of category.children) stack.push(child);
}
return total;
}
// 100 000 qavat ichma-ich kategoriya (masalan, buzilgan import)
let deep = { prices: [5], children: [] };
for (let i = 0; i < 100000; i++) {
deep = { prices: [5], children: [deep] };
}
console.log(sumWithStack(deep)); // 500005
try {
sumRecursive(deep);
} catch (e) {
console.log(e.name + ": " + e.message);
}Konsolda:
500005
RangeError: Maximum call stack size exceededIkkalasi ham bir xil ishni bajaradi: har kategoriyani bir marta ko'radi — O(n) vaqt. Farqi faqat "kutayotganlar ro'yxati" qayerda saqlanishida. Rekursiyada — dvigatelning chaqiruvlar stekida (taxminan 10 000 qavat sig'adi). Bizning versiyada — oddiy massivda (millionlab sig'adi). Shuning uchun JSON tahlilchilari, papka aylanuvchilari va kompilyatorlar ko'pincha shunday yozilgan. Bu usul daraxt bo'ylab yurish darsida chuqurlik bo'yicha qidiruvning (DFS) asosiy shakli bo'ladi.
6.3 Stekni qanday tanish mumkin
Masala shartida quyidagi belgilar bo'lsa, stek haqida o'ylang:
- "Eng oxirgisi" bilan ishlash: oxirgi ochilgan qavs, oxirgi o'zgarish, oxirgi kirilgan papka.
- Ichma-ichlik: qavslar, teglar, kategoriyalar, funksiya chaqiruvlari.
- "Orqaga qaytish": labirintdan chiqish yo'li, tarix, bekor qilish.
- Rekursiya bilan yechiladigan, lekin chuqurligi juda katta bo'lishi mumkin bo'lgan masala.
Keyingi va undan keyingi darslarda yana ikki naqsh qo'shiladi: "birinchi kelgan — birinchi" (navbat) va "tartibli stek" (monotonic stack).
7. Chegaraviy holatlar
| Holat | Nima bo'ladi |
|---|---|
Bo'sh stekdan pop / peek |
undefined — tekshiring yoki xato tashlang |
Bo'sh matn — isBalanced("") |
true (ochiq qavs yo'q) |
Faqat yopiluvchi — ")" |
pop() → undefined ≠ "(" → false |
RPN'da operand yetmaydi — ["+"] |
undefined + undefined = NaN — tokenlar sonini tekshiring |
Nolga bo'lish — 5 0 / |
Infinity — xato emas, lekin kassada qabul qilinmaydi |
| Juda chuqur ichma-ichlik | rekursiya yiqiladi, stekli versiya ishlaydi |
8. Ko'p uchraydigan xatolar
8.1 Massivning boshini stek tepasi deb ishlatish
unshift/shift bilan "stek" — har amal O(n) (JS o'rnatilgan amallarining narxi). Tuzatish: tepa — massiv oxiri: push/pop.
8.2 Operandlar tartibini almashtirish
const a = stack.pop(); const b = stack.pop(); push(a - b) — 35 28 - uchun −7 chiqadi, 7 emas. Tuzatish: birinchi olingani — o'ng operand (b).
8.3 Undo'da eski holatni o'zgartirib yuborish
undoStack.push(order); order.push("osh") — stekdagi va joriy holat bitta massiv. Bekor qilish hech narsani qaytarmaydi. Tuzatish: har o'zgarishda yangi massiv/obyekt yarating yoki nusxa saqlang.
8.4 Oxirida stekni tekshirmaslik
Qavslar tekshiruvida return true — "((" ham "to'g'ri" bo'lib qoladi. Tuzatish: return stack.length === 0.
9. Mashqlar
1-mashq (oson): Qo'lda
(a) isBalanced("{[]()}") va isBalanced("{[}]") — har belgidan keyin stek holatini yozing. (b) 5 1 2 + 4 * + 3 - RPN ifodasini qo'lda hisoblang.
Yechim
(a) {[]()}: { → [{], [ → [{, [], ] → [{], ( → [{, (], ) → [{], } → [] — bo'sh, true. {[}]: { → [{], [ → [{, [], } keladi, tepada [ — juft emas, false.
(b) 5, 1, 2 → + → 5, 3 → 4 → * → 5, 12 → + → 17 → 3 → - → 14. Javob 14. Infiksda: 5 + (1 + 2) × 4 − 3.
2-mashq (o'rta): Undo va redo
Undo misolini kengaytiring: redo() qo'shing. Bekor qilingan holat ikkinchi stekka (redoStack) tushsin, redo uni qaytarsin. Yangi change bo'lsa, redoStack tozalanadi (eski "kelajak" endi yaroqsiz). Ishora: undo da joriy holat redoStack ga, redo da esa undoStack ga qo'yiladi.
Yechim
const undoStack = [];
const redoStack = [];
let order = [];
function change(newOrder) {
undoStack.push(order);
redoStack.length = 0; // yangi o'zgarish — "kelajak" o'chadi
order = newOrder;
}
function undo() {
if (undoStack.length === 0) return;
redoStack.push(order);
order = undoStack.pop();
}
function redo() {
if (redoStack.length === 0) return;
undoStack.push(order);
order = redoStack.pop();
}
change(["osh"]);
change(["osh", "manti"]);
undo();
console.log(order); // [ 'osh' ]
redo();
console.log(order); // [ 'osh', 'manti' ]
undo();
change(["osh", "lag'mon"]);
redo(); // hech narsa qilmaydi — redo tarixi tozalangan
console.log(order); // [ 'osh', "lag'mon" ]redoStack.length = 0 — massivni joyida bo'shatishning qisqa yo'li. Oxirgi qatorda Node lag'mon ni qo'shtirnoqda chiqaradi, chunki ichida apostrof bor. Ikki stek — brauzerdagi "orqaga" va "oldinga" tugmalarining ham modeli.
3-mashq (qiyin): Kalkulyator va MinStack testlari
kurs/mashqlar/14/18-stek/stek.test.mjs faylida:
toRPNvaevalRPNni yozing,calc(expr)— ikkalasini ulaydi.MinStackklassini yozing:push,pop,peek,sizevamin()— stekdagi eng kichik qiymat, O(1) da. Ishora: ikkinchi, yordamchi stekda har qavat uchun "shu paytgacha eng kichigi"ni saqlang.
node:test bilan sinang: ustuvorlik (35 + 28 * 2 = 91), qavslar, chapdan o'ngga ayirish (100 - 30 - 20 = 50), bo'sh MinStack, pop dan keyin min qaytishi.
Yechim
// kurs/mashqlar/14/18-stek/stek.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";
class MinStack {
#items = [];
#mins = []; // har qavatda "shu paytgacha eng kichigi"
push(value) {
this.#items.push(value);
const min = this.#mins.length ? this.#mins.at(-1) : value;
this.#mins.push(Math.min(min, value));
}
pop() {
this.#mins.pop();
return this.#items.pop();
}
peek() {
return this.#items.at(-1);
}
min() {
return this.#mins.at(-1);
}
get size() {
return this.#items.length;
}
}
function toRPN(expr) {
const prec = { "+": 1, "-": 1, "*": 2, "/": 2 };
const output = [];
const ops = [];
for (const t of expr.match(/\d+|[-+*/()]/g)) {
if (/\d/.test(t)) output.push(t);
else if (t === "(") ops.push(t);
else if (t === ")") {
while (ops.at(-1) !== "(") output.push(ops.pop());
ops.pop();
} else {
while (ops.length > 0 && prec[ops.at(-1)] >= prec[t]) {
output.push(ops.pop());
}
ops.push(t);
}
}
while (ops.length > 0) output.push(ops.pop());
return output;
}
function evalRPN(tokens) {
const stack = [];
for (const t of tokens) {
if ("+-*/".includes(t)) {
const b = stack.pop();
const a = stack.pop();
if (t === "+") stack.push(a + b);
if (t === "-") stack.push(a - b);
if (t === "*") stack.push(a * b);
if (t === "/") stack.push(a / b);
} else {
stack.push(Number(t));
}
}
return stack.pop();
}
const calc = (expr) => evalRPN(toRPN(expr));
test("MinStack: eng kichigi O(1) da", () => {
const s = new MinStack();
for (const price of [35000, 28000, 30000, 5000]) s.push(price);
assert.equal(s.min(), 5000);
s.pop(); // 5000 ketdi
assert.equal(s.min(), 28000);
assert.equal(s.peek(), 30000);
assert.equal(s.size, 3);
});
test("MinStack: bo'sh stek", () => {
const s = new MinStack();
assert.equal(s.pop(), undefined);
assert.equal(s.min(), undefined);
});
test("kalkulyator: ustuvorlik va qavslar", () => {
assert.equal(calc("35 + 28 * 2"), 91);
assert.equal(calc("(35 + 28) * 2 - 5"), 121);
assert.equal(calc("100 - 30 - 20"), 50); // chapdan o'ngga
assert.equal(calc("((7))"), 7);
});
test("RPN tokenlari", () => {
assert.deepEqual(toRPN("2 * (3 + 4)"), ["2", "3", "4", "+", "*"]);
});Papkada node --test ni ishga tushiring. Bizda (Node 24.21.0) shunday chiqdi — millisekundlar sizda boshqacha bo'ladi:
✔ MinStack: eng kichigi O(1) da (0.803ms)
✔ MinStack: bo'sh stek (0.1488ms)
✔ kalkulyator: ustuvorlik va qavslar (0.5682ms)
✔ RPN tokenlari (1.5055ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 114.2025Uchinchi testdagi 100 - 30 - 20 muhim: toRPN dagi >= (teng ustuvorlikda ham chiqarish) uni 100 30 - 20 - ga aylantiradi — chapdan o'ngga, 50. > yozilsa, 100 30 20 - - chiqadi va javob 90 bo'ladi. MinStack'da minimum alohida stekda yashaydi: pop qilinganda eski minimum o'z-o'zidan "tepaga" chiqadi — qayta qidirish shart emas.
4-mashq: Amaliy tajriba — murakkablik jadvali
kurs/mashqlar/14/MURAKKABLIK.md dagi "Ma'lumotlar tuzilmalari" jadvaliga stek ustunini qo'shing va algoritmlar jadvaliga shu darsdagi uch masalani yozing.
Yechim
| Masala | Yechim | Vaqt | Xotira |
|---|---|---|---|
| Qavslar muvozanati | stek | O(n) | O(n) |
| Postfiks hisoblash | stek | O(n) | O(n) |
| Infiks → postfiks | shunting-yard | O(n) | O(n) |
| Ichma-ich yig'indi | o'z stekimiz | O(n) | O(n) heap'da |Stek ustuni: push/pop/peek — O(1) (massivda push amortizatsiyalangan); i-element, qidirish — "stek amali emas".
git add 14/MURAKKABLIK.md 14/18-stek
git commit -m "14/18: stek — qavslar, kalkulyator, MinStack"10. Real ishda
- Brauzer va muharrirlar. "Orqaga/oldinga" tarixi, Ctrl+Z / Ctrl+Y — ikki stek. VS Code va Figma'dagi undo ham shu g'oya.
- Kompilyator va tahlilchilar. Qavslar, HTML teglarining yopilishi (
<div><p></p></div>), JSON tahlili — stek. Babel, TypeScript, brauzerning HTML tahlilchisi ichida stek bor. - Dvigatelning o'zi. JavaScript'ning chaqiruvlar steki va V8'ning bytecode'i (Ignition registr va akkumulyator bilan ishlaydi, lekin ko'p virtual mashinalar — JVM, WebAssembly — stekli).
- Intervyu. "Valid Parentheses" (LeetCode 20), "Min Stack" (155), "Evaluate Reverse Polish Notation" (150), "Basic Calculator" (224) — stek bo'yicha eng ko'p beriladigan savollar.
Xulosa
- Stek — LIFO: oxirgi qo'yilgan birinchi olinadi.
push,pop,peek— O(1). Massivning oxiri — tayyor stek tepasi. - Qavslar: ochiluvchi — stekka, yopiluvchi — tepasi bilan solishtir, oxirida stek bo'sh bo'lsin. Sanash yetmaydi:
([)]. - Undo: o'zgarishdan oldin eski holatni stekka; redo — ikkinchi stek. Holatlarni o'zgartirmang, yangisini yarating.
- Postfiks (RPN) ifoda stek bilan bir o'tishda hisoblanadi; infiksni shunting-yard RPN'ga o'giradi.
eval— yo'q. - Chuqur rekursiyani o'z stekingiz (massiv) bilan almashtirsangiz,
Maximum call stack size exceededyo'qoladi.
Keyingi dars: Queue va deque (FIFO) — birinchi kirgan birinchi chiqadi: shift tuzog'i, ikki stek bilan navbat, ring buffer va ikki tomonlama navbat.
Manbalar
- Edsger W. Dijkstra, "Algol 60 translation", Mathematisch Centrum, 1961 — shunting-yard algoritmi.
- Thomas H. Cormen va boshq., "Introduction to Algorithms", 4-nashr, MIT Press, 2022 — 10-bob (stek va navbat).
- MDN:
Array.prototype.push,pop,at; Private class features — developer.mozilla.org - LeetCode: 20, 155, 150, 224 — leetcode.com/problems
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!