310-112 · Algorithms & Basic Programming · หน่วยที่ 5

STACK→ POSTFIX

เน้น ตรรกะ ล้วน — ทำไมต้อง pop ตอนไหน ทำไมต้อง push ตอนไหน · ทุกขั้นตอนของการแปลง Infix → Postfix เดินทีละตัวอักษรพร้อมเหตุผลที่ตัดสินใจ

SOURCE 308-233_ch05_Stack.pdf (76 สไลด์) + ALAB5-1.C / ALAB5-2.C ต้องทำได้ เขียนตาราง 3 คอลัมน์แปลงนิพจน์
01

พื้นฐานที่ต้องรู้ก่อน — ฉบับย่อ

ทุกหัวข้อมีภาพเคลื่อนไหวประกอบ · อ่าน bullet แล้วดูภาพก็พอ

สแตกคืออะไร
  • โครงสร้างเชิงเส้น — แตะได้ที่ ปลายด้านเดียว เรียกว่า top
  • LIFO — เข้าทีหลัง ออกก่อน (= FILO) · ต่างจาก Queue = FIFO
  • ภาพที่อาจารย์ใช้: กองชามร้านก๋วยเตี๋ยว — จะหยิบใบล่างต้องรื้อจากบนลงมา
  • เอาไปใช้: เรียกโปรแกรมย่อย · คำนวณนิพจน์ ← บทนี้ · recursion
5 การดำเนินการ
  • clearStack — array: top=0 · list: top=NULL
  • emptyStack — เช็คก่อน pop ทุกครั้ง กัน Underflow
  • fullStack — เช็คก่อน push กัน Overflow (แบบ list ไม่ต้องมี — dynamic)
  • pushวางของก่อน แล้ว top++
  • poptop-- ก่อน แล้วค่อยอ่านค่า ← สลับไม่ได้
clearStack()เตรียมพร้อม
ก่อน
ขยะเดิม
หลัง
ว่าง · top=0
ตั้ง top = 0 · สแตกพร้อมใช้
แบบ list: top = NULL
push(10)เล่นวนซ้ำ
[0] [1] [2] [3]
5
14
10
◀ top
stack[top] = 10 top++ → top = 3
ของใหม่ตกลงช่องที่ top ชี้ แล้ว top ขยับขึ้น 1
วางบนสุดเสมอ — แทรกกลางไม่ได้
pop()เล่นวนซ้ำ
[0] [1] [2] [3]
5
14
10
◀ top
top-- → top = 2 d = stack[2] = 10
ลด top ก่อน แล้วค่อยอ่านค่าที่ช่องนั้น
สลับ 2 บรรทัดไม่ได้ — จะอ่านโดนช่องว่าง
emptyStack()ว่างไหม
top = 0
ว่าง
คืนค่า
TRUE (1)
เช็คก่อน pop ทุกครั้ง
ไม่เช็ค = Stack Underflow
fullStack()เต็มไหม
top = MAXSIZE
54321
คืนค่า
TRUE (1)
เช็คก่อน push ทุกครั้ง
ไม่เช็ค = Stack Overflow
แบบ list ไม่ต้องมี — ขอ memory เพิ่มได้
2 ข้อผิดพลาดออกสอบ
push ตอนเต็ม
OVERFLOW
·
pop ตอนว่าง
UNDERFLOW
เต็มแล้วยัด = Overflow
ว่างแล้วดึง = Underflow
อย่าสลับกัน

lab 00a · กองชามร้านก๋วยเตี๋ยว — ทำไมถึงเรียก LIFO

ภาพที่อาจารย์ใช้

lab 00b · Stack (LIFO) vs Queue (FIFO)

ใส่เหมือนกัน · ออกคนละแบบ

lab 01 · push / pop ทีละคำสั่ง

ตรงกับสไลด์หน้า 26–27
สร้างสแตก 2 แบบ
ARRAY · ALAB5-1.C
  • int stack[MAXSIZE]; int top;
  • push → stack[top]=d; top++;
  • pop → top--; d=stack[top];
  • ขนาดคงที่ → ต้องเช็ค full
LINKED LIST · ALAB5-2.C
  • struct node{int data; struct node *next;}; struct node *top;
  • push = แทรกหัวลิสต์current->next=top; top=current;
  • pop = ลบหัวลิสต์top=top->next; free();
  • ไม่ต้องเช็ค full — ขอ memory เพิ่มได้

lab 02 · สแตกแบบ Linked List — ทีละครึ่งบรรทัด

เห็น node + โค้ดที่รันอยู่
◈ การบ้าน — ในไฟล์ ALAB5-1.C อาจารย์เว้น push() กับ pop() ว่างไว้
เปิดไฟล์ดูจะเห็นว่าตัวฟังก์ชันเป็นวงเล็บเปล่า — ต้องเติมเอง เอาอัลกอริทึมจากสไลด์หน้า 23 กับ 25 มาใส่ตรงๆ ได้เลย
push()
void push(int num)
{
   if(fullStack()){
      printf("
STACK FULL
");
   }
   else
   {
      stack[top] = num;   // วางที่ช่อง top
      top = top + 1;      // แล้วเลื่อน top ขึ้น
   }
}
pop()
int pop()
{
   int d;
   if(emptyStack()){
      printf("
STACK EMPTY
");
      return 0;
   }
   else
   {
      top = top - 1;      // ลด top ก่อน
      d = stack[top];     // แล้วค่อยอ่านค่า
      stack[top] = '\0';  // เคลียร์ช่อง (ให้เมนู 3 แสดงผลถูก)
      return d;
   }
}
  • สลับ 2 บรรทัดใน pop ไม่ได้ — ถ้าอ่าน stack[top] ก่อนลด top จะได้ช่องว่างที่ยังไม่มีข้อมูล
  • ในไฟล์จริง emptyStack() เขียนว่า top <= 0ต่างจากสไลด์หน้า 19 ที่เขียน top <= -1
    เพราะไฟล์นี้ clearStack() ตั้ง top = 0 (top = จำนวนสมาชิก = ช่องว่างถัดไป) → ว่างเมื่อ top = 0 · ถ้าใช้แบบสไลด์ต้องเริ่ม top = −1 แทน · ยึดตามไฟล์ที่ต้องส่ง
02

★ แปลง Infix → Postfix ทีละตัวอักษร

หัวใจของบทนี้ · พิมพ์นิพจน์เองได้ — ทุก step บอกเหตุผลที่ตัดสินใจ push หรือ pop

1ลำดับความสำคัญของ operatorแบบคณิตศาสตร์ปกติ
ลำดับOperatorทิศทางการคำนวณตัวอย่าง
สูงสุด( )ในวงเล็บก่อนเสมอ(A+B)*C → ทำ A+B ก่อน
3^ขวา → ซ้ายA^B^C = A^(B^C)
2* /ซ้าย → ขวาA*B/C = (A*B)/C
ต่ำสุด+ −ซ้าย → ขวาA+B−C = (A+B)−C
  • แรงกว่า = ต้องคำนวณก่อน = ต้องออกไปอยู่ใน postfix ก่อน
  • ทิศทางสำคัญเฉพาะตอนเจอ operator ระดับเดียวกันติดกัน — เช่น A−B−C กับ A^B^C ผลต่างกัน
2เจอ operator แต่ละตัว แล้วเกิดอะไรขึ้นนี่คือ "การย้าย" ที่ต้องเข้าใจ
operator ไม่ได้ออกไป postfix ทันที เหมือน operand — มันเข้าไปรอในสแตกก่อน แล้วจะออกมาเมื่อเจอตัวที่อ่อนกว่าหรือเท่ากันมาไล่ หรือเมื่อเจอ ) หรือจบสตริง
+ −อ่อนที่สุด
เข้ามาแล้วไล่ทุกตัวในสแตกออกหมด (เพราะทุกตัวแรงกว่าหรือเท่ากัน) เหลือแต่ ( ที่ไล่ไม่ออก
+เจอ* * ออกไป postfix+ เข้าแทน
A*B+C → เจอ + ต้องไล่ * ออกก่อน
ได้ AB*C+
* /กลาง
ไล่ได้เฉพาะ * / ^ (แรงกว่าหรือเท่า) · ไล่ + − ไม่ได้ เพราะอ่อนกว่า → + − ค้างอยู่ในสแตกต่อ
*เจอ+ + ยังอยู่·* ทับลงไป
A+B*C → + ยังค้าง, * ทับข้างบน
จบแล้ว pop ได้ * ก่อน + → ABC*+
^แรงสุด · ขวาไปซ้าย
ไล่ * / + − ออกได้หมด แต่ ไล่ ^ ด้วยกันเองไม่ได้ (pred 4 ≤ 3 ไม่จริง) → ^ ซ้อนกันได้
^เจอ^ ^ ยังอยู่·^ ทับลงไป
A^B^C → ^ 2 ตัวซ้อนกัน
pop ออกทีหลังสุดก่อน → ABC^^
(เปิดวงเล็บ
push ลงไปเฉยๆ ไม่ไล่ใครออก (pred ตอนเข้า = 4 สูงสุด) · พออยู่ในสแตกกลายเป็น 0 อ่อนสุด = ไม่มีใครไล่มันออกได้
(push เฉยๆ ·กลายเป็นกำแพงกั้น
ทำหน้าที่เป็นกำแพง — operator ที่เข้ามาทีหลังไล่ของที่อยู่ก่อน ( ไม่ได้
)ปิดวงเล็บ
ไม่ push · แต่บังคับ pop ทุกตัวออกจนเจอ ( แล้วทิ้ง ( ไปเลย
)pop จนถึง ( ทิ้ง ( ทั้งคู่
วงเล็บไม่เคยโผล่ใน postfix — หน้าที่มันคือบังคับลำดับ พอทำเสร็จก็หายไป
\0จบสตริง
pop ทุกตัวที่เหลือในสแตกออกมาต่อ postfix
\0pop ทั้งหมด ได้คำตอบ
ถ้ายังมี ( ค้างอยู่ตอนนี้ = วงเล็บไม่สมดุล
3ตารางตัดสินใจ — เจอตัวไหน เจอบนสุดเป็นตัวไหนสร้างจากค่า precedence จริง
pop ตัวบนสุดออกก่อน แล้วค่อย push ตัวใหม่ push ตัวใหม่ลงไปเลย ไม่ต้อง pop
อ่านตาราง: แถว = ตัวที่กำลังอ่านเข้ามา · คอลัมน์ = ตัวที่อยู่บนสุดของสแตกตอนนั้น
4ตารางค่า precedence ที่ใช้เทียบท่องให้ได้ก่อน
Operator^*/+()\0
ค่าตอนอ่านเข้ามา (Infix)4221140−1
ค่าตอนอยู่ในสแตก322110−1
กฎเดียวที่ต้องจำ
while ( predinfix(ตัวที่อ่านเข้ามา) <= predstack(ตัวบนสุดของสแตก) ) → pop ออกไปต่อ postfix
แล้วค่อย push ตัวใหม่ลงสแตก
  • ทำไม ( ถึงมี 2 ค่า (4 กับ 0)?
    ตอนอ่านเข้ามาให้ค่า 4 สูงสุด → 4 <= อะไรก็ไม่จริง = ไม่ไปเตะใครออก push ลงไปเฉยๆ
    แต่พออยู่ในสแตกให้ค่า 0 ต่ำสุด → ตัวที่มาทีหลังเทียบแล้วไม่มีทาง <= 0 = ไม่มีใครเตะ ( ออกได้ ต้องรอ ) มาเท่านั้น
  • ทำไม ^ เป็น 4/3? เพราะ 4 <= 3 ไม่จริง → เจอ ^ ซ้อน ^ จะไม่ pop = คำนวณจากขวาไปซ้าย (right-associative) ถูกต้องตามคณิตศาสตร์
  • ทำไม * เป็น 2/2? 2 <= 2 จริง → เจอ * ซ้อน * จะ pop ตัวเก่าออกก่อน = คำนวณซ้ายไปขวา (left-associative)
  • \0 = −1 คือ "สแตกว่าง" — ไม่มีอะไรให้ pop
★ รูปแบบคำตอบที่ต้องเขียนตอนสอบ — ตาราง 3 คอลัมน์
ตัวอย่างจากสไลด์หน้า 66 — (A+B)*C · ไล่ทีละตัวที่อ่านเข้ามา แล้วจดว่าตอนนั้น สแตกมีอะไร กับ postfix ได้อะไรแล้ว
ตัวที่อ่านเข้ามาOperator stackนิพจน์ Postfix
((ว่าง
A(A
+(+A
B(+AB
)ว่างAB+
**AB+
C*AB+C
\0ว่างAB+C*
  • แถวสุดท้าย (\0) = pop ที่เหลือทั้งหมด → ช่อง Postfix แถวนี้คือคำตอบ
  • คอลัมน์ Operator stack เขียนจากล่างขึ้นบน — ตัวขวาสุดคือ top
  • สังเกต ( กับ ) ไม่เคยโผล่ในคอลัมน์ Postfix เลยสักแถว
💡 ในแล็บข้างล่าง ตารางนี้จะสร้างขึ้นทีละแถวตามที่กด step — เทียบกับที่เขียนเองได้เลย
5อัลกอริทึม 4 กรณี + ตัวแปลงพิมพ์นิพจน์เองได้
อัลกอริทึม 4 กรณี (สไลด์หน้า 50–51)
เจออะไรทำอะไร
operand
A B C 1 2
ต่อเข้า postfix ทันที — ไม่ต้องคิดอะไรเลย
(push ลงสแตกเลย
)pop ออกมาต่อ postfix เรื่อยๆ จนเจอ ( · แล้วทิ้ง ( ไป (ไม่ต่อ postfix)
วงเล็บไม่เคยโผล่ใน postfix
operatorเทียบ precedence: pop ตัวที่มีค่า ≥ ออกก่อน แล้วค่อย push ตัวใหม่
จบสตริงpop ที่เหลือทั้งหมดออกมาต่อ postfix
6ตัวอย่างเยอะๆ ให้จับตรรกะกดการ์ด = โหลดเข้าตัวแปลง

lab 05 · ตัวแปลง Infix → Postfix

← → เปลี่ยน step ได้
03

โจทย์ฝึก — ทำเองก่อนกดเฉลย

พิมพ์ลงตารางทุกช่องแล้วกดตรวจ · หรือจะร่างในกระดาษทดก่อน (ปุ่มมุมขวาล่าง / กด D)

lab 07 · drill

ข้อ 1 / 6
โจทย์
กรอกให้ครบทุกช่อง แล้วกด ✓ ตรวจคำตอบ
04

คำนวณค่าจากนิพจน์ Postfix

ใช้สแตกอีกรอบ — แต่คราวนี้เก็บ ค่า ไม่ใช่ operator

  • อ่าน postfix จากซ้ายไปขวาทีละตัว
  • เจอ operandpush ลงสแตก
  • เจอ operatorpop 2 ค่า · ตัวแรกที่ pop = ตัวที่ 1, ตัวที่สอง = ตัวที่ 2
  • ลำดับสำคัญมาก: คำนวณ ตัวที่ 2 (operator) ตัวที่ 1 — ไม่ใช่กลับกัน · - กับ / สลับแล้วผิดทันที
  • push ผลลัพธ์กลับลงสแตก แล้วอ่านตัวถัดไป
  • อ่านหมดแล้ว → ค่าที่เหลือตัวเดียวในสแตก = คำตอบ

lab 06 · เครื่องคำนวณ Postfix

ใส่ตัวเลขได้ คิดเลขจริง
05

3 รูปแบบนิพจน์ — ฉบับย่อ

อ่าน bullet 6 บรรทัด แล้วดูต้นไม้ประกอบ — เข้าใจว่าทำไม 3 แบบถึงเป็นนิพจน์เดียวกัน

  • Infix A+B-C — operator ตรงกลาง · คนอ่านง่าย ภาษาโปรแกรมเขียนแบบนี้
  • Postfix AB+C- — operator ข้างหลัง · ไม่ต้องมีวงเล็บ เครื่องคำนวณรอบเดียวจบ
  • Prefix -+ABC — operator ข้างหน้า
  • ทำไมต้องแปลง? Infix ต้องสแกนซ้ำหลายรอบหาว่าตัวไหนคำนวณก่อน — Postfix อ่านรอบเดียวจบ เพราะลำดับฝังอยู่ในตำแหน่งแล้ว
  • ลำดับความสำคัญ: ( ) > ^ > * / > + −
  • ทั้ง 3 แบบมาจากต้นไม้ต้นเดียวกัน — เปลี่ยนแค่จังหวะที่แวะเก็บตัวเอง (ดูภาพข้างล่าง)

lab 03 · ต้นไม้นิพจน์ — ทำไม 3 รูปแบบถึงเป็นตัวเดียวกัน

เดินต้นไม้คนละลำดับ

แผ่นโกง

ท่องก่อนเข้าห้องสอบ

LIFO
เข้าทีหลัง ออกก่อน · แตะได้ที่ top ด้านเดียว
PUSH (array)
stack[top] = d; top = top+1;
POP (array)
top = top-1; d = stack[top];
PUSH (list)
current->next = top; top = current;
POP (list)
value = top->data; top = top->next; free(current);
ERROR 2 แบบ
เต็มแล้ว push = Overflow · ว่างแล้ว pop = Underflow
PRECEDENCE ตอนอ่านเข้า
^ 4 · * / 2 · + − 1 · ( 4 · ) 0 · \0 −1
PRECEDENCE ตอนในสแตก
^ 3 · * / 2 · + − 1 · ( 0 · \0 −1
กฎแปลง
while predIn(ใหม่) <= predSt(บนสุด) → pop ต่อ postfix · แล้ว push
เจอ )
pop จนเจอ ( · ทิ้ง ( ไม่ต่อ postfix
จบสตริง
pop ที่เหลือทั้งหมดต่อ postfix
คำนวณ POSTFIX
operand→push · operator→pop 2 ตัว คิด (ตัวที่2) op (ตัวที่1) → push กลับ
✅ เช็กก่อนเข้าห้องสอบ
  • ท่องตาราง precedence 2 คอลัมน์ได้ (โดยเฉพาะ ( = 4/0)
  • เขียนตาราง 3 คอลัมน์ (ตัวที่อ่าน / Operator stack / Postfix) แปลงนิพจน์ได้ครบ
  • อธิบายได้ว่าทำไมวงเล็บไม่โผล่ใน postfix
  • คำนวณ postfix แล้วไม่สลับลำดับตอนเจอ กับ /
  • แยก Overflow กับ Underflow ออก · แยก LIFO (stack) กับ FIFO (queue) ออก