Stack (กองซ้อน)
วัตถุประสงค์
- เข้าใจแนวคิดและหลักการของโครงสร้างข้อมูลแบบสแตก
- อธิบายหลักการทำงานของฟังก์ชันการดำเนินงานพื้นฐานของสแตกได้
- เข้าใจหลักการสร้างสแตกด้วยอาร์เรย์และลิงค์ลิสต์ได้
- สามารถแปลงนิพจน์ Infix ให้เป็น Postfix ได้
- เข้าใจหลักการรีเคอร์ซีฟ
ลักษณะของสแตก Stack
- สแตก เป็นโครงสร้างข้อมูลแบบเชิงเส้น
- โครงสร้างข้อมูลที่จัดเก็บเป็นแบบเรียงลำดับต่อเนื่องกันไป
- การเพิ่มหรือนำข้อมูลออกจากสแตกทำได้ที่จุดปลายของสแตกทางเดียว
- มาทีหลังแต่ออกก่อน (Last In – First Out : LIFO)
ส่วนประกอบสำคัญของสแตก
- ตัวชี้สแตก หรือ Stack Pointer เป็นตัวควบคุมการนำสมาชิกเข้า หรือออกจากสแตก
- สมาชิกของสแตก หรือ Stack Element คือ สมาชิกของสแตก
- ค่าสูงสุดของสแตก หรือ Max Stack เป็นค่าที่บอกว่าสแตกนี้สามารถเก็บข้อมูลได้มากที่สุดเท่าไหร่
การสร้างสแตก Stack implement
สามารถสร้างสแตกด้วยการแทนที่ข้อมูลสแตกได้ 2
วิธี คือ
- การสร้างสแตกด้วยอาร์เรย์ เป็นการจัดสรรเนื้อที่หน่วยความจำแบบ Static
- การสร้างสแตกด้วยลิงค์ลิสต์ เป็นการจัดสรรเนื้อที่หน่วยความจำแบบ Dynamic
การจัดการกับสแตก
ในการทำงานของสแตก ประกอบด้วย 2 กระบวนการ คือ
การเพิ่มข้อมูลลงบนสแตก (Push
Stack) และการดึงข้อมูลออกจากสแตก (Pop Stack)
การดำเนินงานของสแตก Stack operations
ประกอบด้วยฟังก์ชันพื้นฐาน
ดังนี้
- ClearStack
- EmptyStack
- FullStack
- Push
- Pop
ฟังก์ชัน Clear stack ClearStack : เป็นการดำเนินการเพื่อลบข้อมูลออกจากสแตกให้หมดกรณีใช้โครงสร้างข้อมูลอาร์เรย์
ต้องสั่งให้ช่องบนสุดของสแตกเป็น 0 Stack.Top :=
0;
ฟังก์ชัน Clear stack ClearStack : กรณีใช้โครงสร้างข้อมูลลิงค์ลิสต์
ต้องสั่งให้ตัวชี้สแตกไม่ชี้ไปที่ไหนเลย
กำหนดค่าตัวชี้สแตก =
Null
ฟังก์ชัน empty stack EmptyStack : เป็นการดำเนินการเพื่อตรวจสอบว่าสแตกนั้นมีข้อมูลหรือไม่ กรณีใช้โครงสร้างข้อมูลอาร์เรย์
ต้องสั่งให้ตรวจสอบว่าที่ Top
ของสแตกนั้นมีค่า = 0 หรือไม่ EmptyStack :=
Stack.top := 0;
{ถ้าเป็นจริงจะส่งค่า
True ออกมาให้โปรแกรมหลัก}
กรณีใช้โครงสร้างข้อมูลลิงค์ลิสต์
ให้ตรวจสอบว่าตัวชี้สแตกไม่ชี้ไปที่ไหน EmptyStack :=
Stack.Nil;
{ถ้าเป็นจริงคืนค่า True ถ้าไม่จริง
คืนค่า False}
ฟังก์ชัน Full stack FullStack : เป็นการดำเนินการเพื่อตรวจสอบว่าสแตกนั้นเต็มหรือไม่ ดำเนินการตรวจสอบเฉพาะข้อมูลอาร์เรย์
ต้องตรวจสอบว่า Top ของสแตกนั้นเป็นค่าสูงสุดที่ขอไว้หรือไม่
FullStack := Stack.top := MaxStack;
ตัวอย่างการ Push stack
ฟังก์ชัน push stack เป็นการทำงานของสแตกในการนำข้อมูลเข้าสู่สแตก
โดยก่อนที่จะนำข้อมูลเข้านั้นต้องจัดการให้ตัวชี้สแตกให้ไปชี้ในตำแหน่งต่อไปของสแตกก่อน
ฟังก์ชัน push stack ในกรณีที่ Push ข้อมูลลงสู่สแตกจนตัวชี้สแตกเท่ากับจำนวนช่องของสแตกแล้ว
จะไม่สามารถทำการ Push ข้อมูลได้อีก
จะเกิด Error ที่เรียกว่า
Stack Overflow
อัลกอริทึมในการ push stack
- ตรวจสอบว่าสแตกเต็มหรือไม่ โดยเปรียบเทียบค่า Top กับขนาดของตัวแปรอาร์เรย์ที่ใช้เก็บ ถ้าไม่เต็ม ทำข้อ 2. ถ้าเต็ม ทำข้อ 4.
- ขยับตำแหน่ง Top ไปยังตำแหน่งถัดไป (Top = Top +1)
- ทำการนำค่าที่ได้มาเก็บไว้ยังตำแหน่งของ Top {push(value,stack);}
- แจ้งผู้ใช้งานว่า สแตกเต็มไม่สามารถเก็บข้อมูลได้
- จบการทำงาน
ฟังก์ชัน pop stack เป็นการทำงานของสแตกที่นำข้อมูลที่เก็บอยู่ในสแตกออกจากสแตกมาใช้งาน
เมื่อทำการ Pop ข้อมูลออกจากสแตกแล้ว
ต้องมีการจัดการตัวชี้สแตกให้ชี้ไปยังช่องหรือตำแหน่งก่อนหน้าข้อมูลที่ได้ทำการ Pop ออกไป การ Pop ข้อมูล
จะนำข้อมูลส่วนบนสุดของสแตกออกไปทำงานตามความต้องการ แต่ไม่สามารถ Pop ข้อมูลออกจากสแตกที่ว่างเปล่าได้
เพราะจะเกิด Error ที่เรียกว่า Stack Underflow
ฟังก์ชัน pop stack
อัลกอริทึม pop stack
- ตรวจสอบว่าสแตกว่างหรือไม่ โดยเปรียบเทียบค่า Top ว่ามีค่าเท่ากับ -1 หรือไม่ ถ้าไม่ว่าง ทำข้อ 2. ถ้าว่าง ทำข้อ 4. isEmpty(stack)
- ทำการดึงค่าในตำแหน่งของ Top ออกมาใช้งาน pop(stack)
- ขยับตำแหน่ง Top ให้ถอยหลังลงมา (Top = Top-1) ไปทำข้อ 5.
- แจ้งผู้ใช้งานว่า สแตกว่างไม่สามารถดึงข้อมูลไปใช้งานได้
- จบการทงาน
เปรียบเทียบประสิทธิภาพของการสร้างสแตกด้วยอะเรย์และลิงค์ลิสต์
- การแทนที่ข้อมูลสแตกด้วยอะเรย์ มีข้อจำกัด สำหรับขนาดของสแตกและจะต้องจองเนื้อที่เท่ากับขนาดที่ ใหญ่ที่สุดของสแตกไว้เลย
- การแทนที่ข้อมูลสแตกด้วยลิงค์ลิสต์ ไม่มี ข้อจำกัดของขนาดของสแตกและหน่วยความจำจะถูกใช้ก็ต่อ เมื่อมีข้อมูลจริงๆ แล้วเท่านั้น เพราะเป็นการจัดสรรเนื้อที่หน่วย ความจำแบบไดนามิก
การประยุกต์ใช้งานสแตก
- การเรียงลำดับข้อมูลแบบย้อนกลับ (Reversing Data)
- การแตกข้อมูลออกเป็นส่วน ๆ (Parsing)
- การหน่วงเวลา (Postponement)
- การย้อนรอย (Backtracking Steps)
การใช้สแตกเพื่อแปลงนิพจน์
Infix เป็น Postfix Infix คือ นิพจน์ที่มีตัวดำเนินการ (Operator) อยู่ระหว่างตัวถูกกระทำ
(Operand) เช่น A +
B เครื่องหมาย +
เป็นโอเปอเรเตอร์ระหว่างโอเปอร์แรนด์
A และ B ซึ่งเห็น ว่าเป็นนิพจน์ที่มนุษย์คุ้นเคย ตัวดำเนินการ ก็คือ
เครื่องหมายทางคณิตศาสตร์ สำหรับการคำนวณต่าง ๆ เรียงตามลำดับการดำเนินการก่อนหลัง
(precedence) ได้แก่
ยกกำลัง ^
คูณ หาร * , /
บวก ลบ + , -
ถ้าเครื่องหมายมีลำดับการดำเนินการเดียวกัน
จะเลือกดำเนินงานของเครื่องหมาย จากซ้ายไปขวา (ยกเว้น ยกกำลัง)
และถ้ามีวงเล็บจะดำเนินงานสิ่งที่อยู่ในวงเล็บก่อน
การใช้สแตกเพื่อแปลงนิพจน์
Infix เป็น Postfix ข้อเสียของนิพจน์ infix
ที่ทำให้คอมไพเลอร์ยุ่งยาก คือ
ลำดับความสำคัญของโอเปอร์เรเตอร์ (Precedence) ที่ต่างกัน เช่น เครื่องหมายยกกำลัง (^) มีความสำคัญมากกว่า เครื่องหมายคูณ (*) และหาร (/)
และเครื่องหมายคูณและหารมี ความสำคัญมากกว่าเครื่องหมายบวก (+) และลบ (-) เครื่องหมายใดมีลำดับความสำคัญมากกว่าจะถูกคำนวณก่อน
(ถ้าไม่มีวงเล็บกำกับ) เช่น A + B * C เครื่องจะคำนวณ B *
C ก่อนแล้วนำผลลัพธ์นั้นไปบวกกับค่า
A
ซึ่งจะทำงานเหมือน กับ
A
+ (B * C) ส่วนนิพจน์ใดที่มีโอเปอร์เรเตอร์ที่มีลำดับ
ความสำคัญเท่ากัน การคำนวณจะกระทำจากซ้ายไปขวา เช่น A – B + C จะทำ A –
B ก่อน แล้วจึงนำผลลัพธ์นั้นไปบวกกับ ค่า C
นิพจน์ Infix, Postfix เมื่อการประมวลผลนิพจน์
infix
เป็นไปด้วยความยากที่ การคำนวณไม่เป็นไปตามลำดับของเครื่องหมายโอเปอร์เรเตอร์ที่มีก่อนหลัง
คอมไพเลอร์จึงแปลงนิพจน์ infix ให้เป็น นิพจน์ postfix เสียก่อน ซึ่งนิพจน์ postfix คือนิพจน์ที่มีโอเปอร์เรเตอร์อยู่หลังโอเปอร์แรนด์ทั้งสองของมัน
เช่น
AB+ หมายถึง A + B AB/
หมายถึง A / B
AB- A
– B AB^ A ^ B
AB* A
* B
นิพจน์ Infix, Postfix ข้อดีของนิพจน์ postfix
คือเป็นนิพจน์ที่มีการคำนวณตาม
โอเปอร์เรเตอร์ที่มาก่อนหลัง เช่น นิพจน์ ABC*+ หมายถึง
ทำการคูณแล้วจึงทำการบวก ซึ่งคือต้องคำนวณ B*C ก่อน แล้วจึงนำผลลัพธ์นั้นไปบวกกับ A ต่อไป
กฎการแปลงนิพจน์ Infix, Postfix ด้วยมือ
- ให้ใส่เครื่องหมายวงเล็บให้กับทุก ๆ นิพจน์ด้วยการคำนึงถึงลำดับการคำนวณ
- ทำการเปลี่ยนสัญลักษณ์ Infix ในแต่ละวงเล็บให้มาเป็นสัญลักษณ์แบบ Postfix
- ถอดเครื่องหมายวงเล็บทิ้งให้หมด
กฎการแปลงนิพจน์ Infix, Postfix ด้วยมือ
จงแปลงนิพจน์ A + B * C มาเป็นนิพจน์
Postfix ด้วยมือ
- ใส่วงเล็บให้หมดตามลำดับความสำคัญ (A + (B*C) )
- พิจารณานิพจน์ที่อยู่วงเล็บในสุด โดยย้ายเครื่องหมาย * ไปข้างหลัง C (A + (BC*)) จากนั้นย้ายโอเปอเรเตอร์ + ซึ่งอยู่ที่ตำแหน่งวงเล็บเปิดภายนอกไปยังตำแหน่งวงเล็บปิดภายนอกของคู่ตัวเอง (A(BC*)+)
- ถอดเครื่องหมายวงเล็บทิ้งออกให้หมด ABC*+
= ((56*)-10)
= ((56*)10-)
= 56*10-
นิพจน์ Infix, Postfix
การนำสแตกมาใช้ในการแปลงนิพจน์
Infix, Postfix
- ถ้าค่า input เป็น operand ให้นำค่าไปเป็น Output
- ถ้าค่า input เป็น operator (เครื่องหมายคณิตศาสตร์) ให้พิจารณาดังนี้
- ถ้า สแตกว่าง ให้นำ operator ใส่สแตก
- ถ้า สแตกไม่ว่าง ให้นำ operator ไปเปรียบกับค่า operator ตัวบนในสแตก ถ้าค่า operator ที่เป็น input มีค่า precedence น้อยกว่าหรือเท่ากับค่า operator ตัวบนของสแตก ให้ดึง operator ตัวบนในสแตกไปเป็น output แล้วเปรียบเทียบกับ operator ในสแตกตัวถัดไป แล้วเปรียบเทียบ
- ถ้า input เป็น ( ให้ใส่ลงสแตก
- ถ้า input เป็น ) ให้ดึงข้อมูลออกจากสแตกทีละตัวไปเป็น output จนกระทั่งพบ ( ในสแตก ให้ดึงออกมาทิ้งไป ไม่ต้องไปใส่เป็น output
- ถ้า input หมด และสแตกยังมีข้อมูลอยู่ ให้ดึงข้อมูลออกจาก stack ที่ตัวไปเป็น output จนกระทั่งสแตกว่าง
ความคิดเห็น
แสดงความคิดเห็น