数据结构(二)栈和队列
一、概述
栈和队列是数组和链表的高级封装的数据结构。
栈和队列的重点在其结构特性,栈和队列的底层不论是数组实现还是链表实现都不重要,因为同属于线性存储结构,可以通过数组实现一个栈结构,也可以使用链表实现一个栈结构,同样队列也是如此。
二、实现
2.1 栈
2.1.1栈结构的特点
- 栈的结构特性是:元素先进后出(First In Last Out,简称FILO)
- 我们将元素放入栈结构中的操作称之为元素入栈
- 将元素从栈结构中取出的操作称之为元素出栈
- 将最先进入栈结构中的元素称之为栈底元素,这个方向同时称之为栈底
- 将最后进入栈结构中的元素称之为栈顶元素,这个方法同时称之为栈顶
2.1.2 Java中的栈结构:Stack
在Java中存在一个栈结构的实现类,这个类是Stack类;
Stack类是Vector类的子类,从集成的角度来说,Stack类是List接口的实现类,同样属于Collection接口的实现类之一;
从这个角度来说,Stack类同样具有List接口下其他实现类类似的功能,能够有序可重复的保存元素;
但是除了这些特性之外,Stack类还提供了一些额外的方法,能够从数据结构的角度体现出栈结构的特性。
2.1.3 案例
1、.数组逆序
import java.util.Arrays;
import java.util.Stack;
public class ArrayReverseByStack {
/**
* 使用栈结构实现数组元素逆序
* @param array 参数数组
*/
public void reverse(int[] array) {
//[1]创建一个栈结构,用来逆序数组中的元素
Stack<Integer> reverseStack = new Stack<>();
//[2]将数组中全部元素入栈
for(int i = 0; i < array.length; i++) {
reverseStack.push(array[i]);
}
//[3]将栈结构中的所有元素出栈,出栈过程就是讲数组中元素逆序的过程
for(int i = 0; i < array.length; i++) {
array[i] = reverseStack.pop();
}
}
public static void main(String[] args) {
int[] array = new int[] {1,3,5,7,9};
ArrayReverseByStack arbs = new ArrayReverseByStack();
arbs.reverse(array);
System.out.println(Arrays.toString(array));
}
}
2、.10进制正整数转2进制
import java.util.Stack;
public class DecimalToBinary {
/**
* 将一个10进制正整数转换为一个2进制字符串并返回
* @param num 作为参数的10进制整数
* @return 10进制整数对应的2进制字符串
*/
public String toBinary(int num) {
//[1]创建一个栈结构,用来存储整除法产生的余数
Stack<Integer> binaryStack = new Stack<>();
//[2]对参数num进行对2的整除法,直到这个数取值为0为止,将过程中产生的所有余数全部入栈(最后产生的0不用入栈)
int remainder = 0; //创建一个临时变量,用来保存num对2的余数
while(num > 0) {
remainder = num % 2;
binaryStack.push(remainder); //余数入栈
num /= 2; //别忘了对num除以2,因为上面的步骤只是取余,没有除以2的功能
}
//[3]将栈中所有余数出栈,出栈过程就是倒排余数的过程
String result = "0B";
while(!binaryStack.isEmpty()) { //只要栈结构不是空,就继续元素出栈
result += binaryStack.pop();
}
//[4]返回结果值
return result;
}
public static void main(String[] args) {
int num = 12; //12的2进制结构:0b1100
DecimalToBinary dtb = new DecimalToBinary();
String result = dtb.toBinary(num);
System.out.println(result);
}
}
2.2 队列
2.2.1 队列的结构特性
- 队列的结构特性是:元素先进先出(First In First Out,简称FIFO)
- 队列结构的特征就和现实生活中的排队买东西一样,先来排队的先买,一切先来后到,元素按照进入队列的顺序出队列
- 我们将元素键入队列的操作,称之为元素入队列
- 将元素从队列中取出的操作,称之为元素出队列
- 将元素出队列的一端称之为队头(front)
- 将元素入队列的一端称之为队尾(rear)
2.2.2 Java中的队列结构:LinkedLIst
- Collection接口下,不仅仅有List和Set两大子接口,实际上还存在着一个名为Queue的接口,这个接口从名字上来看,本身就是队列的含义,并且这个接口还有一个子接口名为Deque(双端队列)
- LinkedList类,一方面实现了List接口,一方面也实现了Deque接口,所以我们可以认为LinkedList本身就是一个通过链表实现的双端队列结构
- 双端队列是一种两端可以同时元素入队列、出队列的结构,也就是说,两端同为队头和队尾
同样的,在LinkedList类型中,也提供了一些方法,用来支持队列操作:
2.2.3 案例
1、利用两个栈实现一个队列
给定元素加入结构顺序:a b c d e
元素出结构顺序:a b c d e
这个结构功能等同于一个队列结构,但是内部要求使用两个栈结构实现
import java.util.Stack;
/**
* 使用两个栈结构模拟的队列
*/
public class StackQueue<E> {
private Stack<E> s1 = new Stack<>();
private Stack<E> s2 = new Stack<>();
/**
* 将元素加入这个结构的方法
* @param e 加入结构的元素
*/
public void add(E e) {
//[1]如果栈s2中还有剩余元素,说明上一次的出栈并没有“出干净”,还需要将s2中的元素全部入栈s1当中,保证元素加入结构的相对顺序不变
if(!s2.isEmpty()) {
while(!s2.isEmpty()) {
E tmp = s2.pop(); //s2出栈
s1.push(tmp); //s1入栈
}
}
//[2]如果元素要加入这个结构的话,那么首先将这些元素全部加入栈s1当中
s1.push(e);
}
/**
* 将元素从结构中退出的方法
* @return 退出结构的元素
*/
public E get() {
//[1]如果元素要从结构中退出,那么将栈s1中所有的元素全部出栈,并按照元素出栈顺序加入到栈s2中
if(!s1.isEmpty()) {
while(!s1.isEmpty()) {
E tmp = s1.pop(); //s1出栈
s2.push(tmp); //s2入栈
}
}
//[2]将栈s2中的栈顶元素出栈并返回,这个过程就是元素按照加入结构顺序退出的过程
if(!s2.isEmpty()) {
return s2.pop();
}
return null;
}
public static void main(String[] args) {
StackQueue<Character> sq = new StackQueue<>();
//加入结构5个元素
sq.add('A');
sq.add('B');
sq.add('C');
sq.add('D');
sq.add('E');
//退出结构3个元素
System.out.println(sq.get());
System.out.println(sq.get());
System.out.println(sq.get());
//再次加入结构3个元素
sq.add('F');
sq.add('G');
sq.add('H');
//全部元素退出结构
System.out.println(sq.get());
System.out.println(sq.get());
System.out.println(sq.get());
System.out.println(sq.get());
System.out.println(sq.get());
}
}
2、用两个队列实现一个栈
给定元素加入结构顺序:a b c d e
元素出结构顺序:e d c b a
这个结构功能等同于一个栈结构,但是内部要求使用两个队列结构实现
步骤(1):将全部加入结构的元素加入队列q1
步骤(2):当元素e要退出结构的时候,将q1中除了元素e之外的其他元素全部出队列q1,并同时加入队列q2
步骤(3):将队列q1中的元素e出队列,此时队列q1空
步骤(4):当元素d要退出结构的时候,将q2中除了元素d之外的其他元素全部出队列q2,并同时加入队列q1
步骤(5):将队列q2中的元素d出队列,此时队列q2空
……
以此类推,就能够实现元素的倒序输出
import java.util.LinkedList;
/**
* 使用两个队列结构模拟的栈
*/
public class QueueStack<E> {
private LinkedList<E> l1 = new LinkedList<>();
private LinkedList<E> l2 = new LinkedList<>();
private LinkedList<E> currentList = l1; //创建一个队列变量,用来记录当前应该向哪一个队列中添加元素,默认初始情况是向l1中添加元素
/**
* 元素加入结构的方法
* @param e 加入结构的元素
*/
public void add(E e) {
//[1]向currentList中添加元素
currentList.offer(e);
}
/**
* 元素退出结构的方法
* @return 退出结构的元素
*/
public E get() {
//[1]将当前用来存储元素的队列中的n个元素的前n-1个元素出队列,并存储到另一个空队列中
if(currentList == l1 && !l1.isEmpty()) {
while(l1.size() > 1) {
E tmp = l1.poll(); //l1出队列
l2.offer(tmp); //l2入队列
}
//[2]现在队列l1中仅剩一个元素,将这个元素出队列并返回,并且后来的元素全部存储在l2中
currentList = l2;
return l1.poll();
}else if(currentList == l2 && !l2.isEmpty()) {
while(l2.size() > 1) {
E tmp = l2.poll(); //l2出队列
l1.offer(tmp); //l1如队列
}
//[2]现在队列l2中仅剩一个元素,将这个元素出队列并返回,并且后来的元素全部存储在l1中
currentList = l1;
return l2.poll();
}else { //如果两个队列中都没有任何元素,那么直接返回空,方法结束
return null;
}
}
public static void main(String[] args) {
QueueStack<Character> qs = new QueueStack<>();
//相结构中添加5个元素
qs.add('A');
qs.add('B');
qs.add('C');
qs.add('D');
qs.add('E');
//退出结构3个元素
System.out.println(qs.get());
System.out.println(qs.get());
System.out.println(qs.get());
//再次追加3个元素
qs.add('F');
qs.add('G');
qs.add('H');
//全部元素出结构
System.out.println(qs.get());
System.out.println(qs.get());
System.out.println(qs.get());
System.out.println(qs.get());
System.out.println(qs.get());
}
}