什么是栈(Stack)

栈是一种遵循特定操作顺序的线性数据结构,遵循的顺序是先进后出(FILO:First In Last Out)或者后进先出(LIFO:Last In First Out)
比如:
生活中,厨房里的一摞盘子,你使用的时候会拿最上面的一个,最下面的那个最后使用。这就是FILO。当你想用第二个盘子时,先要拿起第一个,再拿出第二个,然后把第一个放到最上面。
栈的示意图大致如下:
 
 

实现和操作概述

栈的主要操作有以下5种

Object push(Object element) : 向栈顶添加一个元素
Object pop() : 移除栈顶的元素并返回
Object peek() : 返回栈顶元素,但不删除
boolean empty() : 判断栈是否为空,即栈顶是否为空
int search(Object element) :查找element是否存在,存在则返回位置(栈顶为1),不存在则返回-1。

栈的实现

可以通过数组或链表都可以实现栈,下面都会详细说明。
链表实现是自己写的一个demo。
数组实现以java.util.Stack代码做说明。java中的java.util.Stack类实现是通过数组的。
最后举了一个常见的使用栈实现功能的例子:符号匹配的问题

疑问

对比数组和链表的实现后,个人觉得用链表实现的更好,但JDK种使用的是数组实现。
这个需要进一步了解,或者比较各个操作在运行时间及内存上的变化来分析。//TODO
链表实现在链表首部操作,数组实现在有效数组(数组中有效元素的部分,非数组最大容量)的末尾操作,pop、peek、empty、search操作感觉类似(时间、内存消耗)。
下面做了个简单表格:
链表实现的劣势:需额外信息存储引用,内存浪费;push时需要新建节点。
数组实现的劣势:数组大小不足时,在push时需要扩容。扩容即需要重新分配更大空间并复制数据。
 
 
数组
链表
内存浪费
无浪费
有浪费:需存如额外引用信息
动态
非动态:大小无法运行时随意变动
动态的:可以随意增加或缩小
push操作
当数组大小超过时,需要扩容O(n)。
数组大小足够时,直接push完成 O(1)
直接链表首部插入O(1). 但需新建节点
 
 

单链表实现

下面是简易的demo。
通过单链表实现栈很容易实现,push()和pop()直接通过对链表首部的操作即可,时间复杂度都是O(1)。
下面demo很容理解,可以看看注释。需要了解 链表
public class StackTest<E> {
public static void main(String[] args) {
StackTest<Integer> stackTest = new StackTest<>();
for (int i = 4; i > 0; i--) {
System.out.println("push:" + stackTest.push(Integer.valueOf(i)).intValue());
}
System.out.println("peek:" + stackTest.peek());
System.out.println("pop:" + stackTest.pop());
System.out.println("isEmpty:" + stackTest.isEmpty());
for (int i = 4; i > 0; i--) {
System.out.println("search " + i + ":" + stackTest.search(Integer.valueOf(i)));
}
} //栈顶定义
StackNode<E> top; //节点定义:
static class StackNode<E> {
E data;
StackNode<E> next; StackNode(E data, StackNode<E> next) {
this.data = data;
this.next = next;
}
} //向栈顶push一个元素,即向链表首部添加元素
public E push(E data) {
top = new StackNode<E>(data, top);
return top.data;
} //返回栈顶的值。即链表首部节点的值。
public E peek() {
if (isEmpty())
throw new RuntimeException("fail,stack is null!");
return top.data;
} //从栈顶pop一个元素,即返回栈顶的值 并删除链表第一个节点。
public E pop() {
E preTopData = peek();
top = top.next;
return preTopData;
} //判空
public boolean isEmpty() {
return top == null;
} //查找数据为data的节点位置,栈顶为1.没找到返回-1.
public int search(E data) {
int position = 1;
StackNode<E> currNode = top; while (currNode != null && !currNode.data.equals(data)) {
position++;
currNode = currNode.next;
}
if (currNode == null)
position=-1;
return position;
}
}
push()和pop()操作后,效果为:
打印log为:
push:4
push:3
push:2
push:1
peek:1
pop:1
isEmpty:false
search 4:3
search 3:2
search 2:1
search 1:-1
 
 

栈的数组实现

如上所说,栈的数组实现方式以java.util.Stack类的代码作为说明。
Stack类是继承了Vector类,两个类方法都很多,截取了相关的代码。
主要操作过程:定义一个数组(elementData)存放栈的数据;定义一个变量(elementCount)表示有效元素的个数或范围,也就是栈中的元素;主要操作在有效元素部分的尾部,当push时超过数组大小,数组需要扩容。
大致示意图如下:

push()操作

Stack<E>类:
public E push(E item) {
addElement(item);
return item;
}
Vector<E>类:
默认构造时,数组大小初始化为10。 push()操作即向数组最后一个有效元素位置后填入push的数据。当数组大小不足时,需要扩容。具体看下列注释。
//数组变量定义
protected Object[] elementData;
//有效元素个数,在栈中即表示栈的个数
protected int elementCount;
//当数组溢出时,扩容 增加的大小。
protected int capacityIncrement;
//3种构造方式,默认构造方式的 数组大小初始化为10.
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
} public Vector(int initialCapacity) {
this(initialCapacity, 0);
} public Vector() {
this(10);
} //增加元素
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}

数组扩容

这种扩容方式,如果有相关需求可以作为参考。
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
 

peek()操作

先看peek()是,由于pop()会调用到。
Stack类:
栈有效个数为elementCount(也是数组有效元素部分)。返回数组第elementCount个元素即可,下标为elementCount-1。
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}

Vector类:

public synchronized int size() {
return elementCount;
} public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return elementData(index);
} @SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

pop()操作

通过peek获取栈顶元素并作为返回值。再删除栈顶元素。
部分代码上面已有,不列出。
Stack类:
删除数组中有效元素的最后一个,即下标为elementCount-1
public synchronized E pop() {
E obj;
int len = size(); obj = peek();
removeElementAt(len - 1); return obj;
}
Vector类:
通用方法,数组中删除某个元素,之后的元素要前移。
实际对于栈,传入的index=elementCount-1。 所以j=0。不需要移动元素,是删除的最后一个元素。
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}

search()操作

查找到后,返回位置。没找到即返回-1。
这个位置相对栈顶,栈顶(1)即数组最后一个有效元素(下标为 size-1)。如果是数组[i]是查找的元素,那么相对栈顶位置即((size-1)- i )+1 = size - i。
Stack类中
public synchronized int search(Object o) {
int i = lastIndexOf(o); if (i >= 0) {
return size() - i;
}
return -1;
}
Vector类中:
public synchronized int lastIndexOf(Object o) {
return lastIndexOf(o, elementCount-1);
} public synchronized int lastIndexOf(Object o, int index) {
if (index >= elementCount)
throw new IndexOutOfBoundsException(index + " >= "+ elementCount); if (o == null) {
for (int i = index; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = index; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

empty()操作

public boolean empty() {
return size() == 0;
}

栈的使用

符号匹配问题

这里只考虑3对符号,"{}[]()"。匹配规则如代码中,如"{([])}”是符合的,"{([)]}"是不符合的。
思路很简单:符合的情况就是第一个右符合与前面最近的一个左符号是匹配的。
匹配到左符号,压入堆栈;匹配到右符号,与栈顶比较,匹配即符合 pop出栈顶元素;当所有匹配完,栈为空即符合的。
实现代码如下:
public class StackTest<E> {

    public static void main(String[] args) {
System.out.println(symbolMatch("{for(int i=0;i<10;i++)}"));
System.out.println(symbolMatch("[5(3*2)+(2+2)]*(2+0)"));
System.out.println(symbolMatch("([5(3*2)+(2+2))]*(2+0)"));
} public static boolean symbolMatch(String expression) {
final char CHAR_NULL = ' ';
if (expression == null || expression.equals(""))
throw new RuntimeException("expression is nothing or null"); //StackTest<Character> stack = new StackTest<Character>();
Stack<Character> stack = new Stack<Character>();
char[] exps = expression.toCharArray();
for (int i = 0; i < exps.length; i++) {
char matchRight = CHAR_NULL;
switch (exps[i]) {
case '(':
case '[':
case '{':
stack.push(Character.valueOf(exps[i]));
break; case ')':
matchRight = '(';
break;
case ']':
matchRight = '[';
break;
case '}':
matchRight = '{';
break;
}
if(matchRight == CHAR_NULL)
continue;
if (stack.isEmpty())
return false;
if (stack.peek().charValue() == matchRight)
stack.pop();
}
if (stack.isEmpty())
return true;
return false;
}
}
输出结果:
true
true
false
 
 
 
 
 
 
 

数据结构之栈(Stack)的更多相关文章

  1. Python与数据结构[1] -> 栈/Stack[0] -> 链表栈与数组栈的 Python 实现

    栈 / Stack 目录 链表栈 数组栈 栈是一种基本的线性数据结构(先入后出FILO),在 C 语言中有链表和数组两种实现方式,下面用 Python 对这两种栈进行实现. 1 链表栈 链表栈是以单链 ...

  2. [ACM训练] 算法初级 之 数据结构 之 栈stack+队列queue (基础+进阶+POJ 1338+2442+1442)

    再次面对像栈和队列这样的相当基础的数据结构的学习,应该从多个方面,多维度去学习. 首先,这两个数据结构都是比较常用的,在标准库中都有对应的结构能够直接使用,所以第一个阶段应该是先学习直接来使用,下一个 ...

  3. 数据结构11: 栈(Stack)的概念和应用及C语言实现

    栈,线性表的一种特殊的存储结构.与学习过的线性表的不同之处在于栈只能从表的固定一端对数据进行插入和删除操作,另一端是封死的. 图1 栈结构示意图 由于栈只有一边开口存取数据,称开口的那一端为“栈顶”, ...

  4. Python与数据结构[1] -> 栈/Stack[1] -> 中缀表达式与后缀表达式的转换和计算

    中缀表达式与后缀表达式的转换和计算 目录 中缀表达式转换为后缀表达式 后缀表达式的计算 1 中缀表达式转换为后缀表达式 中缀表达式转换为后缀表达式的实现方式为: 依次获取中缀表达式的元素, 若元素为操 ...

  5. 线性数据结构之栈——Stack

    Linear data structures linear structures can be thought of as having two ends, whose items are order ...

  6. C# 数据结构 栈 Stack

    栈和队列是非常重要的两种数据结构,栈和队列也是线性结构,线性表.栈和队列这三种数据结构的数据元素和元素的逻辑关系也相同 差别在于:线性表的操作不受限制,栈和队列操作受限制(遵循一定的原则),因此栈和队 ...

  7. 【Java数据结构学习笔记之二】Java数据结构与算法之栈(Stack)实现

      本篇是java数据结构与算法的第2篇,从本篇开始我们将来了解栈的设计与实现,以下是本篇的相关知识点: 栈的抽象数据类型 顺序栈的设计与实现 链式栈的设计与实现 栈的应用 栈的抽象数据类型   栈是 ...

  8. 数据结构之栈(stack)

    1,栈的定义 栈:先进后出的数据结构,如下图所示,先进去的数据在底部,最后取出,后进去的数据在顶部,最先被取出. 栈常用操作: s=Stack() 创建栈 s.push(item) 将数据item放在 ...

  9. 数据结构与算法:栈(Stack)的实现

    栈在程序设计当中是一个十分常见的数据结构,它就相当于一个瓶子,可以往里面装入各种元素,最先装进这个瓶子里的元素,要把后装进这个瓶子里的全部元素拿出来完之后才能够把他给拿出来.假设这个瓶子在桌上平放,左 ...

随机推荐

  1. Puppeteer笔记(七):Puppeteer切换浏览器TAB页

    一.Puppeteer切换浏览器TAB页 1.browser.pages() 二.上手实例Demo 功能测试:打开www.ly.com首页,定位搜索"苏州",获取新打开页面上的搜索 ...

  2. Git基本操作命令合集

    平时自己敲敲代码,使用Git命令也渐渐多了起来.使用起来的确很方便,今天来分享下Git基本概念和本地代码提交到github上的过程,很简单的,多操作几次就会了. Git定义 Git 是一个开源的分布式 ...

  3. 【一致性检验指标】Kappa(cappa)系数

    1 定义 百度百科的定义: 它是通过把所有地表真实分类中的像元总数(N)乘以混淆矩阵对角线(Xkk)的和,再减去某一类地表真实像元总数与被误分成该类像元总数之积对所有类别求和的结果,再除以总像元数的平 ...

  4. Spring 注入内部 Beans

    注入内部 Beans inner beans 是在其他 bean 的范围内定义的 bean. 下面是一个基于setter注入的内部 bean 进行配置的配置文件 Beans.xml 文件: <? ...

  5. Linux 番茄时钟 定时 取消 快捷方式

    shell 脚本 clock.sh #!/bin/bash if [ $1 == 0 ] then at -d `atq | awk -v ORS=" " '{a[NR]=$1} ...

  6. Kubernetes基本概念与架构

    Kubernetes,面向云原生应用的新“云平台” Kubernetes:以google Brog为原型 Kubernetes的成长历程: l  2014年,Kubernetes正式由google开源 ...

  7. nodejs模块介绍与使用

    1.内置模块 http fs path url os等等 (无需安装直接require引入即可) 2.第三方模块: express mysql ejs等等 (npm包管理工具下载require引入) ...

  8. 02.Django-模板

    模板 1. 简介 模板由HTML代码和逻辑控制代码构成 同一个模板,可以有多个上下文,就可以通过穿件模板对象来渲染多个上下文 创建一个模板就可以多次调用render()方法来渲染上下文 Django模 ...

  9. CSS 风车(花瓣)旋转动画圆角

    这是一个综合的案例,用到了transition(动画,动作在单位时间内完成),transform(旋转),border-radius(圆角),absolute(定位),linear-gradient( ...

  10. Spring Cloud Ribbon 客户端负载均衡

    Ribbon客户端组件提供一系列完善的配置选项,比如连接超时.重试.重试算法等,内置可插拔.可定制的负载均衡组件.下面是用到的一些负载均衡策略: 简单轮询负载均衡 加权轮询负载均衡 区域感知轮询负载均 ...