一、栈 Stack
1.特点
(1)栈是一种线性数据结构
(2)规定只能从栈顶添加元素,从栈顶取出元素
(3)是一种先进后出的数据结构(Last First Out)LIFO
2.具体实现
Java中可以直接调用方法来实现栈
如何自己写代码来实现栈呢?
先定义一个接口,方便后边进行调用
package com.algo.lesson.lesson02.stack; public interface Stack_I<T> { //入栈 void push(T ele); //出栈 T pop(); //查看栈顶元素 T peek(); //判断是否为空 boolean isEmpty(); //后去栈中元素 int getSize(); }
接下来来实现栈的方法,调用接口,完善方法:
package com.algo.lesson.lesson02.stack; import com.algo.lesson.lesson01.MyArr; //以数组作为栈顶的底层数据结构 public class ArrStack<T> implements Stack_I<T> { private MyArr<T>data; int size; public ArrStack() { this.data=new MyArr<>(100); this.size=0; } @Override public void push(T ele) { //在数组尾部添加元素 this.data.add(ele); this.size++; } @Override public T pop() { if(isEmpty()){ return null; } this.size--; return this.data.removeBFromLast(); } @Override public T peek() { return this.data.getLastValue(); } @Override public boolean isEmpty() { return this.size==0; } @Override public int getSize() { return this.size; } }
以上就是方法的代码,接下来,写个main函数来调用,检查方法是否正确
package com.algo.lesson.lesson02.stack; import java.util.ArrayList; import java.util.List; import java.util.Random; public class StackTest<T> { public void test(Stack_I<T>stack, List<T> list){ //开始时间 long startTime=System.nanoTime(); for(int i=0;i<list.size();i++){ stack.push(list.get(i)); } System.out.println(stack.getSize()); while(!stack.isEmpty()){ T ele=stack.pop(); System.out.println(ele+" "); } //结束时间 long endTime=System.nanoTime(); System.out.println("总耗时:"+(endTime-startTime)/100000000.0); } public static void main(String[] args) { StackTest<Integer>stackTest=new StackTest<>(); Stack_I<Integer>stack=new ArrStack<>(); List<Integer>list=new ArrayList<>(); Random random=new Random(); for(int i=0;i<100;i++){ int val= random.nextInt(1000); list.add(val); } stackTest.test(stack,list); } }
注:其中long startTime=System.nanoTime();方法是获取一个时间(单位毫秒)
在程序运行前和运行后个添加一个,最后将两个时间相减,得到程序运行时间。
3.时间复杂度分析
二、队列
1.特点
(1)队列也是一种线性数据结构
(2)只能从一端添加元素,另一端取出元素
(3)是一种先进先出的数据结构(FIFO——fist in fist out )
2.具体实现
Java中也可以直接调用队列的方法:
自己的实现:
接口:
package com.algo.lesson.lesson02.queue; public interface Queue_I<T> { void offer(T ele);//入队 T poll();//出队 T peek();//查找队首元素 int getSize(); boolean isEmpty(); }
方法代码:
package com.algo.lesson.lesson02.queue; import com.algo.lesson.lesson01.MyArr; public class ArrQueue<T>implements Queue_I<T> { private MyArr<T>data; /* private int size;//队列中元素的个数 */ public ArrQueue(){ this.data=new MyArr<>(50); } @Override public void offer(T ele) { this.data.add(ele); } @Override public T poll() { if(isEmpty()){ return null; } return this.data.removeByIndex(0); } @Override public T peek() { if(isEmpty()){ return null; } return this.data.getValueByIndex(0); } @Override public int getSize() { return this.data.getSize(); } @Override public boolean isEmpty() { return this.data.isEmpty(); } }
3.出现的问题
入队列时间复杂度为O(n),因为在出队时,元素要前移,会出现假溢出的情况。
所以就出现了循环队列来解决这个问题
循环队列:
front记录队首,tail记录队尾,队尾达到容积时,返回到队头,将空位置补上,可以继续存储数据。
package com.algo.lesson.lesson02.queue; import java.util.Random; /* 基于Java中的数组进行二次封装,制作一个可变数组 */ //泛型:就是类型作为参数 public class LoopArr<T> { private T[] data;//保存数据 private int size;//数组中实际存放元素的个数 private int front;//队首 private int tail;//队尾 int capacity;//容积 //构造函数 public LoopArr(int capacity) { if (capacity <= 0) { this.capacity = 11; } else { this.capacity = capacity + 1; } this.size = 0; this.front = this.tail = 0; this.data = (T[]) (new Object[this.capacity]); } //获取数组中实际存放元素的个数 public int getSize() { return this.size; } //获取数组的容积 public int getCapacity() { return this.capacity; } //判断数组是否为空 public boolean isEmpty() { return this.front == this.tail; } //向数组中添加元素(尾部) public void add(T item) { //判断数组是否满 if ((this.tail + 1) % this.capacity == this.front) { //扩容 resize((this.capacity-1)*2+1); } //从index位置开始元素需要进行后移 this.data[this.tail] = item; this.tail = (this.tail + 1) % this.capacity; //更新this.size this.size++; } private void resize(int newCapacity) { System.out.println("resize:" + newCapacity); T[] newData = (T[]) (new Object[newCapacity]); //将原数组驾到新数组里 for (int i = 0; i < this.size; i++) { newData[i] = this.data[(this.front+i)%this.capacity]; } //改变容器 this.data = newData; this.capacity = newCapacity; //将this.front置零 this.front=0; this.tail=this.size; } //获取指定位置的值 public T getValueByIndex() { if(isEmpty()){ return null; } return this.data[this.front]; } //移除队首元素 public T remove() { if (isEmpty()) { return null; } //删除操作的核心 /* 1.找到删除的位置 2.删除位置之后的元素要前移 arr[j-1]=arr[j] */ T delValue = this.data[this.front]; this.front = (this.front + 1) % this.capacity; this.size--; //判断是否缩容 if (this.size < this.capacity / 4 && this.capacity / 2 > 0) { resize((this.capacity-1) / 2 +1); } return delValue; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < this.size; i++) { sb.append(this.data[i]); if (i != this.size - 1) { sb.append(","); } } sb.append("]"); return sb.toString(); } }
package com.algo.lesson.lesson02.queue; public class LoopQueue<T> implements Queue_I<T>{ private LoopArr<T>data;//容器 public LoopQueue(){ this.data=new LoopArr<>(100); } @Override public void offer(T ele) { this.data.add(ele); } @Override public T poll() { return this.data.remove(); } @Override public T peek() { return this.data.getValueByIndex(); } @Override public int getSize() { return this.data.getSize(); } @Override public boolean isEmpty() { return this.data.isEmpty(); } }
4.循环队列的复杂度分析
三、栈和队列的互相实现
既然我们了解了栈和队列,知道了这两种数据结构十分相似,也就可以大胆假设以下,可不可以相互实现,不是用上面所写的以数组为底层,而是相互为底层存储。
1.用栈来实现队列
import java.util.Stack; class MyQueue { private Stack<Integer> A; private Stack<Integer> B; public MyQueue() { A = new Stack<>(); B = new Stack<>(); } public void push(int x) { while (!B.isEmpty()) { A.push(B.pop()); } A.push(x); while (!A.isEmpty()) { B.push(A.pop()); } } public int pop() { return B.pop(); } public int peek() { return B.peek(); } public boolean empty() { return B.isEmpty(); } }
2.用队列实现栈
import java.util.LinkedList; import java.util.Queue; class MyStack { private Queue<Integer> queue1; private Queue<Integer> queue2; public MyStack() { queue1 = new LinkedList<>(); queue2 = new LinkedList<>(); } public void push(int x) { queue2.add(x); while (!queue1.isEmpty()) { queue2.add(queue1.remove()); } Queue<Integer> temp = queue1; queue1 = queue2; queue2 = temp; } public int pop() { return queue1.remove(); } public int top() { return queue1.peek(); } public boolean empty() { return queue1.isEmpty(); } }
这就是这两种数据结构相互实现的代码
在LeetCode中也有相对应的题目:
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台(栈实现队列)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台(队列实现栈)