跳到主要内容

04、数据结构与算法 - 实战:栈

概念

 

特点:

1、 栈底(bottom)位置不变,栈顶(top)位置改变
2、 元素先进(push)后出(pop)

  • 应用:

1、 算术计算;
2、 子程序的调用;
3、 递归调用;
4、 表达式转换(中缀、后缀表达式的转换);
5、 二叉树的遍历;
6、 图的深层优先搜索算法;

数组 - 模拟栈

代码实现

class Stack<T> {
   
     
	
	Object[] stack;
	int capacity;
	int top;
	
	public Stack(int capacity ) {
   
     
		this.capacity = capacity;
		stack = new Object[capacity];
		top = -1;
	}
	
	// 判断是否为满
	public boolean isFull() {
   
     
		if(top == capacity-1) {
   
     
			return true;
		}
		return false;
	}
	
	//判断是否为空
	public boolean isEmpty() {
   
     
		
		if(top == -1 ) {
   
     
			return true;
		}
		return false;
		
	}
	
	// 将元素放入栈中
	public void push(T num) {
   
     
		
		if(isFull()) {
   
     
			System.out.println("栈已经放满元素了");
			return;
		}
		
		// top记录的是上次压入元素的位置,故压入元素前需要改变top的值
		stack[++top] = num;
		
	}
	
	// 将栈顶的元素取出来,并移动top指针的位置
	@SuppressWarnings("unchecked")
	public T pop() {
   
     
		
		if(isEmpty()) {
   
     
			throw new RuntimeException("栈已经没有元素了");
		}
		
		// 正因为top标记的是上次压入元素的位置,故取元素先不改变top的值
		return (T)stack[top--];
	}
    

	
}

测试代码

public static void main(String[] args) {
   
     
    Stack<String> s = new Stack<String>(5);
    s.push("1发多少");
    s.push("2认为");
    s.push("3不VC");
    s.push("4今年");
    s.push("5iu一");

    while(!s.isEmpty()) {
   
     
        System.out.println(s.pop());
    }

}

运行结果
 

双向链表 - 模拟栈

代码实现

class LinkedStack<T> {
   
     

	// 表示栈顶、当为null时,说明无元素 -- 表示当前栈顶的元素
	Node<T> top = null;

	// 判断栈是否由元素
	public boolean isEmpty() {
   
     
		if (top == null) {
   
     
			return true;
		}
		return false;
	}

	// 压栈
	public void push(T date) {
   
     
		// 将压入的元素封装成为节点
		Node<T> newNode = new Node<T>(date);

		// 当栈中无元素时压栈
		if (top == null) {
   
     
			top = newNode;
			// 当栈中有元素时压栈
		} else {
   
     
			top.next = newNode;
			newNode.pre = top;
			top = newNode;
		}

	}
	
	// 从栈顶取出一个元素
	public T pop() {
   
     

		if (isEmpty()) {
   
     
			throw new RuntimeException("栈中已经无元素了");
		} else {
   
     
			T result = top.date;
			top = top.pre;
			return result;
		}
	}
	
	// 显示当前栈顶元素是什么
	public T peek() {
   
     
		return top.date;
	}

	@SuppressWarnings("hiding")
	private class Node<T> {
   
     
		T date;

		Node<T> next;
		Node<T> pre;

		Node() {
   
     
			date = null;
			next = null;
			pre = null;
		}

		Node(T date) {
   
     
			this.date = date;
			next = null;
			pre = null;
		}

	}
}

测试代码

public static void main(String[] args) {
   
     
    LinkedStack<String> ls = new LinkedStack<String>();
    ls.push("1发多少");
    ls.push("2认为");
    ls.push("3不VC");
    ls.push("4今年");
    ls.push("5iu一");

    while(!ls.isEmpty()) {
   
     
        System.out.println(ls.pop());
    }
}

测试结果
 

应用:模拟算术表达式计算 - 利用上面的双向链表栈

由于代码过长,我分段进行讲解,都是成员函数,如需测式,只需要复制即可以了。

图片解释

 

 

 

 

 

代码实现

① 定义一个OpationalExpression类#

class OperationalExpression {
   
     
	
	// 需要计算的算术表达式
	private String expression;
	
	// 两个栈用来进行计算
	LinkedStack<String> numStack = new LinkedStack<String>();
	LinkedStack<Character> operStack = new LinkedStack<Character>();
	
	// 1. 存储解析expression字符串所有数字、以及操作符
	String[] temp_num;
	Character[] temp_oper;

	// 传入的算术表达式需把所有的空格清除掉
	OperationalExpression(String expression) {
   
     
		this.expression = expression.replaceAll(" ", "");
	}
	
	// 显示最终去空格后的算术表达式是怎么样
	public String showExpression() {
   
     
		return expression;
	}
	
	// 定义操作符优先级
	public int getPriority(Character operation) {
   
     
		if (operation == '*' || operation == '/') {
   
     
			return 1;
		} else {
   
     
			return 0;
		}
	}	
}

② isArithmeticExpression() – 校验算术表达式#

	// 判断传入的算术表达式是否符合计算要求 -- 并且进行操作数数组temp_num、以及操作符数组temp_oper的初始化操作
public boolean isArithmeticExpression() {
   
     
  
        // 因为不能一开始知道表达式中有几个算术操作符,故用容器进行动态存储操作符,注意这里的函数变量名跟成员变量名不冲突
        List<Character> temp_oper = new ArrayList<Character>();

        // 将字符串转成字符数组进行验证是否每个字符符合要求
        char[] chars = expression.toCharArray();
        int index = 0;    // 记录字符串指针移动的位置
        int operCount = 0;  // 记录操作数的个数
        while (true) {
   
     
            if (index == chars.length) {
   
     
                break;
            }
            char selectedChar = chars[index];

            if ((selectedChar >= '0' && selectedChar <= '9') || selectedChar == '.') {
   
     
                index++;
                continue;
            } else if ((selectedChar >= '*' && selectedChar <= '+') || (selectedChar >= '-' && selectedChar <= '/')) {
   
     
                ++operCount;
                index++;
                temp_oper.add(selectedChar);
                continue;
            }
            return false;   // 如果存在不是 0-9、小数点、+-*/ 则直接返回 false, 说明不符合算术表达式要求 
        }

        // 成员变量temp_oper初始化、赋值操作
        this.temp_oper = temp_oper.toArray(new Character[operCount]);

        // 用来查找 算术字符串中的 数字
        StringTokenizer st = new StringTokenizer(expression, "+-*/", false);

        // 如果 数字的个数 = 操作符+1  则返回 true,否则该算术表达式不符合要求
        if (st.countTokens() == operCount + 1) {
   
     
            temp_num = new String[st.countTokens()];
            int tokenIndex = 0;
            while (st.hasMoreTokens()) {
   
     
                temp_num[tokenIndex] = st.nextToken();
                ++tokenIndex;
            }
            return true;
        }

        return false;
}

③ sResult() – 核心代码 - 计算数栈弹出两个元素、操作符栈弹出一个元素进行得到计算结果#

// 数栈弹出两元素、操作符栈弹出一个操作符中进行简单运算
private double sResult(String operand1, String operand2, Character operation) {
   
     

    // 将操作数转成double类型,进行数字运算
    Double num1 = Double.valueOf(operand1);
    Double num2 = Double.valueOf(operand2);

    // 核心代码 - 判断操作符栈是否为空,为空说明正在进行的是最后一次运算,不需要进行操作符转换
    if (!operStack.isEmpty()) {
   
     

        // 获取操作符栈顶的元素 -- 进行下列的字符转换
        Character operPeek = operStack.peek();
        System.out.println("栈顶操作符:" + operPeek);
        System.out.println("实参操作符:" + operation);

        if (operPeek == '-' && operation == '+') {
   
     
            System.out.println("你好");
            operation = '-';

        }
        else if (operPeek == '-' && operation == '-') {
   
     
            operation = '+';
        }

        else if (operPeek == '/' && operation == '/') {
   
     
            operation = '*';
            num2 = 1 / num2;
        }

        else if (operPeek == '/' && operation == '*') {
   
     
            num2 = 1 / num2;
        }
    }
    System.out.println("真正计算时的操作符:" + operation);

    // 根据操作符,进行弹出的两个数字与操作符的运算
    switch (operation) {
   
     

        case '+': {
   
     
            System.out.printf("%.1f %s %.1f = %.1f\n\n", num1, operation +"" , num2 , num1+num2);
            return num1 + num2;
        }
        case '-': {
   
     
            System.out.printf("%.1f %s %.1f = %.1f\n\n", num1, operation +"" , num2 , num1-num2);
            return num1 - num2;
        }
        case '*': {
   
     
            System.out.printf("%.1f %s %.1f = %.1f\n\n", num1, operation +"" , num2 , num1*num2);
            return num1 * num2;
        }
        case '/': {
   
     
            System.out.printf("%.1f %s %.1f = %.1f\n\n", num1, operation +"" , num2 , num1/num2);
            return num1 / num2;
        }
        default: {
   
     
            throw new RuntimeException("不支持处理" + operation + "这种操作符的表达式");
        }
    }

}

④ result() – 获取算术表达式的最终结果#

public double result() {
   
     

    // 1. 判断表达式是否符合计算要求
    if (!isArithmeticExpression()) {
   
     
        throw new RuntimeException("表达式不符合要求");
    }

    // 2. 打印最终解析表达式后的结果
    System.out.println(Arrays.toString(temp_num));
    System.out.println(Arrays.toString(temp_oper) + "\n");

    // 数字数组、操作符数组指针
    int num_index = 0;
    int oper_index = 0;
    // 3. 计算双栈、压栈、弹栈计算操作
    while (true) {
   
     

        // 3.1 先从数字数组压入一个数字
        if (num_index != temp_num.length) {
   
     
            numStack.push(temp_num[num_index++]);
        }

        // 3.2 再从操作符数组压入一个操作符
        if (oper_index != temp_oper.length) {
   
     

            // 3.2.1 如果操作符栈无元素,直接压栈
            if (operStack.isEmpty()) {
   
     
                operStack.push(temp_oper[oper_index++]);
                // 3.2.2 如果待入栈的操作符优先级小于栈顶的操作符,则需要计算一次,弹出操作符,弹出两个数,将结果压入数栈,再将待入栈的操作符入栈
            } else {
   
     
                Character nextOperation = temp_oper[oper_index++];
                if (getPriority(nextOperation) <= getPriority(operStack.peek())) {
   
     
                    String num2 = numStack.pop();
                    String num1 = numStack.pop();
                    Double result = sResult(num1, num2, operStack.pop());
                    numStack.push(result.toString());
                    operStack.push(nextOperation);
                } else {
   
     
                    operStack.push(nextOperation);
                }
            }
        }

        // 3.2.3 如果数字数组、操作符数组的元素已经全部都压入双栈,则停止循环
        if (num_index == temp_num.length && oper_index == temp_oper.length) {
   
     
            break;
        }

    }

    // 4. 计算结果,弹出一个操作符,就弹出两个数,直到操作符栈没有元素可弹为止
    while (!operStack.isEmpty()) {
   
     
        String num2 = numStack.pop();
        String num1 = numStack.pop();
        Character operation = operStack.pop();
        Double result = sResult(num1, num2, operation);
        numStack.push(result.toString());
    }

    // 5. 计算结果已经压入数栈中,直接弹出即可
    return Double.valueOf(numStack.pop());
}

测试代码

public static void main( String[] args ) {
   
     {
   
     
        
        //OperationalExpression os = new OperationalExpression("5-7*3-5");
        //OperationalExpression os = new OperationalExpression("7+2*6-4");
        OperationalExpression os = new OperationalExpression("5-7*3-5/5+5");
        double a = os.result();

        System.out.printf("结果:%.1f" , a);
}

代码运行