بررسی پشته (Stack)
پشته (Stack) یکی از پیاده سازی ها در ساختار داده است، در پشته (Stack) دسترسی به عناصر یک طرفه است و بر خلاف لیست که میتونیم به عناصر با index دسترسی داشته باشیم در پشته فقط میتونیم آخرین عنصری که اضافه کردیم رو صدا بزنیم و برای دسترسی به عنصر بعدی باید آخرین عنصر از Stack خارج (حذف) بشه و پشته (Stack) معروفه به last-in-first-out به زبون خودمونی دیر اومدی میخوای زود بری :)
یک لیوان رو در نظر بگیرید که داخلش میخوایم چندتا توپ بندازیم، برای دسترسی به اولین توپی که انداختیم، باید توپ های بعدی رو بیاریم بیرون؛ نگهداری، دسترسی و حذف عناصر در استک (Stack) شبیه همینه.
پشته (Stack) رو میتونیم با استفاده از آرایه ها با سایز ثابت یا با لیست پیوندی (LinkedList) پیاده سازی کنیم؛ در این مطلب به پیاده سازی استک با لیست پیوندی می پردازیم.
میتونیم یک پشته (Stack) رو با رابطه ی aggregation یا inheritance توسط لیست پیوندی پیاده سازی کنیم؛ که در اینجا به پیاده سازی استک با لیست پیوندی با روش aggregation می پردازیم.
افزودن عنصر به پشته (Stack)
به طور عمومی با تابع push(e: E) میتونیم یک عنصر رو به استک اضافه کنیم، ولی ممکنه در زبان های برنامه نویسی مختلف این اسم فرق کنه؛ پیچیدگی زمان برای اضافه کردن یک عنصر به استک O(1) است.
function push(e: E){
linkedList.addLast(e);
}
حذف عنصر از پشته (Stack)
به طور عمومی برای حذف یک عنصر از تابع pop(): E در استک استفاده می کنیم، ممکنه اسم pop():E در زبان های مختلف فرق کنه؛ آخرین عنصری که به استک اضافه می کنیم، اولین عنصری خواهد بود که از استک حذف میشه و تا زمانی که این عنصر از پشته (Stack) حذف نشه به عنصر بعدی دسترسی نخواهیم داشت.
پیچیدگی زمان برای حذف یک عنصر از استک O(1) است.
function pop(): E{
return linkedList.removeLast()
}
صدا زدن عنصر از پشته (Stack)
به طور عمومی با تابع peek(): E برای صدا زدن یک عنصر از پشته (Stack) استفاده می کنیم، نام این تابع ممکنه در زبان های برنامه نویسی مختلف فرق کنه؛ با تابع peek میتونیم به آخرین عنصری که به استک اضافه کردیم دسترسی داشته باشیم و برای دسترسی به عنصر بعدی با peek باید عنصر فعلی حذف بشه.
پیچیدگی زمان برای صدا زدن عنصر از استک O(1) است
function peek(): E{
return linkedList.getLast()
}
پیاده سازی استک در زبان های برنامه نویسی
در اینجا میخوایم با استفاده از لیست پیوندی که پیاده کردیم، استک رو در زبان های برنامه نویسی (جاوا و کاتلین) پیاده سازی کنیم.
در زیر پیاده سازی پشته (Stack) به صورت uml آورده شده است.
public class MyStack<E> {
private final MyLinkedList<E> list = new MyLinkedList<>();
public MyStack(E[] elements){
for (int i=0; i<elements.length; i++){
list.addLast(elements[i]);
}
}
public MyStack(){
}
public E pop(){
if (list.isEmpty()) return null;
return list.removeLast();
}
public void push(E e){
list.addLast(e);
}
public E peek(){
if (list.isEmpty()) return null;
return list.getLast();
}
public boolean isEmpty(){
return list.isEmpty();
}
public int getSize(){
return list.getSize();
}
}
class MyStack<E>() {
private val list: MyLinkedList<E> = MyLinkedList()
val isEmpty: Boolean
get() = list.size == 0
val size: Int
get() = list.size
constructor(elements: Array<E>): this() {
for (i in elements.indices) {
list.addLast(elements[i])
}
}
fun pop(): E? {
if (list.empty) return null
return list.removeLast()
}
fun push(e: E) {
list.addLast(e)
}
fun peek(): E? {
if (list.empty) return null
return list.getLast()
}
}
استفاده از پشته (Stack) در زبان های برنامه نویسی
میخوایم با استفاده از پشته (Stack) یک پروژه ی ماشین حساب بنویسیم، در این پروژه یک عبارت مثل 2*5+(4/2)-32 حساب کنه.
public class EvaluateExpression {
public double evaluateExpression(String expression) {
Stack<Double> operands = new Stack<>();
Stack<Character> operators = new Stack<>();
expression = expression.trim(); //remove every space in expression
expression = insertBlanks(expression);
String[] tokens = expression.split("\\s");
for (String token : tokens) {
if (!token.isEmpty()) {
if (token.charAt(0) == '+' || token.charAt(0) == '-') {
while (
!operators.isEmpty() && (operators.peek() == '*' || operators.peek() == '-' || operators.peek() == '*' || operators.peek() == '/' || operators.peek() == '^')
) {
processAnOperation(operators, operands);
}
operators.push(token.charAt(0));
} else if (token.charAt(0) == '*' || token.charAt(0) == '/' || token.charAt(0) == '^') {
while (!operators.isEmpty() && (operators.peek() == '*' || operators.peek() == '/' || operators.peek() == '^')) {
processAnOperation(operators, operands);
}
operators.push(token.charAt(0));
} else if (token.charAt(0) == '(') {
operators.push('(');
} else if (token.charAt(0) == ')') {
while (operators.peek() != '(') {
processAnOperation(operators, operands);
}
operators.pop();
} else {
operands.push(Double.parseDouble(token));
}
}
}
while (!operators.isEmpty())
processAnOperation(operators, operands);
return operands.pop();
}
private static void processAnOperation(Stack<Character> operators, Stack<Double> operands) {
char op = operators.pop();
double operand0 = operands.pop();
double operand1 = operands.pop();
switch (op) {
case '*' -> operands.push(operand1 * operand0);
case '/' -> operands.push(operand1 / operand0);
case '-' -> operands.push(operand1 - operand0);
case '+' -> operands.push(operand1 + operand0);
case '^' -> operands.push(Math.pow(operand1, operand0));
}
}
private static String insertBlanks(String s) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')')
result.append(" ").append(ch).append(" ");
else result.append(ch);
}
return result.toString();
}
}
fun evaluateExpression(expression: String): Double {
var expression = expression
val operands = Stack<Double>()
val operators = Stack<Char>()
expression = expression.trim() //remove every space in expression
expression = insertBlanks(expression)
val tokens = expression.split(Regex("\\s"))
for (token in tokens) {
if (!token.isEmpty()) {
if (token[0] == '+' || token[0] == '-') {
while (!operators.isEmpty() && (operators.peek() == '*' || operators.peek() == '-' || operators.peek() == '*' || operators.peek() == '/' || operators.peek() == '^')
) {
processAnOperation(operators, operands)
}
operators.push(token[0])
} else if (token[0] == '*' || token[0] == '/' || token[0] == '^') {
while (!operators.isEmpty() && (operators.peek() == '*' || operators.peek() == '/' || operators.peek() == '^')) {
processAnOperation(operators, operands)
}
operators.push(token[0])
} else if (token[0] == '(') {
operators.push('(')
} else if (token[0] == ')') {
while (operators.peek() != '(') {
processAnOperation(operators, operands)
}
operators.pop()
} else {
operands.push(token.toDouble())
}
}
}
while (!operators.isEmpty()) processAnOperation(operators, operands)
return operands.pop()
}
private fun processAnOperation(operators: Stack<Char>, operands: Stack<Double>) {
val op: Char = operators.pop()
val operand0: Double = operands.pop()
val operand1: Double = operands.pop()
when (op) {
'*' -> operands.push(operand1 * operand0)
'/' -> operands.push(operand1 / operand0)
'-' -> operands.push(operand1 - operand0)
'+' -> operands.push(operand1 + operand0)
'^' -> operands.push(operand1.pow(operand0))
}
}
private fun insertBlanks(s: String): String {
val result = StringBuilder()
for (i in s.indices) {
val ch = s[i]
if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')') result.append(" ")
.append(ch).append(" ")
else result.append(ch)
}
return result.toString()
}
خلاصه
- استک یک ساختار داده ی یک طرفه است.
- در پشته (Stack) آخرین عنصری که وارد میکنیم اولین عنصریه که میتونیم صدا بزنیم یا حذف کنیم.
- ساختار عناصر (داده ها) در استک به last-in-first-out معروف است.
- در پشته (Stack) به فقط به آخرین عنصری که اضافه کردیم دسترسی داریم و تا زمانی که حذف نشده به عناصر بعدی دسترسی نخواهیم داشت.
- با استفاده از آرایه ها میتونیم یک استک با اندازه ی ثابت و با استفاده از لیست پیوندی میتونیم یک استک با اندازه ی متغیر پیاده سازی کنیم.
- پیچیدگی زمان برای اضافه و حذف کردن و صدا زدن عنصر در استک O(1) است.