数据结构(Java)---队列

1.简介

       队列是一种操作受限的线性表,和栈类似,队列的限制主要是运行在表的一端进行插入操作,而在表的另一端进行删除的操作(栈的操作受限是只能在一端进行插入和删除)。队列中进行插入的一端称为队尾 ,删除的一端称为队首
       队列的操作规则是先进先出,先入队的元素先出队,所以队列又被称为先进先出表。队列分为线性队,链队,循环队列和双端队列。

2.顺序队

2.1 简介

       顺序队,采用顺序存储方式的队列称为顺序队,就是在内存中分配一段连续的存储空间来存放元素,常用的方式就是采用数组来实现。

2.2 相关操作

数据结构(Java)---队列_第1张图片
       顺序队列的实现和出入队的方法可以是:先定义一个数组用来存储数据,定义一个front和一个rear变量来辅助元素的出入队。当元素入队时,rear指针(变量)移动,然后将元素放在数组rear位置下(数组名[rear]),当rear=maxsize(数组大小)-1时,队列满了。元素出队时,front指针移动,然后将数组front位置下的元素删掉,当front=rear时,队列为空。

2.3 代码实现

package Structures;

import java.util.Scanner;

public class ArrayQueue {
    public static void main(String[] args) {
        Queue queue = new Queue(5);
        String key;
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);

        while (loop){
            System.out.println("show: 输出队列");
            System.out.println("exit: 退出");
            System.out.println("add: 入队");
            System.out.println("get: 出队");
            System.out.println("请选择相关操作");
            while (loop){
                key = scanner.next();
                switch (key){
                    case "show":
                        queue.list();
                        break;
                    case "exit":
                        scanner.close();
                        loop = false;
                        break;
                    case "add":
                        System.out.println("请输入值:");
                        int i = scanner.nextInt();
                        queue.addQueue(i);
                        break;
                    case "get":
                        try{
                            int pop = queue.getQueue();
                            System.out.print(pop);
                        }catch (Exception e){
                            System.out.println(e.getMessage());
                        }
                        break;
                }
            }
        }
    }
}

class Queue {
    private int[] data;
    private int front;
    private int rear;
    private int maxSize;

    public Queue(int maxSize) {
        this.maxSize = maxSize;
        data = new int[maxSize];
        front = -1;
        rear = -1;
    }

    //判断队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }

    //判断队列是否满
    public boolean isFull(){
        return rear == maxSize-1;
    }

    //入队
    public void addQueue(int n){
        if(isFull()){
            System.out.println("队列已满");
            return;
        }
        rear++;
        data[rear] = n;
    }

    /*出队(注意:这里并不是真正的出队,只是将front移动返回值而已,实际上值还存在数组中,虽然数组是一个对象,但JavaApi并没有相对应的增删改查方法,可以借助ArrayUtils工具来将数组转为集合来操作)*/
    public int getQueue(){
        if(isEmpty()){
            throw new RuntimeException("队列已经空了");
        }
        front++;
        return data[front];
    }

    //遍历队列
    public void list(){
        if (isEmpty()){
            System.out.println("队列为空");
            return;
        }
        for (int i = 0; i < data.length; i++) {
            System.out.print(data[i]);
        }
    }
}

数据结构(Java)---队列_第2张图片

3.环形队列

3.1 简介

数据结构(Java)---队列_第3张图片
       在顺序队列中,由于是使用front和rear指针(变量)来辅助队列的进队和出队,在判断队列满时使用rear=maxsize(数组大小)-1,但这样子会出现**假溢出(尽管元素出队了,但随着front指针的移动,数组产生一些空位置)**的情况,导致数组不可复用的情况出现,而环形队列则可以解决这个问题。
       环形队列主要是在顺序队的基础上,将数组的前端和后端连起来,形成一个环形数组,然后遵从队列的先进先出原则,被称为环形队列。

3.2 相关操作

数据结构(Java)---队列_第4张图片
       在创建循环队列时,和顺序队列不同的是,在循环队列中,front和rear的初始值都为0,而判断队满和队空的条件也不同,元素入队和出队的指针变量移动也不同(取余)。

队列的创建: 在循环队列中,front和rear的指针变量都等于0,直接指向数组的第一个元素,在循环队列中,数组存储元素的个数大小实际上比数组最大值少一个,因为要留一个空位来作为标记,比如上图的front和rear同时指向的那一个位置就是用来作为标记的。

public XQueue(int maxSize){
    this.maxSize = maxSize;
    front = 0;
    rear = 0;
    arr = new int[maxSize];
}

判断队空队满: 如上图所示:当rear+1= =front时,这时候表示队满了,因为循环队列要留一个位置来作为标志位,而在循环队列中,判断队满的条件是(rear+1)%maxSize == front ,因为是循环队列,rear的值是可以一直增长的,但(rear+1)% maxSize的范围一直在maxSize之内,比如说maxSize为4,那么(rear+1)%maxSize*的值就是0~4,范围在数组大小限制之内。而循环队列的判空的条件则是rear==front。

//队列判空
public boolean isFull(){
    return (rear+1)%maxSize == front;
}
    
//队列是否满
public boolean isEmpty(){
    return rear == front;
}

入队: 循环队列的入队分为两部分,首先是赋值然后是rear移位,之所以和顺序队不同是因为rear赋值时循环队列已经指向了数组的第一个位置0了,而rear的移位也和顺序队的不同,顺序队中rear的移位主要是rear = (rear+1)%maxSize,这样子rear的值尽管会一直增加,但+1并%maxSize后,rear最后的值都会在数组最大长度范围之内,不会造成数组下标越界的问题。

//进队
public void add(int n) {
    if (isFull()) {
        System.out.println("队列满了");
        return;
    }
    arr[rear] = n;
    //rear后移((rear+1)这里是会一直加的)
    rear = (rear + 1) % maxSize;
}

出队: 在循环队列中元素的出队上,首先是获取到队首的元素,然后front指针移位,这里的front指针移位不是front++的了,而是front = (front+1)%maxSize,front最终的取值范围就不会超过数组长度,这样子的话front指针就会重新指回数组里的位置,从而形成环形队列。

//出队
public int get(){
    if (isEmpty()){
        throw new RuntimeException("队列为空");
    }
    int i = arr[front];
    front = (front+1)%maxSize;
    return i;
}

遍历: 循环队列中遍历数组的话,可以从队列中有效元素开始遍历,也就是从front开始,然后结束条件则是front加上队列中有效元素的个数,循环队列中计算队列中有效元素个数的是(rear+maxSize-front)%maxSize 。

//遍历
public void list(){
    if (isEmpty()){
        System.out.println("队空");
    }
    for (int i = front; i <front+(rear+maxSize-front)%maxSize; i++) {
        System.out.println(arr[i%maxSize]);
    }
}

3.3 代码实现

package Structures;

import java.util.Scanner;

public class XHQueue {
    public static void main(String[] args) {
        XQueue queue = new XQueue(5);
        String key;
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);

        while (loop){
            System.out.println("show: 输出队列");
            System.out.println("exit: 退出");
            System.out.println("add: 入队");
            System.out.println("get: 出队");
            System.out.println("请选择相关操作");
            while (loop){
                key = scanner.next();
                switch (key){
                    case "show":
                        queue.list();
                        break;
                    case "exit":
                        scanner.close();
                        loop = false;
                        break;
                    case "add":
                        System.out.println("请输入值:");
                        int i = scanner.nextInt();
                        queue.add(i);
                        break;
                    case "get":
                        try{
                            int pop = queue.get();
                            System.out.print(pop);
                        }catch (Exception e){
                            System.out.println(e.getMessage());
                        }
                        break;
                }
            }
        }
    }
}

class XQueue {
    private int maxSize;
    private int front;
    private int rear;
    private int[] arr;

    public XQueue(int maxSize) {
        this.maxSize = maxSize;
        front = 0;
        rear = 0;
        arr = new int[maxSize];
    }

    //队列判空
    public boolean isFull() {
        return (rear + 1) % maxSize == front;
    }

    //队列是否满
    public boolean isEmpty() {
        return rear == front;
    }

    //进队
    public void add(int n) {
        if (isFull()) {
            System.out.println("队列满了");
            return;
        }
        arr[rear] = n;
        //rear后移((rear+1)这里是会一直加的)
        rear = (rear + 1) % maxSize;
    }

    //出队
    public int get(){
        if (isEmpty()){
            throw new RuntimeException("队列为空");
        }
        int i = arr[front];
        front = (front+1)%maxSize;
        return i;
    }

    //遍历
    public void list(){
        if (isEmpty()){
            System.out.println("队空");
        }
        for (int i = front; i <front+((rear+maxSize-front)%maxSize); i++) {
            System.out.println(arr[i%maxSize]);
        }
    }
}

数据结构(Java)---队列_第5张图片

4.链队

4.1 简介

       链队主要是使用链表来实现队列,在单链表中,我们可以指定结点的插入和删除方式来实现队列的先进先出规则,即使用尾插法插入结点(入队)和在头结点处删除结点(出队),从而实现链队。

4.2 相关操作

数据结构(Java)---队列_第6张图片
       如上所示,在实现链队的过程中,首先创建一个带有front和rear的头结点(也可以定义一个front首节点和一个rear尾结点,将链队理解为带有头结点和尾结点的单链表),当元素进队时,采用尾插法的方式插入结点,此时使用的辅助指针变量的是rear,因为rear主要是指向rear尾部的,当元素出队时,删除front指针变量指向的结点,就是front后面的那一个结点。

入队: 采用尾插法的方式插入数据结点,当队列为空时,将front和rear都指向新结点,当队列不为空时,直接使用尾结点rear来进行插入即可。

public void add(Node node) {
    if (isEmpty()) {
        rear = front = node;
        length++;
    } else {
        rear.next = node;
        rear = node;
        length++;
    }
}

出队: 链队中的出队分为两种情况,当只剩下一个元素时,出队后将front和rear置空,当队列中有超过两个元素时,只需将front指向下一个元素(就是队列中的第二个元素)即可。

public void pop() {
    if (!isEmpty()) {
        if (length == 1) {
            front = rear = new Node("", "");
            length--;
        } else {
            front = front.next;
            length--;
        }
    }
}

4.3 代码实现

package Structures;

import java.util.EmptyStackException;

public class LinkedQueue {
    public static void main(String[] args) {
        Node node01 = new Node("1", "1");
        Node node02 = new Node("2", "2");
        Node node03 = new Node("3", "3");

        LQueue lQueue = new LQueue();
        lQueue.add(node01);
        lQueue.add(node02);
        lQueue.add(node03);

        lQueue.list();
        lQueue.pop();
        lQueue.list();
        lQueue.pop();
        lQueue.list();
    }

}

//结点信息
class Node {
    public String id;
    public String name;
    public Node next;

    public Node(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                "}";
    }
}

class LQueue {
    //创建首节点和尾结点
    Node front = new Node("", "");
    Node rear = new Node("", "");
    int length = 0;

    public boolean isEmpty() {
        return length == 0;
    }

    public void add(Node node) {
        if (isEmpty()) {
            rear = front = node;
            length++;
        } else {
            rear.next = node;
            rear = node;
            length++;
        }
    }

    public void pop() {
        if (!isEmpty()) {
            if (length == 1) {
                front = rear = new Node("", "");
                length--;
            } else {
                front = front.next;
                length--;
            }
        }
    }

    //输出链表
    public void list() {
        Node temp = front;
        while (true) {
            if (temp == null) {
                break;
            }
            System.out.print(temp);
            temp = temp.next;
        }
        System.out.println();
    }
}

数据结构(Java)---队列_第7张图片

5.双端队列

5.1 简介

数据结构(Java)---队列_第8张图片
       双端队列是指两端都可以进行进队和出队操作的队列,和普通的队列不同,普通的队列只允许在队尾进队和队首出队,而双端队列则是允许可以在两端进行操作的队列。双端队列可以使用顺序队(循环队列)和链队来实现。
       在双端队列中,进队时前端进的元素排列在队列中后端进的元素的前面,就是说在前端进的元素排在前面,但队列中元素的出队排序上,无论是前端出的还是后端出的,先出的都排在后出的前面。

       双端队列可以看做是队列和栈的结合体,他既可以先进先出也可以后进先出。在双端队列中,可以将双端队列变成普通的队列也可以变成栈来使用,这些也被称为受限的双端队列。比如:前端进后端出就是队列,前端进前端出就是栈。可以根据自己的需求来确定双端队列最后的形式(先进先出还是后进先出)。

5.2 相关操作

       在双端队列的实现中,大部分都是使用循环队列来实现的,这样子可以解决数组假溢出的问题,同时循环队列中也有front和rear两个指针变量,对双端队列的操作可以比较容易实现。

       其实双端队列使用循环队列来实现的话,可以理解为:front和rear同时指向了一个标志位,将这个标志位当成一个站在中间的人,前端进队的话就往左加,后端进队的话就往右加。

前端进队: 在前端入队上,主要是将front指针往前移动,从图中可以看出,front指针的移动从0指向了3,front的取值主要是(front-1+maxSize)%maxSize,这样子就可以取到相对应的数组下标值。
数据结构(Java)---队列_第9张图片

//前端入队
public void addFront(int n) {
    if (isFull()) {
        System.out.println("队列已满");
        return;
    }
    //新元素插到原先元素的前面,先移动指针
    front = (front - 1 + maxSize) % maxSize;
    data[front] = n;
}

后端进队: 后端进队就和循环队列的进队方式类似了,将rear指针往前移动一位即可,即rear= (rear+1)%maxSize。注意要先赋值再移位,因为rear指针要指向的是该元素的下一个位置。
数据结构(Java)---队列_第10张图片

//后端进队
public void addRear(int n) {
    if (isFull()) {
        System.out.println("队列已满");
        return;
    }
    //rear一开始指向的是一个空位置,先赋值
    data[rear] = n;
    rear = (rear + 1) % maxSize;
}

前端出队: 将front指针向前移动一位即可((front+1)%maxSize),注意的是,即使指针变量移动了一位,当实际上在数组中存储的数据还是存在的,并没有删除,可以通过后面添加进来的元素进行覆盖。
数据结构(Java)---队列_第11张图片

//前端出队
public void popFront() {
    if (isEmpty()) {
        System.out.println("队空");
        return;
    }
    int datum = data[front];
    front = (front + 1) % maxSize;
    System.out.println("出队的元素为:" + datum);
}

后端出队: 将rear指针向后移动一位(rear-1+maxSize)%maxSize,之所以在括号里面加上maxSize,是因为rear-1有可能出现负数。
数据结构(Java)---队列_第12张图片

//后端出队
public void popRear() {
    if (isEmpty()) {
        System.out.println("队空");
        return;
    }
    rear = (rear - 1 + maxSize) % maxSize;
    int datum = data[rear];
    System.out.println("出队的元素为:" + datum);
}

5.3 代码实现

package Structures;

import java.util.Scanner;

public class DoubleQueue {
    public static void main(String[] args) {
        DQueue dQueue = new DQueue(5);
        String key;
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);

        while (loop) {
            System.out.println("show: 输出队列");
            System.out.println("exit: 退出");
            System.out.println("addFront: 前端入队");
            System.out.println("getFront: 前端出队");
            System.out.println("addRear: 后端入队");
            System.out.println("getRear: 后端出队");
            System.out.println("请选择相关操作");
            while (loop) {
                key = scanner.next();
                switch (key) {
                    case "show":
                        dQueue.list();
                        break;
                    case "exit":
                        scanner.close();
                        loop = false;
                        break;
                    case "addFront":
                        System.out.println("请输入值:");
                        int i = scanner.nextInt();
                        dQueue.addFront(i);
                        break;
                    case "addRear":
                        System.out.println("请输入值:");
                        int a = scanner.nextInt();
                        dQueue.addRear(a);
                        break;
                    case "getFront":
                        System.out.println("请输入值:");
                        dQueue.popFront();
                        break;
                    case "getRear":
                        System.out.println("请输入值:");
                        dQueue.popRear();
                        break;
                }
            }
        }
    }
}

class DQueue {
    private int maxSize;
    private int front;
    private int rear;
    private int[] data;

    public DQueue(int maxSize) {
        this.maxSize = maxSize;
        front = rear = 0;
        data = new int[maxSize];
    }

    //判断队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }

    //判断队列是否满了
    public boolean isFull() {
        return front == (rear + 1) % maxSize;
    }

    //前端入队
    public void addFront(int n) {
        if (isFull()) {
            System.out.println("队列已满");
            return;
        }
        //新元素插到原先元素的前面,先移动指针
        front = (front - 1 + maxSize) % maxSize;
        data[front] = n;
    }

    //后端进队
    public void addRear(int n) {
        if (isFull()) {
            System.out.println("队列已满");
            return;
        }
        //rear一开始指向的是一个空位置,先赋值
        data[rear] = n;
        rear = (rear + 1) % maxSize;
    }

    //前端出队
    public void popFront() {
        if (isEmpty()) {
            System.out.println("队空");
            return;
        }
        int datum = data[front];
        front = (front + 1) % maxSize;
        System.out.println("出队的元素为:" + datum);
    }

    //后端出队
    public void popRear() {
        if (isEmpty()) {
            System.out.println("队空");
            return;
        }
        rear = (rear - 1 + maxSize) % maxSize;
        int datum = data[rear];
        System.out.println("出队的元素为:" + datum);
    }

    //遍历队列
    public void list() {
        if (isEmpty()) {
            System.out.println("队空");
        }
        for (int i = front; i < front + ((rear + maxSize - front) % maxSize); i++) {
            System.out.println(data[i % maxSize]);
        }
    }
}

你可能感兴趣的:(数据结构和算法,java,数据结构,算法)