最近刚好学习了数据结构中的栈与队列,让我来浅浅谈下这两个有趣的东西吧。(基于java编写)
首先我们得请出第一位明星:栈(Stack)
那什么是栈呢?
栈是一种后进先出的数据结构。
具体来说呢,栈也是一种线性表来的,其只允许在固定一端进行插入和删除。进行数据插入和删除的一端叫栈顶,另一端叫栈底。
同时有两个很常见操作:
压栈:栈的插入操作叫做进栈/入栈/压栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据在栈顶。
接下来,让我们一起看看有什么方法吧。
分别有push(入栈)、pop(出栈)、peek(不出栈看栈顶元素是什么)、isFull(栈是否为满)、isEmpty(栈是否为空)。
介绍完有什么方法,那么看看它们是如何实现的吧:
其实栈的实现还是用数组来实现的。
栈的实现:
isFull
public class MyStack {
int [] arr;
int UsedSized;
public MyStack(){
this.arr=new int [10];
}
public boolean isFull(){
return UsedSized==arr.length;
}
}
这个判断是否为满是挺简单的。
isEmpty
public boolean isEmpty(){
return UsedSized==0;
}
push
public void push(int val){
if(isFull()){
arr= Arrays.copyOf(arr,2*arr.length);
}
arr[UsedSized]=val;
UsedSized++;
}
在push方法中,如何栈满了就要扩容。
然后把添加的值给到UsedSized下标就行。
pop
public int pop(){
if(isEmpty()){
return -1;
}
int oldVal=arr[UsedSized-1];
UsedSized--;
return oldVal;
}
这里pop的话,就要看看是否为空了。然后我们让一个变量储存UsedSized-1的值。返回这个变量即可。
peek
public int peek(){
if(isEmpty()){
return -1;
}
return arr[UsedSized-1];
}
peek方法是挺简单的和pop方法有些相似。
这样子,我们的Stack自己的实现讲完了。
那接下来介绍另一位“明星”——队列(Queue)
那什么是队列呢?
队列:只允许在一端插入数据操作,在另一端进行数据操作的线性表,且具有先进先出的特征。
进行插入操作的一端叫做队尾,进行删除操作的一端叫做队头。
这有点类似我们日常生活中的排队。
在java中,这个Queue底层是通过链表实现的(双向无头链表)
那么像栈那样能否让数组来实现呢?
其实也是可以的,但我们先用链表实现先。
实现之前让我们看看这个Queue有什么方法。
offer(入队列)、poll(出队列)、peek(不出队列,看队头元素)、isEmpty(队列是否为空)
那让我们看看它们是如何实现的吧。
队列的实现:
isEmpty
public class MyQueue {
public ListNode getHead() {
return head;
}
static class ListNode{
//javaQueue底层本质还是一个双向链表
public int val;
public ListNode prev;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
public boolean isEmpty(){
return head==null;
}
}
既然说到了在java中Queue底层使用类似于无头双向链表实现的。
那么我们就可以构建一个无头双向链表。里面有三个域,一个是val值域、一个是前驱节点域、一个是后继节点域。
offer
public void offer(int val){
ListNode node=new ListNode(val);
if(head==null){
head=last=node;
}else{
last.next=node;
node.prev=last;
last=last.next;
}
}
这里的offer,当我们的head不为空后,就类似我们链表的尾插了。
poll
public int poll(){
if(head==null){
return -1;
}
int ret=head.val;
if(head.next==null){
head=null;
last=null;
}else{
head=head.next;
head.prev=null;
}
return ret;
}
poll,有丢丢麻烦,因为我们要返回一个值,所以我们得有个变量事先来保存下队头元素的值,然后我们才能进行链表哦的头删操作。
peek
public int peek(){
if(head==null){
return -1;
}
return head.val;
}
至于我们的peek方法就略显简单了,之前返回队头元素的值就行。
ok,我们的链表实现的队列就讲完了。
那么接下来我们看看如何用数组实现的吧。
队列数组实现:
我们用数组来实现的Queue,有另一个别名——循环队列,很明显是循环的意思。
而它画出来呢,基本就是这个样子:
红色的是下标,黑色是它的值。
那我们用数组来构建它的时候,要进行两个操作,为空还是为满。
目前可以提供三种办法来判断为满还是为空:
1.利用UseSized
2.利用boole flg=false;标记下它是否为满还是weikong
3.浪费一个空间,来判断是否为满还是为空。
本章就介绍下第三种方法。
其他两种相对简单些。
第三种方法怎么来判断是否为空为满呢。
如图所示:
利用这条公式判断为满:last=(last+1)%len
我们舍弃一个空间,利用这条公式,举个例子,当我们的last走到7了,然后+1模上数组长度,等于了0,所以相当于回到了0下标,就代表了是满的。
那么这个棘手的部分讲完了,那我们看看如何实现的吧:
isEmpty and isFull
public class MyCircularQueue {
//使用数组实现
public int [] arr;
public int first;
public int last;
public MyCircularQueue(int k){
arr=new int [k];
}
public boolean isEmpty(){
return first==last;
}
public boolean isFull(){
return (last+1)%arr.length==first;
}
}
enQueue
public boolean enQueue(int val){
if(isFull()){
return false;
}
arr[last]=val;
last=(last+1)%arr.length;
return true;
}
这里不用last++,是因为,会可能造成越界。
deQueue
public boolean deQueue(){
if(isEmpty()){
return false;
}
first=(first+1)%arr.length;
return true;
}
Front
获取队头元素
public int Front(){
if(isEmpty()){
return -1;
}
return arr[first];
}
Rear
获取队尾元素
public int Rear(){
if(isEmpty()){
return -1;
}
int index=(last==0)?arr.length-1:last-1;
return arr[index];
}
获取队尾元素有丢丢麻烦,我们用到一个变量来获取队尾下标,为了防止越界,我们用了三目运算符。
ok,这样子我们栈和队列这两个都讲完了。
完!