一、栈的介绍
栈是操作受限的线性表,只能在一端进行插入和删除操作,这一端被称为栈顶。
栈是 “先进后出、后进先出” 的数据结构。
虽然被称为操作受限的线性表,但是栈有很多应用,比如函数调用栈,表达式求值,括号匹配等等。
栈的操作最主要的就是入栈与出栈;增加元素,在栈中被称为入栈;删除元素,在栈中被称为出栈。
二、栈的实现
栈的底层由两种数据结构实现。
用数组实现栈被称为顺序栈。
用链表实现栈被称为链式栈。
(一)、顺序栈
1、入栈
入栈操作示意图:
顺序栈的底层数据结构是数组,也就是说,顺序栈的入栈实际的操作就是在数组末尾增加元素。时间复杂度为 O(1)
/**
* 入栈
* @param e
*/
public void push(int e) {
// 判断栈是否已满,栈满无法入栈
if (size == capacity) {
throw new RuntimeException("栈满,无法进行入栈操作");
}
// 入栈操作
data[size] = e;
size++;
}
data 为实际存储数据的数组,size 是数组内的元素个数,capacity 是数组的实际大小。
在上面这种情况下,如果数组已满,就不可以继续插入元素。
但是可以使用动态数组来实现可以动态扩容的顺序栈。
2、出栈
出栈示意图:
顺序栈的出栈操作实际上就是删除数组的最后一个数据元素。时间复杂度为 O(1)。
/**
* 出栈
* @return
*/
public int pop() {
// 判断栈是否为空,栈空则无法出栈
if (size == 0) {
throw new RuntimeException("栈空,无法进行出栈操作");
}
int result = data[size - 1];
size--;
return result;
}
size 是数组实际元素数量。
(二)、链式栈
1、入栈
链式栈的入栈操作其实就是在链表末尾添加元素,时间复杂度为 O(1)。
/**
* 入栈
*
* @param e
*/
public void push(int e) {
Node newNode = new Node(e);
if (size == 0) {
head = newNode;
} else {
Node t = head;
// 找到单链表末尾节点
while (t.next != null) {
t = t.next;
}
// 将新节点添加到链表末尾
t.next = newNode;
newNode.next = null;
}
size++;
}
size 为链表中的节点个数,head 存储链表头结点的内存地址。
2、出栈
链式栈的出栈操作其实就是删除链表的尾结点。时间复杂度为 O(1)。
/**
* 出栈
*
* @return
*/
public int pop() {
if (size == 0) {
throw new RuntimeException("栈空,无法执行出栈操作");
}
int result = -1;
if (size == 1) {
result = head.data;
head = null;
} else {
Node t = head;
// 找到单链表的尾结点的前一个结点
while (t.next.next != null) {
t = t.next;
}
// 存储出栈元素的值
result = t.next.data;
// 将链表尾结点删除
t.next = null;
}
size--;
return result;
}