一、栈结构介绍
栈(stack) 是限定仅在表尾进行插入或删除操作和线性表(只有一端口能进出数据),对栈来说表尾和表头有特殊含义,表尾被称为栈顶,表头被称为栈底,没有元素的空表称为空栈,元素数量达到栈的容量称为满栈,数据添加到栈中叫入栈、压栈,数据从栈中删除叫出栈、弹栈,由于栈元素特殊添加和删除的规则,所以栈的元素会先进后出的现象,简称为LIFO(Last in first out)。
数组——代码实现栈:
1、私有变量和构造方法
private static final int CAPACITY = 20;//栈容量
private int topOfStack;//栈顶指针
private Object arrayStack[];
public MyArrayStack(){//构造方法
topOfStack = -1;
arrayStack = new Object[CAPACITY];
}
我们创建一个容量为20的数组,并用一个指针来记录栈顶元素。我们在构造方法里初始化栈顶指针为-1,代表着栈为空。
2、入栈
/***** 入栈 *****/
@Override
public boolean push(Object o) {
if(topOfStack >= (CAPACITY-1)){
System.out.println("栈满了");
return false;
}
arrayStack[++topOfStack] = o;
return true;
}
入栈的思路就是先将栈顶指针向上移一位,再直接赋值,前提需判断该栈是否满,当然栈满了,是不允许再进栈的。
3、出栈
/***** 出栈 *****/
@Override
public Object pop() {
if(topOfStack<0){
System.out.println("栈为空");
}
Object old = arrayStack[topOfStack];
arrayStack[topOfStack--] = null;
return old;
}
出栈的思路和入栈差不多,先判断栈是否为空,再将栈顶元素赋值给一个变量并将其记录,将其设置为空指针向下移一位。
4、
//查看当前栈顶元素
@Override
public Object peek() {
if(topOfStack<0){
System.out.println("栈为空");
}
return arrayStack[topOfStack];
}
@Override
public int size() {
return topOfStack+1;
}
@Override
public boolean isEmpty() {
return topOfStack<0;
}
链表——代码实现栈:(用双向链表)
1、链表节点类
static class Node<E>{
Object element;
Node prev;
Node next;
public Node(Object element){
this.element = element;
}
}
2、私有变量和构造方法
private Node<E> head;
private Node<E> tail;
private int size;
private static final int MAX_CAP = 100;
public MyLinkedStack(){
head = new Node<>(null);
tail = new Node<>(null);
head.next = tail;
tail.prev = head;
size = 0;
}
我们设置最大容量为100的栈,在构造方法里首先将其头尾节点的值设置为null,并互相指向对方。
3、进栈
/***** 进栈 *****/
@Override
public boolean push(Object o) {
if(size<MAX_CAP) {
Node<E> x = new Node<>(o);
Node<E> temp = head.next;
//head temp
x.prev = head;
x.next = temp;
head.next = x;
temp.prev = x;
size++;
return true;
}
return false;
}
思路是先判断栈是否满,然后创建一个节点将要入栈的值装入,再运用头指针获取第一个节点(头指针后面的那个节点),将新节点加入,成为第一个节点。(把头节点的prev,next分别指向head,第一个节点。再将头节点的next指向新节点,将第一个节点的prev也指向新节点)。
4、出栈
/***** 出栈 *****/
@Override
public Object pop() {
if (size>=1) {
Node<E> x = head.next;
Node<E> temp = x.next;
head.next = temp;
temp.prev = head;
Object need = x.element;
x.prev = x.next =null;
size--;
return need;
}
return null;
}
出栈和进栈思路差不多,主要思路就是将第一个节点删除,删除前把值记录下来并返回。
5、
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size==0;
}
/***** 查看栈顶元素 *****/
@Override
public Object peek() {
return head.next.element;
}
二、队列介绍
它与栈刚好相反,是一种先进先出的线性表,它有两个端口添加、删除元素,一个端口只能添加元素,被称为入队,该端口被为队尾,另一个端口只能删除,被称为出队,该端口被称为队头。
链表——实现队列:(双链表)
package pgs0205;
public class MLinkedQueue<E> {
public class Node<E>{
E element;
Node prev;
Node next;
public Node(E element){
this.element = element;
}
}
private static final int MAX_CAP = 20;
private Node<E> head;
private Node<E> tail;
private int size;
public MLinkedQueue(){
head = new Node<>(null);
tail = new Node<>(null);
head.next = tail;
tail.prev = head;
size = 0;
}
/***** 入队 *****/
public boolean enqueue(E element){
if(size<MAX_CAP) {
Node<E> x = new Node<>(element);
Node<E> temp = tail.prev;
x.prev = temp;
x.next = tail;
temp.next = x;
tail.prev = x;
size++;
return true;
}
return false;
}
/***** 出队 *****/
public E dequeue(){
if(size >=1){
size--;
return (E) head.next.element;
}
return null;
}
/***** 查看队列第一个 *****/
public E getFront(){
return (E) head.next.element;
}
/***** 获取队列长队 *****/
public int getSize(){
return size;
}
/***** 判断是否为空 *****/
public boolean isEmpty(){
return size==0;
}
}
主要把握队列的性质(先进先出),然后注意链表的增删。
数组——实现队列:
package pgs0205;
public class MyArrayQueue<E> {
private static final int MAX_CAP = 20;
private Object arrayQueue[];
private int topOfQueue;
private int botOfQueue;
public MyArrayQueue() {
arrayQueue = new Object[MAX_CAP];
}
/***** 入队 *****/
public void enqueue(E element) {
if (isFull()) {
System.out.println("队满");
}
arrayQueue[botOfQueue] = element;
botOfQueue = (botOfQueue + 1) % MAX_CAP;
}
/***** 出队 *****/
public E dequeue(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
E temp = (E) arrayQueue[topOfQueue];
topOfQueue = (topOfQueue+1)%MAX_CAP;
return temp;
}
/***** 查看对头元素 *****/
public E getFront() {
if(isFull()){
throw new RuntimeException("队列满了");
}
return (E) arrayQueue[topOfQueue];
}
/***** 获取队列长度 *****/
//查看队列的第一个元素
public E HeadNum(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
return (E) arrayQueue[topOfQueue];
}
/***** 判断是否为空 *****/
public boolean isEmpty() {
return topOfQueue==botOfQueue;
}
/***** 判断是否满 *****/
public boolean isFull() {
return (botOfQueue + 1) % MAX_CAP == topOfQueue; //最后一个位置没用到
}
/***** 显示队列所有数据 *****/
//显示队列的所有数据
public void ShowQueue(){
if(isEmpty()){
System.out.println("队列中没有数据");
return;
}
for(int i = topOfQueue;i < topOfQueue+CountQueue();i++){
System.out.println(arrayQueue[i%MAX_CAP]);
}
}
/***** 队列中有效元素个数 *****/
public int CountQueue(){
if(isEmpty()){
return 0;
}
return (botOfQueue+MAX_CAP-topOfQueue)%MAX_CAP;
}
// 本文中有几个需要注意的公式,就是判断队列是否满了:
//
// (topOfQueue+1)%MAX_CAP == botOfQueue
// 还要就是队尾指针和队头指针移动的操作:
//
// topOfQueue = (topOfQueue+1)%MAX_CAP;
// 查看队列中有效元素的个数:
//
// (topOfQueue+MAX_CAP-botOfQueue)%MAX_CAP
// 还有就是遍历队列:
//
// for(int i = botOfQueue;i < botOfQueue+CountQueue();i++){
// System.out.println(arrayQueue[i%MAX_CAP]);
// }
}
运用固定大小的数组实现队列,首先我们给定大小为20的数组,入队指针和出队指针都指向数组第一个位置即索引为0,当入队一个元素时,botOfQueue指针就会往后移一位,当出队一个元素时,topOfQueue指针也会往后移一位,这样数组前部分位置都空出来了,当位置不够时,指针又回到索引0位置,这样这个固定大小数组就得以实现重复使用。
注意:判断队列是否满 isFull();方法,判断依据是当botOfQueue指针+1刚好是topOfQueue指针位置则队列就满了。因此本队列真实大小容量为19。