目录
一、栈和队列概念
和链表这种“能看见”的结构不同,栈和队列属于逻辑概念。
栈:犹如弹夹,先进后出;
队列:犹如排队,先进先出。
二、链表实现栈和队列
无论是实现栈还是队列,因为它们是逻辑概念,实现它们无非也是逻辑上如何实现,也就是它们的进出顺序,所以用链表实现进出就可以了。
1、思路
给用户提供一个我们自己封装的栈,用户拿到这个栈实例化对象a,用户对a这个对象进行加数据,弹数据操作。
||
我们内部如何对a进行操作呢?给a连个指针,一个记录head,一个记录tail,加数据、弹数据都是对这两个指针的操作。
||
我们可不可以只提供一个指针呢?毕竟通过一个指针就可以看到这个数据的全貌,当然可以,只不过,比如你只记录了head指针,那么你从尾部加数据时,要不断循环到尾部,定位到尾部之后,再再尾部加数据。这样不如直接用两个指针。那么用三个指针可以吗?可以,当三个指针干嘛用?是没有必要的。
||
想明白要实现什么效果:
用户调用我们的栈类.add() -- >从尾部加数据-->调用另一个类的addFromBottom();
调用栈类.pop() --> 另一个类的popFromBottom();
调用队列.pop() --> 另一个类的popFromHead();
即栈加数据是从尾部加,弹出数据从尾部弹;
队列加数据从尾部加,弹出数据从头部弹。
2、链表实现进出
(1)首先建一个双向链表,我们不像上一章一样是int型的双向链表,我们提供泛型
public class DoubleNode<T>{
public T value;
public DoubleNode next;
public DoubleNode last;
public DoubleNode(T value){
this.value = value;
}
}
(2)建一个类,这个类内部专门提供addFromBottom() 、popFromBottom() 、popFromHead()方法:
public static class AddAndPop<T>{
DoubleNode head;
DoubleNode tail;
//从尾部加数据
//1。旧tail的next指向新加的数据;2.新数据的last指向旧tail;3.cur做新tail
public void addFromBottom(T value){
DoubleNode<T> cur = new DoubleNode<>(value); //先把在这个value封装成node类型,这样就有了指针
if(tail == null){
head = cur;
tail = cur;
}else{
tail.next = cur;
cur.last = tail;
tail = cur;
}
}
//从尾部弹数据
public T popFromBottom(){
if(tail == null){
return null;
}
DoubleNode<T> cur = tail; //这里是把当前tail的内存给cur
if(head == tail){
head = null;
tail = null;
}else{
tail = tail.last;
tail.next = null;
cur.last = null;
}
return cur.value;
}
//从头部弹数据
public T popFromHead(){
if(head == null){
return null;
}
DoubleNode<T> cur = head; //先把旧head内存做一个备份
if(head == tail){
head = null;
tail = null;
}else{
head = head.next;
head.last = null;
cur.next = null;
}
return cur.value;
}
//判断是否为空
public boolean isEmpty(){
return head == null;
}
}
3、链表实现栈和队列
有了进出,那么顺序组合一下就可以实现自己的栈和队列:
实现栈:
public class MyStack<T>{
private AddAndPop<T> addAndPop;
//栈加数据,是从尾部加
public void add(T value){
addAndPop.addFromBottom(value);
}
//栈弹数据,是从尾部弹
public T delete(){
return addAndPop.popFromBottom();
}
//判断栈是否为空
public boolean isEmpty(){
return addAndPop.isEmpty();
}
}
实现队列:
public class MyQueue<T>{
private AddAndPop<T> addAndPop;
//队列加数据,是从尾部加
public void add(T value){
addAndPop.addFromBottom(value);
}
//队列弹数据,是从头部弹
public T delete(){
return addAndPop.popFromHead();
}
//判断队列是否为空
public boolean isEmpty(){
return addAndPop.isEmpty();
}
}
三、数组实现栈和队列
1、数组实现栈
数组实现栈是比较简单的,因为栈的出入都是在尾部,那么我们定位一个指针,指向尾部后一位,这样加数据时,加到此指针位置,此指针++,减数据时,此指针--,定位到尾部,再弹出数据。
//数组实现栈
public class ArrayForStack{
private Object[] myArray;
private int limit;
private int index;
public ArrayForStack(int limit){
this.limit = limit;
myArray = new Object[this.limit];
}
//加数据
public void add(Object value){
if(index <= limit-1){
myArray[index] = value;
index++;
}
}
//弹数据
public Object delete(){
Object cur = null;
if(index != 0){
index --;
cur = myArray[index];
myArray[index] = null;
}
return cur;
}
}
2、数组实现队列
队列进是从尾部进,出是从头部出,所以要准备两个指针,一个指向头部,进数据,一个指向尾部,出数据。
有数据的部分是两个指针之间的,无数据的部分是两个指针之外。我们可以用一个size表示有数据部分大小
public class ArrayForQueue{
private Object[] queue;
private int pullIndex;
private int pushIndex;
private int size;
private int limit;
public ArrayForQueue(int limit){
this.limit = limit;
queue = new Object[limit];
pullIndex = 0;
pushIndex = 0;
size = 0;
}
//添加数据,size增加,pullindex移动,判断size是否为limit,是的话不能再加
public void add(Object value){
if(size!=limit){
queue[pullIndex] = value;
pullIndex = pullIndex++ == limit ? 0:pullIndex++ ; //pullIndex移动到下个“空格”
size++;
}else{
throw new RuntimeException("栈满了,不能再加");
}
}
//减数据,size减,pushindex移动,判断size是否为0,是则不能取数据
public Object delete(){
Object cur = null;
if(size!=0){
cur = queue[pushIndex];
queue[pushIndex]=null; //这里其实无所谓写,因为再加数据,加到这个位置会覆盖原来的数据,原来的数据存在也好,不存在(null)也好,都不影响加新数据
pushIndex = pushIndex++ == limit? 0: pushIndex++;
size--;
}else{
throw new RuntimeException("栈空了,不能再取");
}
return cur;
}
}
四、实现一个栈方法,返回最小元素
要清楚,栈是逻辑概念,它只是因为进出的顺序让它被称为栈。我们要利用它的逻辑意义---即进出取得它的最小值。
1、思路
既然只能通过进出获取最小值,那么我们希望当我要最小值时,这个栈就给我弹一个值,这个值恰好是最小值。
||
那么就要在进数据的时候动心思,进数据是不区分大小的。我们可以自己设计加一个栈,专门记录大小,这个栈栈顶永远是最小值。
||
进数据的时候,最小栈判断是不是比上一个值小,小的话加新进的数据,大的话加上一个数据。
||
出数据的时候,最小栈直接弹栈顶就可以了。
2、实现
我们利用系统栈,不用自己实现的栈:
public class MyStack2{
private Stack<Integer> data;
private Stack<Integer> min;
public MyStack2(){
data = new Stack<Integer>();
min = new Stack<Integer>();
}
public void add(Integer i){
if(min.isEmpty()){
min.push(i);
}else if(i < getMin()){
min.push(i);
}else{
min.push(getMin());
}
data.add(i);
}
public Integer pop(){
if(data.isEmpty()){
throw new RuntimeException("栈空,无法弹出");
}
min.pop();
return data.pop();
}
public Integer getMin(){
if(min.isEmpty()){
throw new RuntimeException("栈空,无最小值");
}
return min.peek();//返回栈顶的元素,但不弹出该栈顶元素。
}
}
五、栈和队列的相互实现
1、如何用栈实现队列
进入的时候进入栈,顺序12345,出的时候也是从栈出,54321,但是给用户展现的是12345
需要明确,进数据的时候用push栈,出的时候用先把push栈的数据弹出,进入pop栈,再从pop栈出数据。
但是为了防止:pop栈还有数据--push栈再进数据--需要出数据时--push栈倒数据给pop栈 -- pop栈弹数据--弹出最新数据 这种情况发生,所以应保证pop栈数据全部弹光,push栈才可以给pop栈新加数据。
public class StackForQueue<T>{
private Stack<T> push;
private Stack<T> pop;
public void add(T value){
push.add(value);
}
public T pop(){
//push栈出数据,pop栈加数据,而且1要加光,push栈不能留有数据;2pop栈要弹光,push栈才能加
if(pop.isEmpty()){
while(!push.isEmpty()){
pop.add(push.pop());
}
}
if(pop.isEmpty()){
throw new RuntimeException("栈空了,无法弹出数据");
}
return pop.pop();
}
}
2、如何用队列实现栈
比如,进数据是进入队列,依次进12345,出的时候内部是从队列出,顺序是12345,但是给用户展现的是54321,此时我们可以用两个队列实现倒序出
用系统提供的数组队列,data是操作的队列,无论加数据还是弹数据都用它,help是辅助队列,承担倒数据的作用
public class QueueForStack<T>{
private ArrayBlockingQueue<T> data;
private ArrayBlockingQueue<T> help;
private int limit;
public QueueForStack(int limit){
data = new ArrayBlockingQueue<T>(limit);
help = new ArrayBlockingQueue<T>(limit);
this.limit = limit;
}
public void add(T value){
if(data.size() < limit){
data.add(value);
}
}
/*
* 1.data开始搬数据,只留下一个元素
* 2.这个元素做为返回值弹出
* 3.data help互换
* */
public T pop(){
//data出数据,help加数据
while(help.size()<limit && data.size()>1){
help.add(data.poll());
}
T value = data.poll();
//data help互换
ArrayBlockingQueue<T> tmp;
tmp = data;
data = help;
help = tmp;
return value;
}
}