栈1:栈的特征和常用操作
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈1:栈的特征和常用操作相关的知识,希望对你有一定的参考价值。
从今天开始我们进入第二个专题:三种特殊的线性结构:栈,队和Hash。
栈是很多表达式、符号等运算的基础,也是递归的底层实现。理论上递归能做的题目,栈都可以,只是有些问题用栈会非常复杂。
队列的考察比较特殊,直接考察队列本身的题目不多。在面试时重点在两个方面发力,一个是java中的队列,特别是阻塞队列,这个是多线程和并发框架JUC的重要组成部分。而在手写算法中,队列主要集中在于广度遍历有关的场景,例如二叉树的层次遍历,N叉树的广度优先遍历等等。广度优先的问题我们后面再看。
Hash的应用之广也不必多说,在各类应用中也大量见到。而HashMap和支持并发的ConcurrentHashMap的原理本身也是面试的重点。我们知道Spring的核心是管理bean的容器,而这个容器本身就是ConcurrentHashMap结构,另外Dubbo等代码中也都大量使用ConcurrentHashMap。
但是比较尴尬的是,因为Hash要开辟O(n)的空间,很多用Hash写出来的算法,面试官总是会问是否有更好的算法。所以在应用中Hash是扛把子,而在很多算法面试题里,Hash只是个备胎。
还有一点就是很多问题一旦使用hash就没什么技术含量了,例如在链表部分我们介绍的如何判断链表中是否有环,如何确定两个链表是否交叉等等。在应用中我们优先采用Hash,而在算法中则应重点设计那些烧脑的方法。
我们接下来就逐步拆解这三大结构以及相关的算法题。
1.栈的定义和特征
栈本质上仍然是线性表,底层实现也是链表或者顺序表,栈与线性表的最大区别是数据的存取的操作被限制了,其插入和删除操作只允许在线性表的一端进行。一般而言,把允许操作的一端称为栈顶(Top),不可操作的一端称为栈底(Bottom),同时把插入元素的操作称为入栈(Push),删除元素的操作称为出栈(Pop)。若栈中没有任何元素,则称为空栈,栈的结构如下图:
栈的操作主要有5个:
push(E):增加一个元素E
pop();弹出元素E
peek():显示栈顶元素,但是不出栈
empty():判断栈是否为空
search():在栈中搜索元素
我们在设计自己的栈的时候,不管用数组还是链表,都要实现上面几个方法,不过search()这个用的不多,前面四个是必不可少的。
由图我们可看到,栈只能从栈顶存取元素,先进入的元素反而是后出,而栈顶永远指向栈内最顶部的元素。因此栈也称为后进先出(Last In First Out,LIFO)或先进后出(First In Last Out FILO)的线性表。栈的基本操作创建栈,判空,入栈,出栈,获取栈顶元素等,但是不支持对指定位置进行删除,插入。
2.java中的栈
java的util中就提供了栈Stack,其使用也不难,看一个例子就够了:
public class MainTest {
public static void main(String[] args) {
Stack<Integer> stack=new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println("栈顶元素为:"+stack.peek());
while(!stack.empty()){
if(stack.peek()%2==0){
System.out.println(stack.pop());
}
}
}
}
接下来,我们看一下如何自己实现,如果要自己实现栈,可以有数组,链表和LinkedList三种基本方式,如果想熟练搞定这方面的题,都要会才行。
3.基于数组实现栈
采用顺序表实现的的栈,内部以数组为基础,实现对元素的存取操作,
这里需要注意的一个点是:每次入栈之前先判断栈的容量是否够用,如果不够用就用Arrays.copyOf()
进行扩容。
基于数组设计的栈,入栈过程看这个图就行了
注意入栈之后要更新栈顶top指向要变化
出栈的过程看这张图就够了:
完整的实现代码是:
import java.util.Arrays;
/**
* 数组实现栈
*/
class Mystack<T> {
//实现栈的数组
private Object[] stack;
//数组大小
private int size;
Mystack() {
//初始容量为10
stack = new Object[10];
}
//判断是否为空
public boolean isEmpty() {
return size == 0;
}
//返回栈顶元素
public T peek() {
T t = null;
if (size > 0)
t = (T) stack[size - 1];
return t;
}
public void push(T t) {
expandCapacity(size + 1);
stack[size] = t;
size++;
}
//出栈
public T pop() {
T t = peek();
if (size > 0) {
stack[size - 1] = null;
size--;
}
return t;
}
//扩大容量
public void expandCapacity(int size) {
int len = stack.length;
if (size > len) {
size = size * 3 / 2 + 1;//每次扩大50%
stack = Arrays.copyOf(stack, size);
}
}
}
}
然后写一个测试类看看上面的方法好不好用:
public class ArrayStack {
public static void main(String[] args) {
Mystack<String> stack = new Mystack<>();
System.out.println(stack.peek());
System.out.println(stack.isEmpty());
stack.push("java");
stack.push("is");
stack.push("beautiful");
stack.push("language");
System.out.println(stack.pop());
System.out.println(stack.isEmpty());
System.out.println(stack.peek());
}
4.基于链表实现栈
链表也可以实现栈,关键点是插入和删除都在头结点进行。
具体来说,添加元素的过程如下图:
删除元素的过程如下图:
代码实现也不复杂,完整实现如下:
/**
* 链表实现栈
*/
class Mystack<T> {
//定义链表
class Node<T> {
private T t;
private Node next;
}
private Node<T> head;
//构造函数初始化头指针
Mystack() {
head = null;
}
//入栈
public void push(T t) {
if (t == null) {
throw new NullPointerException("参数不能为空");
}
if (head == null) {
head = new Node<T>();
head.t = t;
head.next = null;
} else {
Node<T> temp = head;
head = new Node<>();
head.t = t;
head.next = temp;
}
}
//出栈
public T pop() {
T t = head.t;
head = head.next;
return t;
}
//栈顶元素
public T peek() {
T t = head.t;
return t;
}
//栈空
public boolean isEmpty() {
if (head == null)
return true;
else
return false;
}
}
同样写一个测试类:
public class LinkStack {
public static void main(String[] args) {
Mystack stack = new Mystack();
System.out.println(stack.isEmpty());
stack.push("Java");
stack.push("is");
stack.push("beautiful");
System.out.println(stack.peek());
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.isEmpty());
System.out.println(stack.pop());
System.out.println(stack.isEmpty());
}
}
5.基于LinkedList实现栈
LinkedList是java做了初步封装的顺序表,因此我们使用起来更为方便。例如栈的基本操作基于LinkedList就是这样的:
push-----addFirst()
pop-------removeFirst()
peek-----getFirst()
isEmpty-isEmpty()
实现代码就是:
import java.util.LinkedList;
/**
* LinkedList实现栈
* @param <T>
*/
class ListStack<T> {
private LinkedList<T> ll = new LinkedList<>();
//入栈
public void push(T t) {
ll.addFirst(t);
}
//出栈
public T pop() {
return ll.removeFirst();
}
//栈顶元素
public T peek() {
T t = null;
//直接取元素会报异常,需要先判断是否为空
if (!ll.isEmpty())
t = ll.getFirst();
return t;
}
//栈空
public boolean isEmpty() {
return ll.isEmpty();
}
}
还是写一个测试类吧:
public class LinkedListStack {
public static void main(String[] args) {
ListStack<String> stack = new ListStack();
System.out.println(stack.isEmpty());
System.out.println(stack.peek());
stack.push("java");
stack.push("is");
stack.push("beautiful");
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack.isEmpty());
System.out.println(stack.peek());
}
}
以上是关于栈1:栈的特征和常用操作的主要内容,如果未能解决你的问题,请参考以下文章
栈是常用的一种数据结构,有 n 个元素在栈顶端一侧等待进栈,栈顶端另一侧是 出栈序列。你已经知道栈的操作有两种:push 和 pop,前者是将一个元素进栈,后 者是将栈顶元素弹出。现在要使用这两种操作