一、栈的基本概念与操作
栈是一种特殊的线性表,只允许在一端实现插入删除的操作,插入删除的一端称之为栈顶,另一端称为栈底。栈遵循后进先出的原则。
压栈:指栈的插入操作,入数据在栈顶。
出栈:又叫弹栈,指栈的出数据操作,出数据在栈顶。
基本操作如图:
二、栈的代码实现以及解释
如图,栈的存储方式和数组类似,所以较为简单。因此,我们只需用到数组就可以解决问题。
1.操作前准备
先定义一个数组并给定空间。
public class MyStack {
int[] array;
int size = 0;
public MyStack(){
this.array = new int[10];
}
}
2.压栈操作
注意:压栈操作我们需要考虑一个问题,栈是否已满?
//定义一个方法判断栈是否已满
public boolean isFull(){
return size == array.length;
}
之后就可以实现操作
public int push (int val){
//如果是满的要扩容
if(isFull()){
array = Arrays.copyOf(array,2*array.length);
}
this.array[size] = val;
size++;
return val;
}
3.出栈操作
注意:这里需要判断值是否为空的情况,可以创建一个异常类来报错
public class MyEmptyStackException extends RuntimeException{
public MyEmptyStackException() {
}
public MyEmptyStackException(String message) {
super(message);
}
}
public boolean empty(){
return size == 0;
}
操作实现:
public int pop() {
//判断元素是否为空
if(empty()){
throw new MyEmptyStackException("栈为空!");
}
//直接将栈首的元素记录输出即可
int set = array[size-1];
size--;
return set;
}
4.peek查看操作
这个方法只是提供查看栈的第一个元素,不会将元素弹出栈。
public int peek(){
//栈为空要报错
if(empty()){
throw new MyEmptyStackException("栈为空!");
}
return array[size-1];
}
5.整体测试
public class Test {
public static void main(String[] args) {
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.push(3);
myStack.push(4);
int X = myStack.pop();
System.out.println(X);
int Y = myStack.peek();
System.out.println(Y);
Y = myStack.peek();
System.out.println(Y);
}
}
三、队列的基本概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的原则。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
基本操作如下:
四、队列的代码实现及解释
1.操作前准备
在这里实现相关操作我们用单链表的方法实现,我们可以定义两个变量分别指向队头和队尾,代码如下:
static class ListNode{
public int val;
public ListNode next;
public ListNode(int val){
this.val = val;
}
}
//定义头尾结点
public ListNode head;
public ListNode tail;
//定义一个计数变量
public int size;
2.判断队列是否为空
public boolean empty(){
return size == 0;
}
3.实现入队列操作
public void offer(int val){
ListNode node = new ListNode(val);
//如果队列为空时
if(empty()){
head = node;
tail = node;
}else{
tail.next = node;
tail = tail.next;
}
size++;
}
4.实现出队列操作
public int poll(){
if(empty()){
return -1;
}
//定义一个参数存放头结点的值
int ret = head.val;
head = head.next;
if(head == null){
tail = null;
}
size--;
return ret;
}
5.实现查看队头元素
public int peek(){
if(empty()){
return -1;
}
return head.val;
}
6.查看元素个数
//查看有多少元素
public int getSize(){
return size;
}
7.整体实现
public class Test {
public static void main(String[] args) {
MyQueue myQueue = new MyQueue();
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
System.out.println(myQueue.getSize());
System.out.println(myQueue.poll());
System.out.println(myQueue.poll());
System.out.println(myQueue.peek());
System.out.println(myQueue.poll());
}
}
五. 循环队列
循环队列,顾名思义就是一个环形的队列,这种队列的实现就是通过一个数组进行实现。
对应操作:
- 入队操作
- 出队操作
- 获取队头元素
- 获取队尾元素
- 判断队列是否为空
- 判断队列是否为满
相应元素的定义与数组的实现
private int[] elem;
private int front; //代表队头
private int rear; //表示队尾
//给定数组长度
public MyCircularQueue(int k) {
elem = new int[k+1];
}
- 判断数组是否为满
public boolean isFull() {
if((rear+1)%elem.length == front){
return true;
}
return false;
}
如图所示:这里判断队列已满时需要注意,不能单纯的进行 rear++ 这样会出现数组越界的现象,因此 (rear+1)%elem.length == float 这个方法来判断是否出现队列已满的情况。
- 判断队列是否为空
public boolean isEmpty() {
if(front == rear){
return true;
}
return false;
}
- 入队操作
public boolean enQueue(int value) {
if(isFull()){
return false;
}
elem[rear] = value;
rear = (rear + 1)%elem.length;
return true;
}
同样的,这里的队尾 rear 要实现后移,同样需要考虑是否为数组最后一个元素的情况。
- 出队操作
public boolean deQueue() {
if(isEmpty()){
return false;
}
front = (front+1)%elem.length;
return true;
}
- 获取队头元素
//获取队头元素
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
- 获取队尾元素
public int Rear() {
if(isEmpty()){
return -1;
}
int index = rear == 0 ? elem.length-1 : rear-1;
return elem[index];
}
在这里,对获取队尾链表中的三目运算符进行简单的解释。
首先,我们要知道的是,要寻找队尾元素有两种情况第一种如下:
如图所示,此时的 rear 指针正指在数组尾部这个一般的位置,此时,只要不在在 0 这个特殊的下表位,不难理解,真正的队尾元素,就是队尾指针的前一个位置。 所以只需要 rear-1 即可。
第二种情况:
如上图所示,此时 rear 正处在特殊的 0 下标位置,此时 rear-1 很明显已经不在适用,这里就需要特殊方法 (elem.length-1) 来判断队尾元素的位置!!