今天跟着左程云算法课013和014,复习了【队列和栈(以及循环队列)的实现】以及【栈和队列的相互实现】。是队列和栈复习的前半部分。
一、队列和栈的实现
队列和栈是逻辑结构,在代码实现时,其存储结构可以用链表也可以用数组。那么分别用链表和数组来实现队列和栈的类。队列是先进先出(offer和poll),栈是先进后出(push和pop),只需在写数据增加和删除时抓住这个特征即可,代码实现还是比较简单的。
(1)用链表实现队列
用Java内部的双向链表LinkedList来实现队列,并且写代码的时候看了一下LinkedList的add和offer方法的区别。区别在于,在往队列中添加数据时,遇到队列大小限制时,add会抛出一个异常,offer会返回一个false,用add和offer都可以,这里使用offer。代码实现如下:
//用链表实现队列
public static class Queue1{
//用Java内部的双向链表来实现
public Queue<Integer> queue=new LinkedList<>();
//判断队列是否为空
public boolean isEmpty() {
return queue.isEmpty();
}
//向队列中加入num
public void Addnum(int n) {
//遇到队列大小限制时,add会抛出一个异常,offer会返回一个false
//queue.add(n);
queue.offer(n);
}
//从队列头拿数据
public int poll() {
return queue.poll();
}
//返回队列头的元素但是不弹出
public int getnum() {
return queue.peek();
}
//返回目前队列里有几个数
public int size() {
return queue.size();
}
}
(2)用数组实现队列
左神在视频课里提到的,虽然用链表和数组实现的时间复杂度一样,他们的基本操作都是O(1),但是常数时间不一样,链表比数组要慢,这个想一下就知道。一般的笔面试要考的话也是用数组会比较多。用数组实现队列时,要设置两个变量head和end,分别指向队列的头和尾,所表示的是一个左闭右开的范围,这个队列中的数据是[head,end)。如果数据入队,那么把数据放到arr[end],然后end++;如果数据出队,那么返回arr[head]的数据,然后head++。比较简单,代码实现如下:
//用数组实现队列
public static class Queue2{
public int[] queue;
public int head;
public int end;
//构造函数
public Queue2(int n) {
this.queue=new int[n];
head=0;
end=0;
}
//判断队列是否为空
public boolean isEmpty() {
return head==end;
}
//判断队列是否已满
public boolean isFull() {
return end==queue.length;
}
//往队列中加入元素
public void Addnum(int n) {
if(isFull()) {
System.out.println("队列已满");
return;
}
else {
queue[end++]=n;
}
}
//取队列头的元素
public int poll() {
if(isEmpty()) {
System.out.println("队列为空,取数失败");
return -1;
}
else {
return queue[head++];
}
}
//取队列头的元素,但不出队
public int gethead() {
if(isEmpty()) {
System.out.println("队列为空,取数失败");
return -1;
}
else {
return queue[head];
}
}
//取队列尾的数据
public int getend() {
if(isEmpty()) {
System.out.println("队列为空,取数失败");
return -1;
}
else {
return queue[end-1];
}
}
//返回队列数据长度
public int getSize() {
return end-head;
}
}
(3)用链表实现栈
可以用Java内部的Stack来实现,这是java的模板方法(template method),本质上是一个动态数组。那这里直接用Stack模板来实现,那就非常简单了,代码如下:
//用java内部的动态数组实现栈
public static class Stack1{
public Stack<Integer> s=new Stack<>();
//判断栈是否为空
public boolean isEmpty() {
return s.isEmpty();
}
//数据入栈
public void push(int n) {
s.push(n);
}
//数据出栈
public int pop() {
return s.pop();
}
//返回栈顶的值但不弹出
public int peek() {
return s.peek();
}
//返回栈的元素个数
public int size() {
return s.size();
}
}
(4)用数组实现栈
用数组实现栈也是比较简单,只用设置一个变量size来指向栈顶就可以,表达的数据范围是[0,size),也就是说栈顶的元素是arr[size-1]。直接实现,代码如下:
public static class Stack2{
public int[] arr;
public int size;
public Stack2(int n){
arr=new int[n];
size=0;
}
//判断为空
public boolean isEmpty() {
return size==0;
}
//判断为满
public boolean isFull() {
return size==arr.length;
}
//数据入栈
public void push(int n) {
//先判断为满,懒得写,省略
arr[size++]=n;
}
//数据出栈
public int pop() {
//先判断是否为空,懒得写,省略
return arr[--size];
}
//返回栈顶数据但不出栈
public int getnum() {
//先判断是否为空,省略
return arr[size-1];
}
//返回栈的元素个数
public int size() {
return size;
}
}
二、设计循环队列
循环队列就是当数据入队入到数组最大限制之后,可以折回去继续使用数组前面已经被释放的空间。本科上数据结构课程的时候,老师好像讲了循环队列的实现,用的是取模的方法,比较复杂一些。左神的课里直接设置head、end、size还有limit,对于这个队列是否为空和是否为满的判断仅用size和limit来判断(limit其实就是arr.length,也可以不用设置limit,在判断的时候直接用arr.length就OK,这里为了方便设置了limit)。
那么这样一来,关键的点在于:数据入队的时候,判断end的位置,如果end已经在limit-1的位置,把数据放在arr[end],end折返到0,否则就正常的end+1;数据出队的时候,判断head的位置,如果head已经在limit-1的位置,head折返到0,否则head+1;想清楚这个点就很好实现了,代码如下:
public static class MyCircularQueue{
public int[] arr;
public int head;
public int end;
public int size;
public int limit;
public MyCircularQueue(int k) {
arr=new int[k];
head=0;
end=0;
size=0;
limit=k;
}
public boolean isEmpty() {
return size==0;
}
public boolean isFull() {
return size==limit;
}
public boolean enQueue(int value) {
if(isFull()) {
return false;
}
else {
arr[end++]=value;
end= end==limit?0:end;
size++;
return true;
}
}
public boolean deQueue() {
if(isEmpty()) {
return false;
}
else {
head= head==limit-1?0:head+1;
size--;
return true;
}
}
public int Front() {
if(isEmpty()) {
return -1;
}
else {
return arr[head];
}
}
public int Rear() {
if(isEmpty()) {
return -1;
}
else {
int last= end==0?limit-1:end-1;
return arr[last];
}
}
}
三、队列和栈的互相实现
(1)用栈实现队列
要用两个栈实现队列,用两个栈可以倒数据,数据进到in栈里,然后再倒到out栈里,这样数据一倒就能变成队列的正常顺序。只是倒数据的时候要注意两点:1)当out栈为空的时候才能从in里倒数据出来;2)如果倒数据,in里面的数据一定要倒完。
核心是倒数据的操作,那么在实现队列的push,pop以及peek操作时,push只涉及到往in栈里加数据,在push函数中可以不去倒数据;而pop和peek是对out栈进行操作的,在进行取数据的操作之前要倒数据。想清楚这个就很好实现了,代码如下:
//用两个栈实现队列
public class MyQueue{
public Stack<Integer> in;
public Stack<Integer> out;
public MyQueue() {
in=new Stack<>();
out=new Stack<>();
}
//倒数据
//out为空的时候才能倒数据
//in必须倒完
public void InToOut() {
if(out.isEmpty()) {
while(!in.isEmpty()) {
out.push(in.pop());
}
}
}
//队列加入数据
public void offer(int n) {
in.push(n);
}
//数据出队
public int poll() {
InToOut();
int num=out.pop();
return num;
}
//返回队头数据
public int peek() {
InToOut();
return out.peek();
}
//判断队列是否为空
public boolean isEmpty() {
return in.isEmpty()&&out.isEmpty();
}
}
(2)用队列实现栈
用队列实现栈的核心思想在于:当数据入栈(push)时,计算一下入栈前的元素个数n,那么在本次数据入栈后,在进行n次将数据出队再入队的操作。也就数据入栈(push)时比较特殊,需要思考一下,其他操作都正常进行。
另外在这里再强调一下,Stack有java模板函数,但是Queue没有。因此在创建的时候要注意下,Queue要用LinkedList(也就是双向链表)来实现。
public Stack<Integer> stack=new Satck<Integer>();
public Queue<Integer> queue=new LinkedList<Integer>();
用队列实现栈的代码如下:
public class MySatck{
public Queue<Integer> stack;
public MySatck() {
stack=new LinkedList<>();
}
//入栈功能
public void push(int x) {
//入栈之前先算一下队列中有几个数
int num=stack.size();
stack.offer(x);
for(int i=0;i<num;i++) {
stack.offer(stack.poll());
}
}
//出栈功能
public int pop() {
return stack.poll();
}
//返回栈上面的数但不弹出
public int peek() {
return stack.peek();
}
//判断是否为空
public boolean isEmpty() {
return stack.isEmpty();
}
}