第四章 栈

1 基本概念

  • 栈是一种操作受限的线性表,只允许从一端插入和删除数据。

  • 栈有两种存储方式,即线性存储(数组)和链式存储(链表)

  • 栈的一个最重要的特征就是栈的插入和删除只能在栈顶进行,所以每次删除的元素都是最后进栈的元素,故栈也被称为后进先出(LIFO)表

  • 每个栈都有一个栈顶指针,它初始值为-1,且总是指向最后一个入栈的元素。

  • 栈有两种处理方式,即进栈(push)和出栈(pop),因为在进栈只需要移动一个变量存储空间,所以它的时间复杂度为O(1),但是对于出栈分两种情况,栈未满时,时间复杂度也为O(1),但是当栈满时,需要重新分配内存,并移动栈内所有数据,所以此时的时间复杂度为O(n)。

    下图是进栈(push)和出栈(pop)的过程示意图:

    image-20200904134722629

2 数组模拟栈

数组模拟栈的基本示意图:

image-20200904142535450

数组模拟栈的思路分析:

  • 定义一个top来表示栈顶,初始化为-1;
  • 入栈的操作,当有数据加入到栈时,top++; stack[top] = data;
  • 出栈的操作,int value = stack[top]; top--;return value

数组模拟栈的Java实现

package com.victor.stack;
import java.util.Scanner;


public class ArrayStackDemo {

	public static void main(String[] args) {
		ArrayStack as = new ArrayStack(4);
		String key = ""; //接收用户输入
		Scanner scanner = new Scanner(System.in);
		boolean loop = true;
		//输出一个菜单栏
		while(loop){
			System.out.println("show: 打印栈");
			System.out.println("push: 入栈");
			System.out.println("pop: 出栈");
			System.out.println("peek: 查看栈顶");
			System.out.println("exit: 退出程序");
			key = scanner.next();
			switch (key) {
			case "show":
				try {
					as.show(); 
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case "push": //入栈
				try {
					System.out.println("请输入一个整数");
					int value = scanner.nextInt();
					as.push(value);  //最好判断一下value是不是整数
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case "pop": //出栈
				try {
					int value = as.pop();
					System.out.printf("出栈的整数为%d
", value);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage()); //输出pop()方法中定义好的异常信息
				}
				break;
			case "peek": //查看栈顶
				try {
					int value = as.peek();
					System.out.printf("栈顶的整数为%d
", value);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case "exit": //退出
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出");
	}
}


//数组栈类
class ArrayStack {
	private int maxSize; //栈的最大容量
	private int[] stack; //数组,用来模拟栈
	private int top = -1; //栈顶指针
	
	//构造方法
	public ArrayStack(int maxSize) {
		this.maxSize = maxSize;
		stack = new int[this.maxSize];
	}
	
	//栈满
	public boolean isFull() {
		return top == this.maxSize - 1;
	}
	
	//栈空
	public boolean isEmpty() {
		return top == -1;
	}
	
	//入栈
	public void push(int value) {
		if (isFull()) {
			throw new RuntimeException("栈满,不能再入栈了");
		}
		stack[++top] = value;
	}
	
	//出栈
	public int pop() {
		if (isEmpty()) {
			throw new RuntimeException("栈空,不能再出栈了");
		}
		int value = stack[top--];
		return value;
	}
	
	//查看栈顶元素
	public int peek() {
		if (isEmpty()) {
			throw new RuntimeException("栈空,你懂的!");
		}
		return stack[top];
	}
	
	//打印栈
	public void show() {
		if (isEmpty()) {
			throw new RuntimeException("栈空,你懂的!");
		}
		for (int i = top; i >= 0; i--) {
			System.out.printf("stack[%d]=%d
",i,stack[i]);
		}
	}
}

3 链表模拟栈

  • 链栈即用链表实现栈存储结构。

  • 链栈的实现思路同顺序栈类似,顺序栈是将数顺序表(数组)的一端作为栈底,另一端为栈顶。链栈也如此,通常我们将链表的头部作为栈顶,尾部作为栈底,如下图所示:

image-20200905091924014

  • 将链表头部作为栈顶,可以避免在数据 "入栈" 和 "出栈" 操作时做大量遍历链表的耗时操作
  • 链表的头部作为栈顶,意味着:
    • 在实现数据"入栈"操作时,需要将数据从链表的头部插入;
    • 在实现数据"出栈"操作时,需要删除链表头部的首元节点;

因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表。

链表模拟栈的java实现

package com.victor.stack;

import java.util.Scanner;

public class LinkStackDemo {

	public static void main(String[] args) {
		LinkStack ls = new LinkStack();
		String key = ""; //接收用户输入
		Scanner scanner = new Scanner(System.in);
		boolean loop = true;
		//输出一个菜单栏
		while(loop){
			System.out.println("show: 打印栈");
			System.out.println("push: 入栈");
			System.out.println("pop: 出栈");
			System.out.println("peek: 查看栈顶");
			System.out.println("exit: 退出程序");
			key = scanner.next();
			switch (key) {
			case "show": //打印栈
				try {
					ls.show(); 
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case "push": //入栈
				try {
					System.out.println("请输入一个整数");
					int value = scanner.nextInt();
					ls.push(value);  //最好判断一下value是不是整数
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case "pop": //出栈
				try {
					int value = ls.pop();
					System.out.printf("出栈的整数为%d
", value);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage()); //输出pop()方法中定义好的异常信息
				}
				break;
			case "peek": //查看栈顶
				try {
					int value = ls.peek();
					System.out.printf("栈顶的整数为%d
", value);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case "length":
				System.out.printf("栈长度为%d
", ls.getLength());
				break;
			case "exit": //退出
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出");
	}
}


//定义结点类
class ListNode{
	private int data;
	private ListNode next = null;
	
	//构造方法
	public ListNode(int data) {
		this.data = data;
	}
	
	//返回data值
	public int getData() {
		return this.data;
	}

	//设置data值
	public void setData(int data) {
		this.data = data;
	}

	//返回下一个结点地址
	public ListNode getNext() {
		return this.next;
	}

	//设置下一个结点地址
	public void setNext(ListNode next) {
		this.next = next;
	}

	//重写toString方法
	@Override
	public String toString() {
		return "ListNode [data=" + data + "]";
	}
}


class LinkStack{
	private ListNode stack; //链表头结点,也是栈顶指针

	//构造方法
	public LinkStack() {
		stack = new ListNode(-1);
	}
	
	//栈空
	public boolean isEmpty() {
		return stack.getNext() == null;
	}
	
	//入栈,使用头插法插入结点
	public void push(int value) {
		ListNode node = new ListNode(value);  //新建结点
		ListNode curr = stack.getNext();
		stack.setNext(node);
		node.setNext(curr);
	}
	
	//出栈,从链表头部删除结点
	public int pop() {
		if (isEmpty()) {
			throw new RuntimeException("栈空,不能再出栈了");
		}
		ListNode curr = stack.getNext();
		int value = curr.getData();
		stack.setNext(curr.getNext());
		return value;
	}
	
	//查看栈顶元素
	public int peek() {
		if (isEmpty()) {
			throw new RuntimeException("栈空,你懂的!");
		}
		return stack.getNext().getData();
	}
	
	//打印栈
	public void show() {
		if (isEmpty()) {
			throw new RuntimeException("栈空,你懂的!");
		}
		ListNode curr = stack.getNext();
		while(curr != null) {
			System.out.println(curr.getData());
			curr = curr.getNext();
		}
	}
	
	//求链栈长度
	public int getLength() {
		int length = 0;
		ListNode curr = stack.getNext();
		while(curr != null) {
			length++;
			curr = curr.getNext();
		}
		return length;
	}
}

reference

数据结构 之 栈【图文详解】https://blog.csdn.net/chiang2018/article/details/82731108

链栈基本操作(入栈和出栈)C语言详解http://data.biancheng.net/view/171.html

韩顺平数据结构

原文地址:https://www.cnblogs.com/victorxiao/p/13617169.html