一、栈 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)官网 - 全球极客挚爱的技术成长平台(队列实现栈)