1 基本概念
-
栈是一种操作受限的线性表,只允许从一端插入和删除数据。
-
栈有两种存储方式,即线性存储(数组)和链式存储(链表)。
-
栈的一个最重要的特征就是栈的插入和删除只能在栈顶进行,所以每次删除的元素都是最后进栈的元素,故栈也被称为后进先出(LIFO)表。
-
每个栈都有一个栈顶指针,它初始值为-1,且总是指向最后一个入栈的元素。
-
栈有两种处理方式,即进栈(push)和出栈(pop),因为在进栈只需要移动一个变量存储空间,所以它的时间复杂度为O(1),但是对于出栈分两种情况,栈未满时,时间复杂度也为O(1),但是当栈满时,需要重新分配内存,并移动栈内所有数据,所以此时的时间复杂度为O(n)。
下图是进栈(push)和出栈(pop)的过程示意图:
2 数组模拟栈
数组模拟栈的基本示意图:
数组模拟栈的思路分析:
- 定义一个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 链表模拟栈
-
链栈即用链表实现栈存储结构。
-
链栈的实现思路同顺序栈类似,顺序栈是将数顺序表(数组)的一端作为栈底,另一端为栈顶。链栈也如此,通常我们将链表的头部作为栈顶,尾部作为栈底,如下图所示:
- 将链表头部作为栈顶,可以避免在数据 "入栈" 和 "出栈" 操作时做大量遍历链表的耗时操作。
- 链表的头部作为栈顶,意味着:
- 在实现数据"入栈"操作时,需要将数据从链表的头部插入;
- 在实现数据"出栈"操作时,需要删除链表头部的首元节点;
因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表。
链表模拟栈的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
韩顺平数据结构