大话数据结构-栈

news2024/9/24 9:19:01

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 总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/382830.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

界面控件DevExpress WinForm——轻松构建类Visual Studio UI(三)

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

MyBatisPlus Study Notes

文章目录1 MyBatisPlus概述1.1 MyBatis介绍1.2 MyBatisPlus特性2 标准数据层开发2.1 MyBatisPlus的CRUD操作API2.2 分页功能接口实现2.2.1 config&#xff08;配置层&#xff09;拦截器实现2.2.2 Dao(Mapper)数据访问层&#xff08;CRUD&#xff09;操作2.2.3 Junit单元测试进行…

新版本GPU加速的tensorflow库的配置方法

本文介绍在Anaconda环境中&#xff0c;配置可以用GPU运行的Python新版tensorflow库的方法。 在上一篇文章Anaconda配置Python新版本tensorflow库&#xff08;CPU、GPU通用&#xff09;的方法&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/129285815&am…

【分布式】10张图带你彻底搞懂限流、熔断、服务降级

文章目录1 限流1.1 限流指标1.1.1 TPS1.1.2 HPS1.1.3 QPS1.2 限流方法1.2.1 流量计数器1.2.2 滑动时间窗口1.2.3 漏桶算法1.2.4 令牌桶算法1.2.5 分布式限流1.2.6 hystrix限流1.2.6.1 信号量限流1.2.6.2 线程池限流2 熔断2.1 断路器的状态2.2 需要考虑的问题2.3 使用场景3 服务…

游戏开发是个“坑”,而且是个“天坑”

本文首发于CSDN公众号 作者 | 开发游戏的老王 责编 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 各位游戏开发者大家好&#xff0c;我是开发游戏的老王&#xff0c;一名游戏开发者同时也是一名高校游戏方向的主讲教师&#xff0c;从事游戏开发及相关教…

HTTP缓存从入门到踹门

1 与缓存相关的字段Expires&#xff1a;缓存的绝对过期时间Cache-Control&#xff1a;缓存的相对过期时间Last-Modified&#xff1a;缓存上一次修改的时间&#xff08;服务端保存&#xff09;If-Modified-Since&#xff1a;缓存上一次修改的时间&#xff08;客户端保存&#xf…

第十一届蓝桥杯省赛——2解密

题目&#xff1a;【问题描述】小明设计了一种文章加密的方法&#xff1a;对于每个字母 c&#xff0c;将它变成某个另外的字符 Tc。下表给出了字符变换的规则&#xff1a;字母cTc字母cTc字母cTc字母cTcaynlAYNLbxogBXOGcmpoCMPOddquDDQUearfEARFfcssFCSSgitzGITZhkupHKUPinvwINV…

【ArcGIS Pro二次开发】(11):面要素的一键拓扑

在工作中&#xff0c;经常需要对要素进行拓扑检查。 在ArcGIS Pro中正常的工作流程是在数据库中【新建要素数据集——新建拓扑——将要素加入拓扑——添加规则——验证】&#xff0c;工作流程不算短&#xff0c;操作起来比较繁琐。 下面以一个例子演示如何在ArcGIS Pro SDK二次…

数组一次性删除多条数据

需求描述 最后提交时删除表格中的空行 实现方法 单行删除 - 并不是一次性删除 表格每行的最后设置删除按钮&#xff0c;点击时将当前行的索引传递给方法&#xff0c;splice 删除当前行。 <el-table :data"tableData" class"myTable" border>..…

爬虫实战进阶版【1】——某眼专业版实时票房接口破解

某眼专业版-实时票房接口破解 某眼票房接口:https://piaofang.maoyan.com/dashboard-ajax 前言 当我们想根据某眼的接口获取票房信息的时候,发现它的接口处的参数是加密的,如下图: 红色框框的参数都是动态变化的,且signKey明显是加密的一个参数。对于这种加密的参数,我们需要…

第14届蓝桥杯STEMA测评真题剖析-2023年2月12日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第103讲。 蓝桥杯选拔赛现已更名为STEMA&#xff0c;即STEM 能力测试&#xff0c;是蓝桥杯大赛组委会与美国普林斯顿多…

域权限维持之创建DSRM后门

DSRM&#xff08;目录服务还原模式&#xff09;&#xff0c;在初期安装域控的时候会让我们设置DSRM的管理员密码&#xff0c;这个密码是为了在后期域控发生问题时修复、还原或重建活动目录。DSRM账户实际上是administrator账户&#xff0c;并且该账户的密码在创建之后很少使用。…

Azure OpenAI 官方指南 01|GPT-3 的原理揭秘与微调技巧

Azure OpenAI 服务在微软全球 Azure 平台正式发布后&#xff0c;迅速成为众多用户最关心的服务之一。 Azure OpenAI 服务允许用户通过 REST API 访问 OpenAI 的强大语言模型&#xff0c;包括 GPT-3、Codex 和 Embeddings 模型系列。本期&#xff0c;我们将为您揭秘 Azure Open…

乌班图安装kvm并配置网络

乌班图22安装KVM 1.安装KVM sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virtinstsudo adduser id -un libvirt sudo adduser id -un kvm sudo apt install virtinst qemu-efi sudo systemctl enable --now libvirtd sudo s…

Tcl_Init error: Can‘t find a usable init.tcl in the following directories

目录 问题 解决 小结 问题 最近在研究开源波形显示软件gtkwave时,Ubuntu18.04下编译打包完成,移植到另一个电脑上运行时,出现以下错误,如图: 擦掉的部分是一些路径信息,这个错误提示意味着您的系统中缺少所需的 Tcl 初始化文件,路径下确实没有init.tcl文…

嵌入式 LVGL移植到STM32F4

目录 LVGL简介 1、特点 2、LVGL的硬件要求 3、相关网站 4、LVGL源码下载 5、LVGL移植要求 5.1 移植过程-添加源码 2、更改接口文件 3、显示实现 4、添加外部中文字体的方法 5、编译下载后有几种情况 6、调用显示 6、GUI-Guider使用 6.1 安装软件 6.2 使用…

Kakfa详解(一)

kafka使用场景 canal同步mysqlelk日志系统业务系统Topic kafka基础概念 Producer: 消息生产者&#xff0c;向kafka发送消息Consumer: 从kafka中拉取消息消费的客户端Consumer Group: 消费者组&#xff0c;消费者组是多个消费者的集合。消费者组之间互不影响&#xff0c;所有…

Wireshark+Go捕获本地TCP通信

初学计网&#xff0c;使用Wireshark观察本地端口间TCP通信过程。 目录 步骤1&#xff1a; 步骤2&#xff1a; 步骤3&#xff1a; 步骤1&#xff1a; 使用go语言搭建本地客户端与服务器TCP通信&#xff0c;测试完成后在步骤2先运行服务器&#xff0c;再运行客户端。 服务器…

C语言查漏补缺(进阶)volatile、__attribute__、void*、地址对齐、$$Super$main

最近在学习RT-Thread&#xff0c;在看其源码的时候发现了许多自己不太了解的C语言知识点&#xff0c;在此查漏补缺一下。 1. 关键字 volatile volatile是C90新增关键字&#xff0c;volatile的的中文意思是adj.易变的&#xff1b;无定性的&#xff1b;无常性的&#xff1b;可…

如何使用FarsightAD在活动目录域中检测攻击者部署的持久化机制

关于FarsightAD FarsightAD是一款功能强大的PowerShell脚本&#xff0c;该工具可以帮助广大研究人员在活动目录域遭受到渗透攻击之后&#xff0c;检测到由攻击者部署的持久化机制。 该脚本能够生成并导出各种对象及其属性的CSV/JSON文件&#xff0c;并附带从元数据副本中获取…