大话数据结构-栈

1 概述

  栈(Stack)是限定仅在表尾进行插入和删除操作的线性表。

  允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈,栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。

  栈的插入操作,叫作进栈,也称压栈、入栈;栈的删除操作,叫作出栈,也有的叫作弹栈。

2 栈的抽象数据类型

ADT 栈(Stack)
    Data:同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
    Operation:
        init(int maxSize):初始化操作,建立一个空栈;
        destroy():若栈存在,则销毁它;
        clear():清空栈;
        isEmpty():若栈为空,返回true,否则返回false;
        getTop():若栈存在且非空,则返回栈顶元素;
        push(DataType e):进栈;
        pop():出栈;
        length():返回栈的元素个数;
endADT

3 顺序存储结构

3.1 概述

  基于栈的特殊性,我们可以考虑使用数组来实现栈,令数组的下标为0的元素作为栈底元素,并定义一个top指针来指向栈顶元素在数组中的位置:

image.png

3.2 进栈操作

  把数据添加到top指向的下标的下一个下标,然后把top下移一位即可:

image.png

  代码实现如下:

/**
 * 进栈操作
 *
 * @param value 待进栈的值
 * @throws RuntimeException 栈满时抛出
 * @author Korbin
 * @date 2023-01-10 11:32:41
 **/
public void push(T value) {
    if (top == maxSize - 1) {
        throw new RuntimeException("fulled");
    }
    data[++top] = value;
}

3.3 出栈操作

  返回top所在的当前元素,并令top减1即可:

 /**
 * 出栈
 *
 * @return 出栈的元素
 * @throws RuntimeException 栈空时抛出异常
 * @author Korbin
 * @date 2023-01-10 11:34:49
 **/
public T pop() {
    if (top == -1) {
        throw new RuntimeException("empty stack");
    }
    return data[top--];
}

3.4 完整代码

import java.util.Arrays;

/**
 * 栈
 * <p>
 * 线性栈顶的数据为一个数组,数组的第一个元素(索引为0)为栈底
 * <p>
 * 栈是先进后出的数据结构
 *
 * @author Korbin
 * @date 2023-01-10 11:21:07
 **/
public class Stack<T> {

    /**
     * 栈内数据
     **/
    private T[] data;

    /**
     * 栈的最大长度
     **/
    private int maxSize;

    /**
     * 栈顶指针,为数组索引
     * <p>
     * 值为-1时表示空栈
     **/
    private int top = -1;

    /**
     * 清空栈
     **/
    public void clear() {
        init(maxSize);
    }

    /**
     * 获取栈顶指针
     *
     * @return 栈顶指针
     * @author Korbin
     * @date 2023-01-10 11:45:08
     **/
    public T getTop() {
        if (top == -1) {
            throw new RuntimeException("empty stack");
        }
        return data[top];
    }

    /**
     * 初始化
     *
     * @param maxSize 栈的长度
     * @author Korbin
     * @date 2023-01-10 11:38:41
     **/
    @SuppressWarnings("unchecked")
    public void init(int maxSize) {
        this.maxSize = maxSize;
        data = (T[]) new Object[maxSize];
        top = -1;
    }

    /**
     * 栈是否为空栈,空栈返回true,否则返回false
     * <p>
     * top为-1时为空栈
     **/
    public boolean isEmpty() {
        return top == -1;
    }

    /**
     * 栈的元素个数
     **/
    public int length() {
        return top + 1;
    }

    /**
     * 出栈
     *
     * @return 出栈的元素
     * @throws RuntimeException 栈空时抛出异常
     * @author Korbin
     * @date 2023-01-10 11:34:49
     **/
    public T pop() {
        if (top == -1) {
            throw new RuntimeException("empty stack");
        }
        return data[top--];
    }

    /**
     * 进栈操作
     *
     * @param value 待进栈的值
     * @throws RuntimeException 栈满时抛出
     * @author Korbin
     * @date 2023-01-10 11:32:41
     **/
    public void push(T value) {
        if (top == maxSize - 1) {
            throw new RuntimeException("fulled");
        }
        data[++top] = value;
    }

    @Override
    public String toString() {

        return "Stack{" + "data=" + Arrays.toString(data) + ", stackSize=" + maxSize + ", top=" + top + '}';
    }

}

4 两栈共享空间

4.1 概述

  顺序存储的栈使用数组存储数据,将下标为0的元素作为栈底元素,并定义了top指向栈顶元素,定义maxSize确认栈中最大可以存储的元素个数。

  这种实现方式可能造成maxSize定义较大,但实际使用的空间较小的情况,因此为了尽可能节省或者说尽可能利用上存储空间,如果有两个栈是相同的数据类型时,可以将两个栈一起放置在一个数组中,栈A以下标为0的元素为栈底,栈B以下标为maxSize-1的元素为栈底,双向进行操作,以最大利用定义好的数组空间。

image.png

  实现方式并不复杂,只是由原来的只管理一个top,变成管理两个top,只要top1和top2不“见面”,那么数组就可以一直被使用,因此,当top1 + 1 = top2时,才表示栈满了,这时,两个top“见面”了。

4.2 代码实现

import java.util.Arrays;

/**
 * 双向顺序栈
 * <p>
 * 一个数组中包含两个栈,栈1的栈顶索引为0,栈2的栈顶索引为n-1
 * <p>
 * 当栈1的栈顶索引为-1时,表示栈1为空栈
 * <p>
 * 当栈2的栈顶索引为n时,表示栈2为空栈
 *
 * @author Korbin
 * @date 2023-01-10 11:54:20
 **/
public class BidirectionalStack<T> {

    /**
     * 栈内数据
     **/
    private T[] data;

    /**
     * 栈的最大长度
     **/
    private int maxSize;

    /**
     * 栈1的栈顶指针,为数组索引
     * <p>
     * 值为-1时表示空栈
     **/
    private int top1 = -1;

    /**
     * 栈2的栈顶指针,为数组索引
     * <p>
     * 值为stackSize时表示空栈
     **/
    private int top2 = -1;

    /**
     * 清空栈
     *
     * @param stackNumber 栈的数量,只能为1或2
     * @throws RuntimeException 当栈为空或stackNumber不为1和2时抛出异常
     * @author Korbin
     * @date 2023-01-16 10:14:32
     **/
    public void clear(int stackNumber) {
        if (stackNumber == 1) {
            for (int i = 0; i <= top1; i++) {
                data[i] = null;
            }
            top1 = -1;
        } else if (stackNumber == 2) {
            for (int i = maxSize - 1; i >= top2; i--) {
                data[i] = null;
            }
            top2 = maxSize;
        } else {
            throw new RuntimeException("error stack number");
        }
    }

    /**
     * 获取栈1的栈顶指针
     *
     * @param stackNumber 栈的数量,只能为1或2
     * @return 栈顶指针
     * @throws RuntimeException 当栈为空或stackNumber不为1和2时抛出异常
     * @author Korbin
     * @date 2023-01-10 11:45:08
     **/
    public T getTop(int stackNumber) {
        if (stackNumber == 1) {
            if (top1 == -1) {
                throw new RuntimeException("empty stack");
            }
            return data[top1];
        } else if (stackNumber == 2) {
            if (top2 == maxSize) {
                throw new RuntimeException("empty stack");
            }
            return data[top2];
        } else {
            throw new RuntimeException("error stack number");
        }
    }

    /**
     * 初始化
     *
     * @param maxSize 栈的长度
     * @author Korbin
     * @date 2023-01-10 11:38:41
     **/
    @SuppressWarnings("unchecked")
    public void init(int maxSize) {
        this.maxSize = maxSize;
        this.top1 = -1;
        this.top2 = maxSize;
        data = (T[]) new Object[maxSize];
    }

    /**
     * 栈是否为空
     *
     * @param stackNumber 栈的数量,只能为1或2
     * @return 为空返回true,不为空返回false
     * @throws RuntimeException 当栈为空或stackNumber不为1和2时抛出异常
     * @author Korbin
     * @date 2023-01-16 10:16:29
     **/
    public boolean isEmpty(int stackNumber) {
        if (stackNumber == 1) {
            return top1 == -1;
        } else if (stackNumber == 2) {
            return top2 == maxSize;
        } else {
            throw new RuntimeException("error stack number");
        }
    }

    /**
     * 返回栈的元素个数
     *
     * @param stackNumber 栈的数量,只能为1或2
     * @return 栈的元素个数
     * @throws RuntimeException 当栈为空或stackNumber不为1和2时抛出异常
     * @author Korbin
     * @date 2023-01-16 10:18:19
     **/
    public int length(int stackNumber) {
        if (stackNumber == 1) {
            return top1 + 1;
        } else if (stackNumber == 2) {
            return maxSize - top2;
        } else {
            throw new RuntimeException("error stack number");
        }
    }

    /**
     * 出栈
     *
     * @param stackNumber 栈的数量,只能为1或2
     * @return 出栈的值
     * @throws RuntimeException 当栈为空或stackNumber不为1和2时抛出异常
     * @author Korbin
     * @date 2023-01-10 12:06:56
     **/
    public T pop(int stackNumber) {
        if (stackNumber == 1) {
            // 栈1出栈
            if (top1 == -1) {
                throw new RuntimeException("empty stack");
            }
            T result = data[top1];
            data[top1] = null;
            top1--;
            return result;
        } else if (stackNumber == 2) {
            // 栈2出栈
            if (top2 == maxSize) {
                throw new RuntimeException("empty stack");
            }
            T result = data[top2];
            data[top2] = null;
            top2++;
            return result;
        } else {
            throw new RuntimeException("error stack number");
        }
    }

    /**
     * 进栈
     *
     * @param value       待进栈的值
     * @param stackNumber 栈的数量,为1或2
     * @throws RuntimeException 栈满或者stackNumber不为1和2时抛出异常
     * @author Korbin
     * @date 2023-01-10 12:03:48
     **/
    public void push(T value, int stackNumber) {
        if (top1 + 1 == top2) {
            throw new RuntimeException("fulled");
        }

        if (stackNumber == 1) {
            // 栈1进栈
            data[++top1] = value;
        } else if (stackNumber == 2) {
            // 栈2进栈
            data[--top2] = value;
        } else {
            throw new RuntimeException("error stack number");
        }
    }

    @Override
    public String toString() {
        return "BidirectionalStack{" + "data=" + Arrays.toString(data) + ", stackSize=" + maxSize + ", top1=" + top1 +
                ", top2=" + top2 + '}';
    }

}

5 栈的链式存储结构

5.1 概述

  栈的链式存储结构,简称为链栈,处理方式是,把栈顶放在单链表的头部,用来代替头结点:

image.png

  栈的结点定义中,用data表示存储的元素,用next表示后继节点:

import lombok.Data;

/**
 * 链栈节点
 *
 * @author Korbin
 * @date 2023-01-10 17:12:00
 **/
@Data
public class StackNode<T> {

    /**
     * 节点值
     **/
    private T data;

    /**
     * 后继节点
     **/
    private StackNode<T> next;

}

5.2 进栈操作

  链栈中定义了两个属性,一为top,指向栈顶元素,一为count,表示的是实际元素的数量。

  因此链栈在进栈时,只需要两步操作:

  (1) 定义一个新的结点,令其的后继结点指向top结点;

  (2) 令top指向新定义的结点;

image.png

/**
 * 进栈
 *
 * @param value 待进栈的节点值
 * @author Korbin
 * @date 2023-01-10 17:31:45
 **/
public void push(T value) {

    StackNode<T> newNode = new StackNode<>();
    newNode.setData(value);
    newNode.setNext(top);

    top = newNode;

    count++;
}

5.3 出栈操作

  令top指向其后继结点即可:

/**
 * 出栈
 *
 * @return 出栈的节点
 * @throws RuntimeException 栈为空时抛出异常
 * @author Korbin
 * @date 2023-01-10 17:38:10
 **/
public StackNode<T> pop() {

    if (count == 0) {
        throw new RuntimeException("empty stack");
    }

    StackNode<T> result = top;
    top = top.getNext();
    count--;
    return result;
}

5.4 完整代码

/**
 * 链栈,即链式存储的栈
 *
 * @author Korbin
 * @date 2023-01-10 17:21:26
 **/
public class LinkStack<T> {

    /**
     * 节点数量
     **/
    private int count = 0;

    /**
     * 栈顶指针
     **/
    private StackNode<T> top = null;

    /**
     * 清空链栈
     *
     * @author Korbin
     * @date 2023-01-16 10:34:55
     **/
    public void clear() {
        init();
    }

    /**
     * 获取栈顶节点
     *
     * @return 栈顶节点
     * @author Korbin
     * @date 2023-01-10 17:34:41
     **/
    public StackNode<T> getTop() {
        return top;
    }

    /**
     * 初始化链栈
     *
     * @author Korbin
     * @date 2023-01-16 10:34:55
     **/
    public void init() {
        top = null;
        count = 0;
    }

    /**
     * 是否为空栈,是则返回true,否则返回false
     *
     * @return 空栈返回true,否则返回false
     * @author Korbin
     * @date 2023-01-16 10:35:29
     **/
    public boolean isEmpty() {
        return count == 0;
    }

    /**
     * 获取总节点数
     *
     * @return 总节点数
     * @author Korbin
     * @date 2023-01-10 17:32:14
     **/
    public int length() {
        return count;
    }

    /**
     * 出栈
     *
     * @return 出栈的节点
     * @throws RuntimeException 栈为空时抛出异常
     * @author Korbin
     * @date 2023-01-10 17:38:10
     **/
    public StackNode<T> pop() {

        if (count == 0) {
            throw new RuntimeException("empty stack");
        }

        StackNode<T> result = top;
        top = top.getNext();
        count--;
        return result;
    }

    /**
     * 进栈
     *
     * @param value 待进栈的节点值
     * @author Korbin
     * @date 2023-01-10 17:31:45
     **/
    public void push(T value) {

        StackNode<T> newNode = new StackNode<>();
        newNode.setData(value);
        newNode.setNext(top);

        top = newNode;

        count++;
    }

    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder("[");

        if (null != top) {
            StackNode<T> tmp = top;
            builder.append(tmp.getData()).append(",");
            while (null != tmp.getNext()) {
                builder.append(tmp.getNext().getData()).append(",");
                tmp = tmp.getNext();
            }
        }

        builder.append("]");
        return builder.toString();
    }

}

  BTW:用Java来描述数据结构可能不是一个好的方式——所有内存的处理都忽略了——也可能是我还学不到家。

6 栈的应用-四则运算表达式求值

6.1 中缀表示法、前缀表示法、后缀表示法

  通常我们看到的四则运算,称为中缀表示法比如:

a + b * c - (d + e)

  前缀表示法又称波兰表示法,即把运算符放在前面,操作数写在后面,前缀表示法是一种没有括号的表示法,将中缀表示法转化成前缀表示法的步骤为:

  (1) 首先按照运算符的优先级对所有的运算单位加括号;

  (2) 转前缀则将符号移动到对应括号之前;

  (3) 去掉括号;

  如上述表达式,我们先将所有运算单位加上括号,我们知道,上述表达式的运算过程应为:

  (1) 计算d + e;

  (2) 计算b * c;

  (3) 计算a + (b * c);

  (4) 计算(a + (b * c)) - (d + e);

  因此,把所有运算单元都加上括号后,表达式变为:

((a + (b * c)) - ( d + e ))

  注意,每一步都要加上括号。

  然后,我们把每个运算符,都移到到其对应的括号前面,表达式变成:

-(+(a  *(b  c))  +( d  e ))

  再把括号省略掉,得到前缀表达式:

- + a * b c + d e

  后缀表达式转换方式类似,先加括号:

((a + (b * c)) - ( d + e ))

  然后把运算符移到对应括号后面:

((a  (b  c)*)+  ( d  e )+)-

  然后去掉括号,得到结果:

a b c * + d e + -

6.2 中缀表达式转前缀表达式的代码实现

  代码实现时与手动算法不同,遵循如下过程:

  (1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2;

  (2) 从右至左扫描中缀表达式;

  (3) 遇到操作数时,将其压入S2;

  (4) 遇到运算符时,比较其与S1栈顶运算符的优先级:

    1) 如果S1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;

    2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;

    3) 否则,将S1栈顶的运算符弹出并压入到S2中,然后从S1中取出栈顶元素,再将待处理的元素与栈顶运行符比较,处理方式与第(4)点相同;

  (5) 遇到括号时:

    1) 如果是右括号“)”,则直接压入S1;

    2) 如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;

  (6) 重复步骤(2)至(5),直到表达式的最左边;

  (7) 将S1中剩余的运算符依次弹出并压入S2;

  (8) 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。

  以以下表达式为例:

a + b * c - (d + e)

  首先是两个空栈S1和S2:

image.png

  然后是倒序迭代给定的字符串,首先是“)”,右括号直接压入S1:

image.png

  然后是“e”,操作数,直接压入S2:

image.png

  然后是“+”,运算符,与S1的栈顶元素“)”比较,直接入S1栈:

image.png

  然后是“d”,操作数,直接压入S2:

image.png

  然后是“(”,依次弹出S1的栈顶元素,直到遇到“)”,并抛弃这一对括号:

image.png

  然后是“-”,与S1的栈顶元素比较,因S1的栈顶元素是空,因此直接进入S1栈:

image.png

  然后是“c”,操作数,直接压入S2:

image.png

  然后是“*”,与S的栈顶元素“-”比较,优先级较高,因此压入S1:

image.png

  然后是“b”,操作数,直接压入S2:

image.png

  然后是“+”,与S1的栈顶元素“*”比较,优先级较低,因此先将S1的栈顶元素弹出,放到S2:

image.png

  然后与S1新的栈顶元素“-”相比,优先级相同,因此直接将“+”压入S1:

image.png

  然后是“a”,操作数,直接压入S2:

image.png

  中缀表达式的所有字符都已读取完,这时,将S1的所有元素依次弹出,压入S2:

image.png

  再将S2依次读出,得到前缀表达式结果:

- + a * b c + d e

  我们用两栈共享空间的方式来实现代码:


/**
 * 判断字符串是否是数字
 *
 * @param val 待判断的字符串
 * @return 是数字则返回true,否则返回false
 * @author Korbin
 * @date 2023-01-16 16:53:26
 **/
private boolean isData(String val) {
    Pattern p = Pattern.compile("^[a-zA-Z0-9]+$");
    return p.matcher(val).find();
}


/**
 * 中缀表达式转前缀表达式
 *
 * @param infix 中缀表达式
 * @return 前缀表达式
 * @author Korbin
 * @date 2023-01-16 11:49:29
 **/
public String infix2Prefix(String[] infix) {
    // 定义一个双向栈
    // 左边栈为S1,为运算符栈
    // 右边栈为S2,为中间结果栈
    BidirectionalStack<String> stack = new BidirectionalStack<>();
    stack.init(infix.length);

    for (int i = infix.length - 1; i >= 0; i--) {
        // 从右至左迭代中缀表达式
        String curStr = infix[i];

        // 取出运行符栈的栈顶元素
        String topOperator = null;
        try {
            topOperator = stack.getTop(1);
        } catch (Exception e) {
            // do nothing
        }

        // 如果为操作数直接入中间结果栈
        if (isData(curStr)) {
            // 操作数
            stack.push(curStr, 2);
        } else if (curStr.equals("(")) {
            // 左括号

            while (null != topOperator && !topOperator.equals(")")) {
                // 弹出运算符栈的元素,并压入中间结果栈,直到遇到右括号为止
                stack.push(stack.pop(1), 2);
                try {
                    topOperator = stack.getTop(1);
                } catch (Exception e) {
                    // do nothing
                    topOperator = null;
                }
            }

            if (null != topOperator) {
                // 抛弃右括号
                stack.pop(1);
            }

        } else if (curStr.equals(")")) {
            // 右括号
            // 直接进入运算符栈
            stack.push(curStr, 1);
        } else if (curStr.equals("+") || curStr.equals("-") || curStr.equals("*") || curStr.equals("/")) {
            // 运算符

            boolean inserted = false;
            while (!inserted) {
                if (null == topOperator || topOperator.equals(")")) {
                    // 运算符栈顶为空或栈顶元素为右括号
                    // 直接入栈到运算符栈,并结束
                    stack.push(curStr, 1);
                    inserted = true;
                } else if (curStr.equals("*") || curStr.equals("/") || topOperator.equals("+") ||
                        topOperator.equals("-")) {
                    // 优先级比栈顶运算符的较高或相等
                    // 即:待比较的字符为乘或除,或者运算符栈顶元素为加或减时
                    // 直接入栈到运算符栈,并结束
                    stack.push(curStr, 1);
                    inserted = true;
                } else {
                    // 弹出运算符栈的元素,并压入中间结果栈,然后继续比较运算符栈的栈顶元素
                    stack.push(stack.pop(1), 2);
                    try {
                        topOperator = stack.getTop(1);
                    } catch (Exception e) {
                        // do nothing
                        topOperator = null;
                    }
                }
            }

        } else {
            // 其他情况,例如为空时,1直接入中间结果栈
            if (!curStr.equals(" ")) {
                stack.push(curStr, 2);
            }
        }
    }

    // 然后将运算符栈中的所有元素再压入中间结果栈
    while (!stack.isEmpty(1)) {
        stack.push(stack.pop(1), 2);
    }

    // 得到结果
    StringBuilder result = new StringBuilder();
    while (!stack.isEmpty(2)) {
        result.append(stack.pop(2));
    }

    return result.toString();

}

6.3 中缀表达式转后缀表达式的代码实现

  (1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2;

  (2) 从左至右扫描中缀表达式;

  (3) 遇到操作数时,将其压入S2;

  (4) 遇到运算符时,比较其与S1栈顶运算符的优先级:

    1) 如果S1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;

    2) 否则,若优先级比栈顶运算符的高,也将运算符压入S1(注意转换为前缀表达式时是优先级较高或相同,而这里则不包括相同的情况);

    3) 否则,将S1栈顶的运算符弹出并压入到S2中,然后取出S1中新的栈顶元素,再次转到(4)进行比较;

  (5) 遇到括号时:

    1) 如果是左括号“(”,则直接压入S1;

    2) 如果是右括号“)”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号为止,此时将这一对括号丢弃;

  (6) 重复步骤(2)至(5),直到表达式的最右边;

  (7) 将S1中剩余的运算符依次弹出并压入S2;

  (8) 依次弹出S2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式(转换为前缀表达式时不用逆序)。

&msp; 然后以以下表达式为例:

a + b * c - (d + e)

  先定义两个空的栈:

image.png

  然后从头开始迭代,先是“a”,操作数,直接压入S2:

image.png

  然后是“+”,运算符,与S1的栈顶元素比较,因S1的栈顶元素为空,因此直接压入S1:

image.png

  然后是“b”,操作数,直接压入S2:

image.png

  然后是“*”,运算符,与S1的栈顶元素“+”比较,优先级较高,直接压入S1:

image.png

  然后是“c”,操作数,直接压入S2:

image.png

  然后是“-”,操作数,与S1的栈顶元素“”比较,发现优先级低,因此将“”从S1弹出,压入S2:

image.png

  继续与S1新的栈顶元素“+”比较,优先级相同,将“+”从S1弹出,压入S2:

image.png

  继续与S1新的栈顶元素比较,发现S1为空,因此直接将“-”压入S1:

image.png

  然后是“(”,直接压入S1:

image.png

  然后是“d”,操作数,直接压入S2:

image.png

  然后是“+”,运算符,与S1的栈顶元素“(”比较,因此是左括号,因此直接压入S1:

image.png

  然后是“e”,操作数,直接压入S2:

image.png

  然后是“)”,依次弹出S1的元素,压入S2,直到遇到左括号,然后把这一对括号去掉:

image.png

  迭代完毕,把S1剩余的元素依次压入S2:

image.png

  从S2中依次读取出结果:

- + e d + * c b a

  reverse,得到后缀表达式结果:

a b c * + d e + -

  代码如下所示:

/**
 * 中缀表达式转后缀表达式
 *
 * @param infix 后缀表达式
 * @return 前缀表达式
 * @author Korbin
 * @date 2023-01-16 11:49:29
 **/
public String infix2Suffix(String[] infix) {
    // 定义一个双向栈
    // 左边栈为S1,为运算符栈
    // 右边栈为S2,为中间结果栈
    BidirectionalStack<String> stack = new BidirectionalStack<>();
    stack.init(infix.length);

    for (int i = 0; i <= infix.length - 1; i++) {
        // 从左至右迭代中缀表达式
        String curStr = infix[i];

        // 取出运行符栈的栈顶元素
        String topOperator = null;
        try {
            topOperator = stack.getTop(1);
        } catch (Exception e) {
            // do nothing
        }

        // 如果为操作数直接入中间结果栈
        if (isData(curStr)) {
            // 操作数
            stack.push(curStr, 2);
        } else if (curStr.equals("(")) {
            // 左括号
            // 直接进入运算符栈
            stack.push(curStr, 1);
        } else if (curStr.equals(")")) {
            // 右括号
            while (null != topOperator && !topOperator.equals("(")) {
                // 弹出运算符栈的元素,并压入中间结果栈,直到遇到左括号为止
                stack.push(stack.pop(1), 2);
                try {
                    topOperator = stack.getTop(1);
                } catch (Exception e) {
                    // do nothing
                    topOperator = null;
                }
            }

            if (null != topOperator) {
                // 抛弃右括号
                stack.pop(1);
            }
        } else if (curStr.equals("+") || curStr.equals("-") || curStr.equals("*") || curStr.equals("/")) {
            // 运算符

            boolean inserted = false;
            while (!inserted) {
                if (null == topOperator || topOperator.equals("(")) {
                    // 运算符栈顶为空或栈顶元素为左括号
                    // 直接入栈到运算符栈,并结束
                    stack.push(curStr, 1);
                    inserted = true;
                } else if ((curStr.equals("*") || curStr.equals("/")) &&
                        (topOperator.equals("+") || topOperator.equals("-"))) {
                    // 优先级比栈顶运算符的较高
                    // 即:运算符栈顶元素为加或减时
                    // 直接入栈到运算符栈,并结束
                    stack.push(curStr, 1);
                    inserted = true;
                } else {
                    // 弹出运算符栈的元素,并压入中间结果栈,然后继续比较运算符栈的栈顶元素
                    stack.push(stack.pop(1), 2);
                    try {
                        topOperator = stack.getTop(1);
                    } catch (Exception e) {
                        // do nothing
                        topOperator = null;
                    }
                }
            }

        } else {
            // 其他情况,例如为空时,1直接入中间结果栈
            if (!curStr.equals(" ")) {
                stack.push(curStr, 2);
            }
        }

    }
    // 然后将运算符栈中的所有元素再压入中间结果栈
    while (!stack.isEmpty(1)) {
        stack.push(stack.pop(1), 2);
    }

    // 得到结果
    StringBuilder result = new StringBuilder();
    while (!stack.isEmpty(2)) {
        result.append(stack.pop(2));
    }

    return result.reverse().toString();
}

6.4 使用前缀表达式计算四则运算

  现有四则运算表达式:

9 + (3 - 1) * 3 + 10 / 2

  转化成前缀表达式后是:

+ + 9 * - 3 1 3 / 10 2

  先初始化一个空栈,然后从右向左开始处理,规则是:如果是数字,则进栈,如果是符号,就将处于栈顶的两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果,注意,栈顶元素是“x”,栈的第二个元素是“y”,则当是运算符“zz”时,运算方式应是“x zz y”。

  如上述前缀表达式,先是“2”,直接进栈:

image.png

  然后是“10”,直接进栈:

image.png

  然后是“/”,取出栈顶的两个元素,然后进行“10 / 2”处理,得到5,然后入栈:

image.png

  然后是“3”,直接进栈:

image.png

  然后是“1”,直接进栈:

image.png

  然后是“3”,直接进栈:

image.png

  然后是“-”,取出栈顶的两个元素,进行“3 - 1”处理,得到2,然后入栈:

image.png

  然后是“*”,取出栈顶的两个元素,进行“2 * 3”处理,得到6,然后入栈:

image.png

  然后是“9”,直接进栈:

image.png

  然后是“+”,取出栈顶的两个元素,进行“9 + 6”处理,得到15,然后入栈:

image.png

  然后是“+”,取出栈顶的两个元素,进行“15 + 5”处理,得到20,然后入栈:

image.png

  于是得到最终结果20。

  代码如下所示:

/**
 * 使用前缀表达式计算
 *
 * @param prefixExpression 前缀表达式
 * @return 计算结果
 * @author Korbin
 * @date 2023-01-16 18:17:01
 **/
public double prefixCalc(String[] prefixExpression) {

    LinkStack<String> stack = new LinkStack<>();

    int length = prefixExpression.length;
    for (int i = length - 1; i >= 0; i--) {
        String curStr = prefixExpression[i];
        if (isData(curStr)) {
            // 如果是操作数,则直接入栈
            stack.push(curStr);

        } else {

            // 否则,使用第二个元素 运算符 栈顶元素得出结果后,再将结果入栈
            double firstData = Double.parseDouble(stack.pop().getData());
            double secondData = Double.parseDouble(stack.pop().getData());

            double value = 0;
            switch (curStr) {
                case "+":
                    value = firstData + secondData;
                    break;
                case "-":
                    value = firstData - secondData;
                    break;
                case "*":
                    value = firstData * secondData;
                    break;
                case "/":
                    value = firstData / secondData;
                    break;
            }

            stack.push(String.valueOf(value));
        }
    }

    return Double.parseDouble(stack.pop().getData());
}

6.5 使用后缀表达式计算四则运算

  现有四则运算表达式:

9 + (3 - 1) * 3 + 10 / 2

  转化成后缀表达式后是:

9 3 1 - 3 * + 10 2 / +

  先初始化一个空栈,然后从左向右开始处理,规则是:如果是数字,则进栈,如果是符号,就将处于栈顶的两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果,注意,栈顶元素是“x”,栈的第二个元素是“y”,则当是运算符“zz”时,运算方式应是“y zz x”。

  如上述后缀表达式,先是“9”,直接进栈:

image.png

  然后是“3”,直接进栈:

image.png

  然后是“1”,直接进栈:

image.png

  然后是“-”,取出栈顶的两个值,进行“3 - 1”处理,得到2,再进栈:

image.png

  然后是“3”,直接进栈:

image.png

  然后是“*”,取出栈顶的两个值,进行“2 * 3”处理,得到6,再进栈:

image.png

  然后是“+”,取出栈顶的两个值,进行“9 + 6”处理,得到15,再进栈:

image.png

  然后是“10”,直接进栈:

image.png

  然后是“2”,直接进栈:

image.png

  然后是“/”,取出栈顶的两个值,进行“10 / 2”处理,得到5,再进栈:

image.png

  然后是“+”,取出栈顶的两个值,进行“15 + 5”处理,得到20,再进栈:

image.png

  这样就得到了最终结果。

  代码如下所示:

/**
 * 使用后缀表达式计算
 *
 * @param suffixExpression 后缀表达式
 * @return 计算结果
 * @author Korbin
 * @date 2023-01-16 18:17:01
 **/
public double suffixCalc(String[] suffixExpression) {

    LinkStack<String> stack = new LinkStack<>();

    int length = suffixExpression.length;
    for (int i = 0; i <= length - 1; i++) {
        String curStr = suffixExpression[i];
        if (isData(curStr)) {
            // 如果是操作数,则直接入栈
            stack.push(curStr);

        } else {

            // 否则,使用栈顶元素 运算符 第二个元素得出结果后,再将结果入栈
            double secondData = Double.parseDouble(stack.pop().getData());
            double firstData = Double.parseDouble(stack.pop().getData());

            double value = 0;
            switch (curStr) {
                case "+":
                    value = firstData + secondData;
                    break;
                case "-":
                    value = firstData - secondData;
                    break;
                case "*":
                    value = firstData * secondData;
                    break;
                case "/":
                    value = firstData / secondData;
                    break;
            }

            stack.push(String.valueOf(value));
        }
    }

    return Double.parseDouble(stack.pop().getData());
}

6.6 总结

  可见,无论在中缀转前缀或中缀转后缀时,或者在使用前缀或后缀进行计算时,都可以使用栈来进行辅助计算。

  注:本文为程 杰老师《大话数据结构》的读书笔记,其中一些示例和代码是笔者阅读后自行编制的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值