(超详细)动手编写 — 栈、队列 ( Java实现 )
前言
栈
概念
什么是栈?
**栈 **:是一种特殊的线性表,只能在一端进行操作
入栈:往栈中添加元素的操作,一般叫做push
出栈:从栈中移除元素的操作,一般叫做pop,出栈(弹出栈顶元素)
注意:这里说的"栈"与内存中的"栈空间"是两个不同的概念
栈的结构
相比于数组和链表而言,栈同样是存储相同类型数据的线性数据结构,只不过栈的受限性比较大,比如说:栈只有一端是开放的(栈顶),所有的数据操作都是在这一端进行的,基于这个特性,有了所谓的"后进先出(Last In First Out, LIFO)"的特点,其他 3 面是封闭的,所以栈除了栈顶元素,栈中的其他元素都是未知的,栈同时也做不到随机访问。
图示栈结构:
后进先出:
栈的设计
看到前面的栈结构图,是不是很熟悉,事实上,栈除了三面封闭的特性,其他的是和之前写过的线性数据结构一致的,所以栈的内部实现可以直接利用以前学过的数据结构实现,动态数组DynamicArray
,链表LinkedList
都是可以的,没有读过前面的编写动态数组DynamicArray
,链表LinkedList
的文章的可以先去看看,动手编写—动态数组(Java实现) 以及 动手编写-链表(Java实现)
但是我们编写的Stack
栈类,并不是直接去继承这些类,因为这样子会暴露动态数组DynamicArray
,链表LinkedList
的一些原有方法,例如随机访问,随机插入,删除等等,这样都会使得栈失去特性。采用组合模式的方式能够解决这一点,画一下类图关系:
栈的接口设计
1、属性:
private List<E> list;
—— 利用基于List接口的线性表实现类设计栈
2、接口方法:
int size();
—— 查看当前栈元素的数量boolean isEmpty();
—— 判断栈是否为空public void push(E element);
—— 入栈,添加元素public E pop();
—— 出栈,删除尾部元素public E top();
—— 添获取栈顶元素void clear();
—— 清除栈元素
完成设计后,是具体的方法编码实现,因为是利用动态数组DynamicArray
,链表LinkedList
实现的栈,调用的都是封装好的方法,这里就不细讲了
编码实现
public class Stack<E> extends DynamicArray<E>{
//利用动态数组实现栈
private List<E> list = new DynamicArray<>();
//利用链表实现栈
//private List<E> list = new DynamicArray<>();
/**
* 查看栈元素数量
* @return
*/
public int size() {
return list.size();
}
/**
* 判断栈是否为空
* @return
*/
public boolean isEmpty() {
return list.isEmpty();
}
/**
* 入栈,添加元素
* @param element
*/
public void push(E element){
list.add(element);
}
/**
* 出栈,删除尾部元素
*/
public E pop(){
return list.remove(list.size() - 1);
}
/**
* 获取栈顶元素
* @return
*/
public E top(){
return list.get(list.size() - 1);
}
/**
* 清空栈元素
*/
public void clear() {
list.clear();
}
}
小结
栈的应用
1、双栈实现浏览器的前进和后退
2、软件的撤销(Undo)、恢复(Redo)功能
队列
概念
什么是队列?
队列:与前面栈不同的一点是,栈只能在栈顶一端操作元素,而队列能在首尾两端进行操作,队列同样是一种特殊的线性表
入队:只能从队尾(rear)添加元素,一般叫做enQueue
出队:只能从队头(front)移除元素,一般叫做deQueue
队列的结构
相比于数组、链表及栈而言,队列同样是存储相同类型数据的线性数据结构,只不过队列的受限性比栈小一点,但比数组、链表大,比如说:队列只能在队尾一端添加数据,队头移除元素,基于这个特性,有了所谓的"先进先出的原则,First In First Out,FIFO"的特点,其他 2 面在结构设计上是封闭的,所以队列除了队头元素,队列中的其他元素都是未知的,当然队尾元素也是可见的,但是我们一般只在队尾进行元素添加操作,所以也不会开放这个方法,队列同时也做不到随机访问。
图示队列结构:
队列的设计
队列和数组、链表、以及栈都是线性表结构,所以我们没有必要去做一些重复的操作,利用之前写好的动态数组DynamicArray
,链表LinkedList
都是可以实现的,同样利用栈也是可以实现队列的,但是这里我们是用双向链表Both_LinkedList
实现。
在前面动手编写-链表(Java实现)一文讲到,双向链表的头结点与尾结点有first
与last
指针指向,这对于队列在队头、队尾操作元素是十分方便的,当然是用动态数组或者单向链表也是可以的,只是数组在队头删除元素会使得后面的元素结点往前移动,而单向链表在队尾添加元素时,指针head
需要遍历到尾部结点,这两者都会造成复杂度的增加,所以选择双向链表更好
同样的,但是我们编写的Queue
队列并不直接接去继承这些类,依旧采用组合的方式实现,画一下类图关系
队列的接口设计
1、属性:
private List<E> list;
—— 利用基于List接口的线性表实现类设计队列
2、接口方法:
int size();
—— 查看当前队列元素的数量boolean isEmpty();
—— 判断队列是否为空public void enQueue(E element);
—— 入队,添加元素public E deQueue();
—— 出队,删除头部元素public E front();
—— 添获取队头元素void clear();
—— 清除队列元素
完成设计后,是具体的方法编码实现,因为是利用双向链表Both_LinkedList
实现的队列,调用的都是封装好的方法,这里不细讲
编码实现
双向链表实现队列:
public class Queue<E> {
//利用双向链表封装好的方法实现队列
private List<E> list = new Both_LinkedList<>();
/**
* 获取队列元素数量
* @return
*/
public int size() {
return list.size();
}
/**
* 判断当前队列是否为空
* @return
*/
public boolean isEmpty() {
return list.isEmpty();
}
/**
* 入队,从队尾添加元素
* @param element
*/
public void enQueue(E element) {
list.add(element);
}
/**
* 出队,从队头移除元素
* @return
*/
public E deQueue() {
return list.remove(0);
}
/**
* 获取队头元素
* @return
*/
public E front() {
return list.get(0);
}
/**
* 清空队列元素
*/
public void clear() {
list.clear();
}
}
双栈实现队列:
public class QueueByStack<E> {
//定义两个栈,inStack用于队尾入队,outStack用于队头出队
private Stack<E> inStack,outStack;
//使用构造函数初始化
public QueueByStack() {
this.inStack = new Stack<>();
this.outStack = new Stack<>();
}
/**
* 获取队列元素数量
* @return
*/
public int size() {
return inStack.size() + outStack.size();
}
/**
* 判断当前队列是否为空
* @return
*/
public boolean isEmpty() {
return inStack.isEmpty() && outStack.isEmpty();
}
/**
* 入队,从队尾添加元素
* @param element
*/
public void enQueue(E element) {
inStack.push(element);
}
/**
* 出队,从队头添加元素
* @return
*/
public E deQueue() {
checkOutStack();
return outStack.pop();
}
/**
* 获取队头元素
* @return
*/
public E front() {
checkOutStack();
return outStack.top();
}
/**
* 清空栈元素
*/
public void clear() {
inStack.clear();
outStack.clear();
}
/**
* 检查outStack是否为空,如果不为空,等着出队
* 如果为空,且inStack不为空,将inStack中的
* 元素出栈,入栈到outStack,然后准备出队
*/
private void checkOutStack() {
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
}
双端队列
概念
双端队列:是能在头尾两端添加、删除的队列
结构图示:
设计
双端队列Deque
与队列Queue
在实现关系上没有区别,同样是基于双向链表Both_LinkedList
,使用组合模式实现的
双向队列的接口设计
1、属性:
private List<E> list;
—— 利用基于List接口的线性表实现类设计队列
2、接口方法:
int size();
—— 查看当前队列元素的数量boolean isEmpty();
—— 判断队列是否为空public void enQueueRear(E element);
—— 入队,从队尾入队public E deQueueRear();
—— 出队,从队尾出队public void enQueueFront(E element);
—— 入队,从队头入队public E enQueueFront();
—— 出队,从队头出队public E front();
—— 添获取队头元素public E rear();
—— 添获取队尾元素void clear();
—— 清除队列元素
编码
public class Deque<E> {
//利用双向链表封装好的方法实现队列
private List<E> list = new Both_LinkedList<>();
/**
* 获取队列元素数量
* @return
*/
public int size() {
return list.size();
}
/**
* 判断当前队列是否为空
* @return
*/
public boolean isEmpty() {
return list.isEmpty();
}
/**
* 入队,从队尾入队
* @param element
*/
public void enQueueRear(E element) {
list.add(element);
}
/**
* 出队,从队尾出队
* @return
*/
public E deQueueRear() {
return list.remove(list.size() - 1);
}
/**
* 入队,从队头入队
* @param element
*/
public void enQueueFront(E element) {
list.add(0, element);
}
/**
* 出队,从对头出队
* @return
*/
public E deQueueFront() {
return list.remove(0);
}
/**
* 获取队头元素
* @return
*/
public E front() {
return list.get(0);
}
/**
* 获取队尾元素
* @return
*/
public E rear() {
return list.get(list.size() - 1);
}
/**
* 清空队列元素
*/
public void clear() {
list.clear();
}
}
循环队列
循环队列
概念:
循环队列:用数组实现并且优化之后的队列
图示结构:
设计:
循环队列又叫环形队列,是基于Java
数组实现的,使用front
指针指向的位置是队头,设计上,删除元素后不会像数组一样,挪动元素往前覆盖,而是将值置空,front
往后移动,以这样的机制删除元素,删除后的位置,当front
指针后边的位置满了,新元素就可以填补刚刚删除的空位,起到环形的作用
循环接口设计
1、属性:
private int front;
—— 循环队列队头指针private int size;
—— 队列元素数量private E[] elements;
—— 使用顺序结构数组存储private static final int DEFAULT_CAPACITY = 10;
—— 数组的默认初始化值
2、接口方法:
int size();
—— 查看当前队列元素的数量boolean isEmpty();
—— 判断队列是否为空public void enQueue(E element);
—— 入队,从队尾入队public E deQueue();
—— 出队,删除头部元素public E front();
—— 添获取队头元素void clear();
—— 清除队列元素private void ensureCapacity(int capacity)
—— 保证要有capacity的容量,不足则扩容private int index(int index);
—— 索引映射函数,返回真实数组下标
1、出队操作
2、入队操作
3、再入队
4、注意点:
(1) 入队
(2)入队
(3)出队
(4)扩容
编码:
public class CircleQueue<E> {
//数组的默认初始化值
private static final int DEFAULT_CAPACITY = 10;
//循环队列队头指针
private int front;
//队列元素数量
private int size;
//使用顺序结构数组存储
private E[] elements;
/**
* 构造函数初始化数组
*/
public CircleQueue() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
/**
* 获取队列元素的数量
* @return
*/
public int size(){
return size;
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 入队,从队尾添加元素
* @param element
*/
public void enQueue(E element) {
ensureCapacity(size + 1);
//elements[(front + size) % elements.length] = element;
//调用封装函数
elements[index(size)] = element;
size++;
}
/**
* 出队,从队头移除元素
* @return
*/
public E deQueue() {
E element = elements[front];
elements[front] = null;
//front = (front + 1) % elements.length;
//调用封装函数
front = index(1);
size--;
return element;
}
/**
* 获取队头元素
* @return
*/
public E front(){
return elements[front];
}
/**
* 清空队列元素
*/
public void clear() {
for (int i = 0; i < size; i++) {
//elements[(i + front) % elements.length] = null;
//调用封装函数
elements[index(i)] = null;
}
front = 0;
size = 0;
}
/**
* 保证要有capacity的容量,不足则扩容
* @param capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity) return;
// 新容量为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
//newElements[i] = elements[(i + front) % elements.length];
//调用封装函数
newElements[i] = elements[index(i)];
}
elements = newElements;
// 重置front
front = 0;
}
/**
* 索引映射函数,返回真实数组下标
* @param index
* @return
*/
private int index(int index){
return (front + index) % elements.length;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("capcacity=").append(elements.length)
.append(" size=").append(size)
.append(" front=").append(front)
.append(", [");
for (int i = 0; i < elements.length; i++) {
if (i != 0) {
string.append(", ");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
}
循环双端队列
概念:
循环双端队列:可以进行两端添加、删除操作的循环队
图示结构:
事实上,在结构上,与循环队列是一样的,没有必要设置一个last
指针指向队尾,因为我们采用的是数组这种顺序存储结构,实际上,last = (font + size - 1) % array.length
,只是我们在方法上对其功能进行了扩展而已
循环接口设计
1、属性:
private int front;
—— 循环队列队头指针private int size;
—— 队列元素数量private E[] elements;
—— 使用顺序结构数组存储private static final int DEFAULT_CAPACITY = 10;
—— 数组的默认初始化值
2、接口方法:
int size();
—— 查看当前队列元素的数量boolean isEmpty();
—— 判断队列是否为空public void enQueueRear(E element);
—— 入队,从队尾入队public E deQueueRear();
—— 出队,从队尾出队public void enQueueFront(E element);
—— 入队,从队头入队public E enQueueFront();
—— 出队,从队头出队public E front();
—— 添获取队头元素public E rear();
—— 添获取队尾元素void clear();
—— 清除队列元素private void ensureCapacity(int capacity)
—— 保证要有capacity的容量,不足则扩容private int index(int index);
—— 索引映射函数,返回真实数组下标
编码实现
上面也说到了,在结构上,与循环队列是一样的,所以大多数的方法是一样了,只是对其功能进行了增强,调整了部分方法逻辑
方法变动:
(1) 新增public void enQueueFront(E element);
—— 入队,从队头入队
/**
* 入队,从队头入队
* @param element
*/
public void enQueueFront(E element) {
//front指向当前节点前一位置
front = index(-1);
//假设虚拟索引,以front指向的位置为0,则向队头添加元素时往-1添加
elements[front] = element;
size++;
}
(2) 新增public E deQueueRear();
—— 出队,从队尾出队
/**
* 出队,从队尾出队
* @return
*/
public E deQueueRear() {
//找到尾部元素的真实索引
int last = index(size - 1);
E element = elements[last];
elements[last] = null;
size--;
return element;
}
(3) 新增public E rear();
—— 添获取队尾元素
/**
* 获取队尾元素
* @return
*/
public E rear() {
return elements[index(size - 1)];
}
(4) 变动private int index(int index);
—— 索引映射函数,返回真实数组下标
/**
* 索引映射函数,返回真实数组下标
* @param index
* @return
*/
private int index(int index){
index += front;
//但真实index为0时,往队头添加元素,传入 -1,小于0
if (index < 0){
index += elements.length;
}
return index % elements.length;
}
声明
个人能力有限,有不正确的地方,还请指正
文章为原创,欢迎转载,注明出处即可
本文的代码已上传github
,欢迎star
(超详细)动手编写 — 栈、队列 ( Java实现 )的更多相关文章
- 超详细动手搭建一个Vuepress站点及开启PWA与自动部署
超详细动手搭建一个Vuepress站点及开启PWA与自动部署 五一之前就想写一篇关于Vuepress的文章,结果朋友结婚就不了了之了. 记得最后一定要看注意事项! Vuepress介绍 官网:http ...
- (超详细)动手编写-链表(Java实现)
目录 前言 概念 链表的设计 完整代码 List接口 抽象父类设计 链表-LinkedList 虚拟头结点 概念 结构设计 方法变动 双向链表 概念 双向链表设计 方法变动 循环链表 单向循环链表 双 ...
- 顺序栈,链栈,队列java实现
顺序栈 /** * 顺序栈 * */ public class SqStack { //栈的大小 private int maxSize; //栈顶指针 private int top; privat ...
- java 多线程超详细总结——阿里大牛熬夜整理
引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个 ...
- J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP
J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP 前言 搜狐畅游笔试题中有一道问答题涉及到回答谈谈对Spring IOC与AOP的理解.特将相关内容进行整理. ...
- Java集合框架总结—超详细-适合面试
Java集合框架总结—超详细-适合面试 一.精简: A.概念汇总 1.Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口, ...
- 超详细的Java面试题总结(四 )之JavaWeb基础知识总结
系列文章请查看: 超详细的Java面试题总结(一)之Java基础知识篇 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java ...
- Java多线程学习(吐血超详细总结)
Java多线程学习(吐血超详细总结) 林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 写在前面的话:此文只能说是java多线程的一个入门,其实 ...
- Java中的三大特性 - 超详细篇
前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的三大特性 - 超详细篇>,希望对大家有帮助,谢谢 这一节的内容可能有点多,大家可以选择性的来看 简介 Java的三大特性:封装.继 ...
随机推荐
- C#LeetCode刷题之#100-相同的树(Same Tree)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4066 访问. 给定两个二叉树,编写一个函数来检验它们是否相同. ...
- next()与nextLine()的区别
abc def ghij kl mno pqr st uvw xyz 你用next(),第一次取的是abc,第二次取的是def,第三次取的是ghij 你用nextLine(),第一次取的是abc de ...
- sql server 查询表字段的说明备注信息
SELECT 表名 = case when a.colorder= then d.name else '' end, 表说明 = case when a.colorder= then isnull(f ...
- Android ActivityResumeTrigger: not whiteListed
在点击返回按钮的时候报错: ActivityResumeTrigger: not whiteListed 合作者写的返回操作是: findViewById(R.id.lin_back).setOnTo ...
- linux驱动之内核多线程(四)
本文摘自 http://www.cnblogs.com/zhuyp1015/archive/2012/06/13/2548494.html 自己创建的内核线程,当把模块加载到内核之后,可以通过:ps ...
- 什么情况下适合用UDP协议,什么情况下适合用TCP协议?
总的来说 TCP协议提供可靠的服务, UDP协议提供高效率的服务. 高可靠性的TCP服务提供面向连接的服务,主要用于一次传输大量报文的情形, 如文件传输,远程登录等: 高效率的UDP协议提供无连接的数 ...
- chromium 源码下载地址
下载链接:https://gsdview.appspot.com/chromium-browser-official/
- lvm常用指令
1.物理卷命令一般维护命令:#pvscan //在系统的所有磁盘中搜索已存在的物理卷#pvdisplay 物理卷全路径名称 //用于显示指定物理卷的属性.#pvdata 物理卷全路径名称 //用于显示 ...
- 获取元素top值,屏幕滚动到当前元素
var top = $(this).offset().top; $('html , body').animate({scrollTop: top-100},10);
- 算法-搜索(5)m路搜索树
动态m路搜索树即系统运行时可以动态调整保持较高搜索效率的最多m路的搜索树.以3路搜索树为例说明其关键码排序关系: const int MaxValue=; template <class T ...