zoukankan      html  css  js  c++  java
  • 数据结构----队列:顺序队列&顺序循环队列、链式队列、顺序优先队列

    一、队列的概念:

      队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在其一端进行插入操作在其另一端进行删除操作

    队列中允许进行插入操作的一端称为队尾,允许进行删除操作的一端称为队头。队列的插入操作通常称作入队列,队列的删除操作通常称作出队列

    下图是一个依次向队列中插入数据元素a0,a1,...,an-1后的示意图:

    faf7d1e6-07b1-44bb-9752-5fcc113a0648

    上图中,a0是当前 队头数据元素,an-1是当前 队尾数据元素。

    为了避免当只有一个元素时,对头和队尾重合使得处理变得麻烦,所以引入两个指针:front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样的话,当front指针等于rear时,此队列不是还剩一个元素,而是空队列。

    二、队列的抽象数据类型:

    数据集合:

      队列的数据集合可以表示为a0,a1,…,an-1,每个数据元素的数据类型可以是任意的类型。

    操作集合:

    (1)入队列append(obj):把数据元素obj插入队尾。

    (2)出队列delete():把队头数据元素删除并由函数返回。

    (3)取队头数据元素getFront():取队头数据元素并由函数返回。

    (4)非空否isEmpty():非空否。若队列非空,则函数返回false,否则函数返回true。

    三、循环顺序队列:

    线性表有顺序存储和链式存储,队列是一种特殊的线性表,同样也存在这两种存储方式。我们先来看一下队列的顺序存储。

    1、顺序队列的“假溢出”:

    607ede1a-91b5-4ac9-8850-07078379fddb

    上图中,front指针指向队头元素,rear指针指向队尾元素的下一个位置。图(d)中b、c、d出队后,front指针指向元素e,rear指针在数组外面。假设这个队列的总个数不超过5个,但目前如果接着入队的话,因数组末尾元素已经被占用,再向后加就会产生数组越界的错误,可实际上队列在下标为0、1、2、3、4的地方还是空闲的,我们把这种现象叫做“假溢出”。

    2、循环顺序队列:

        所以解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种逻辑上首尾相连的顺序存储结构称为循环队列

    如何判断循环队列究竟是空的还是满的:

      现在问题又来了,我们之前说,空队列时,front指针等于rear指针,那么现在循环队列满的时候,也是front等于rear,那么如何判断循环队列究竟是空的还是满的?有如下办法:

    • 办法1:设置一个标志位flag。初始时置flag=0;每当入队列操作成功就置flag=1;每当出队列操作成功就置flag=0。则队列空的判断条件为:rear == front && tag==0;队列满的判断条件为:rear = = front && tag= =1。
    • 办法2:保留一个元素的存储空间。此时,队列满时的判断条件为  (rear + 1) % maxSize == front;队列空的判断条件还是front == rear。
    • 办法3:设计一个计数器count,统计队列中的元素个数。此时,队列满的判断条件为:count > 0 && rear == front ;队列空的判断条件为count == 0。

    我们在接下来的代码中采用方法3来实现。

    3、代码实现:(循环顺序队列的创建)

    (1)Queue.java:(队列接口)

    //队列接口
    public interface Queue {
    
        //入队
        public void append(Object obj) throws Exception;
    
        //出队
        public Object delete() throws Exception;
    
        //获得队头元素
        public Object getFront() throws Exception;
    
        //判断对列是否为空
        public boolean isEmpty();
    }

    (2)CircleSequenceQueue.java:(循环顺序队列)

    //循环顺序队列
    public class CircleSequenceQueue implements Queue {
    
        static final int defaultSize = 10; //默认队列的长度
        int front;  //队头
        int rear;   //队尾
        int count;  //统计元素个数的计数器
        int maxSize; //队的最大长度
        Object[] queue;  //队列
    
        public CircleSequenceQueue() {
            init(defaultSize);
        }
    
        public CircleSequenceQueue(int size) {
            init(size);
        }
    
        public void init(int size) {
            maxSize = size;
            front = rear = 0;
            count = 0;
            queue = new Object[size];
        }
    
        @Override
        public void append(Object obj) throws Exception {
            // TODO Auto-generated method stub
            if (count > 0 && front == rear) {
                throw new Exception("队列已满!");
            }
            queue[rear] = obj;
            rear = (rear + 1) % maxSize;
            count++;
        }
    
        @Override
        public Object delete() throws Exception {
            // TODO Auto-generated method stub
            if (isEmpty()) {
                throw new Exception("队列为空!");
            }
            Object obj = queue[front];
            front = (front + 1) % maxSize;
            count--;
            return obj;
        }
    
        @Override
        public Object getFront() throws Exception {
            // TODO Auto-generated method stub
            if (!isEmpty()) {
                return queue[front];
            } else {
                return null;
            }
        }
    
        @Override
        public boolean isEmpty() {
            // TODO Auto-generated method stub
            return count == 0;
        }
    
    }

    (3)Test.java:

    public class Test {
        public static void main(String[] args) throws Exception {
    
            CircleSequenceQueue queue = new CircleSequenceQueue();
            queue.append("a");
            queue.append("b");
            queue.append("c");
            queue.append("d");
            queue.append("e");
            queue.append("f");
    
            queue.delete();
            queue.delete();
    
            queue.append("g");
    
            while (!queue.isEmpty()) {
                System.out.println(queue.delete());
            }
        }
    }

    运行效果:

    251fa2d6-120f-4b20-8754-f7a795b20cf1 

    4、循环队列应用:

      使用顺序循环队列和多线程实现一个排队买票的例子。而且,我们只允许这个队伍中同时排队的只有10个人,那就需要用到队列了。

        生产者(等候买票)

        消费者 (买票离开)

    这里面我们需要用到上面的Queue.java类和CircleSequenceQueue.java类。

    代码结构:

    62c99e55-fe55-41d0-8794-7a74a37ca826

    (3)WindowQueue.java:

    //卖票窗口
    public class WindowQueue {
    
        //卖票的队列
        int maxSize = 10;
        CircleSequenceQueue queue = new CircleSequenceQueue(maxSize);
        int num = 0; //统计卖票的数量,一天最多卖100张票。
        boolean isAlive = true; //判断是否继续卖票。
    
        //排队买票
        public synchronized void producer() throws Exception {
            if (queue.count < maxSize) {
                queue.append(num++); //等待买票的数量加1
                System.out.println("第" + num + "个客户排队等待买票!");
                this.notifyAll();//唤醒卖票的线程
            } else {
                try {
                    System.out.println("队列已满...请等待!");
                    this.wait();//队列满时,排队买票线程等待。
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    
        //卖票
        public synchronized void consumer() throws Exception {
            if (queue.count > 0) {
                Object obj = queue.delete();
                int temp = Integer.parseInt(obj.toString());
                System.out.println("第" + (temp + 1) + "个客户买到票离开队列!");
                //如果当前队列为空,并且卖出票的数量大于等于100,说明卖票结束
                if (queue.isEmpty() && this.num >= 100) {
                    this.isAlive = false;
                }
                this.notifyAll(); //唤醒排队买票的线程。
            } else {
                try {
                    System.out.println("队列已空...请等待!");
                    this.wait();//队列空时,卖票线程等待。
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    (4)Producer.java:

    //买票者
    public class Producer implements Runnable {
    
        WindowQueue queue;
    
        public Producer(WindowQueue queue) {
            this.queue = queue;
        }
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (queue.num < 100) {
                try {
                    queue.producer();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    
    }

    (5)Consumer.java:

    //卖票者
    public class Consumer implements Runnable {
    
        WindowQueue queue;
    
        public Consumer(WindowQueue queue) {
            this.queue = queue;
        }
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (queue.isAlive) {
                try {
                    queue.consumer();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    
    }

    (6)test.java:

    public class Test {
    
        public static void main(String[] args) throws Exception {
    
            WindowQueue queue = new WindowQueue();
    
            Producer p = new Producer(queue);//注意一定要传同一个窗口对象
            Consumer c = new Consumer(queue);
    
            //排队买票线程
            Thread pThread = new Thread(p);
            //卖票线程
            Thread cThread = new Thread(c);
    
            pThread.start(); //开始排队买票
            cThread.start();  //开始卖票
        }
    
    }

    注意第07行的注释。

    运行效果:

    bb9b709e-bc10-48ab-89e6-260ca38ae5d2

      

    四、链式队列:

        链式队列其实就是特殊的单链表,只不过它只能尾进头出而已。链式队列的存储结构如下图所示:

    6c1486eb-a189-413f-bce1-a8f516aede26

    1、链式队列的实现:

    (1)Node.java:结点类

    //结点类
    public class Node {
    
        Object element; //数据域
        Node next;  //指针域
    
        //头结点的构造方法
        public Node(Node nextval) {
            this.next = nextval;
        }
    
        //非头结点的构造方法
        public Node(Object obj, Node nextval) {
            this.element = obj;
            this.next = nextval;
        }
    
        //获得当前结点的后继结点
        public Node getNext() {
            return this.next;
        }
    
        //获得当前的数据域的值
        public Object getElement() {
            return this.element;
        }
    
        //设置当前结点的指针域
        public void setNext(Node nextval) {
            this.next = nextval;
        }
    
        //设置当前结点的数据域
        public void setElement(Object obj) {
            this.element = obj;
        }
    
        public String toString() {
            return this.element.toString();
        }
    }

    (2)Queue.java:

    //队列接口
    public interface Queue {
    
        //入队
        public void append(Object obj) throws Exception;
    
        //出队
        public Object delete() throws Exception;
    
        //获得队头元素
        public Object getFront() throws Exception;
    
        //判断对列是否为空
        public boolean isEmpty();
    }

    (3)LinkQueue.java:

    public class LinkQueue implements Queue {
    
        Node front; //队头
        Node rear;  //队尾
        int count; //计数器
    
        public LinkQueue() {
            init();
        }
    
        public void init() {
            front = rear = null;
            count = 0;
        }
    
        @Override
        public void append(Object obj) throws Exception {
            // TODO Auto-generated method stub
            Node node = new Node(obj, null);
    
            //如果当前队列不为空。
            if (rear != null) {
                rear.next = node; //队尾结点指向新结点
            }
    
            rear = node; //设置队尾结点为新结点
    
            //说明要插入的结点是队列的第一个结点
            if (front == null) {
                front = node;
            }
            count++;
        }
    
        @Override
        public Object delete() throws Exception {
            // TODO Auto-generated method stub
            if (isEmpty()) {
                new Exception("队列已空!");
            }
            Node node = front;
            front = front.next;
            count--;
            return node.getElement();
        }
    
        @Override
        public Object getFront() throws Exception {
            // TODO Auto-generated method stub
            if (!isEmpty()) {
                return front.getElement();
            } else {
                return null;
            }
        }
    
        @Override
        public boolean isEmpty() {
            // TODO Auto-generated method stub
            return count == 0;
        }
    
    }

    (4)Test.java:

    public class Test {
    
        public static void main(String[] args) throws Exception {
    
            LinkQueue queue = new LinkQueue();
            queue.append("a");
            queue.append("b");
            queue.append("c");
            queue.append("d");
            queue.append("e");
            queue.append("f");
    
            queue.delete();
            queue.delete();
    
            queue.append("g");
    
            while (!queue.isEmpty()) {
                System.out.println(queue.delete());
            }
        }
    }

    运行效果:

    7ee743ea-3e2a-4795-b7b3-10dfef4f3e9b 

    2、链式队列的应用:

    题目:

      编写一个判断一个字符串是否是回文的算法。

    思路:

      设字符数组str中存放了要判断的字符串。把字符数组中的字符逐个分别存入一个队列和栈中,然后逐个出队和出栈比较出队的字符与出栈的字符是否相同,若全部相等则该字符串为回文。

    代码实现:

      这里面需要用到上面一段中的LinkQueue类。代码结构如下:

    3a04b30d-6ed1-4556-a408-afc8b4c6068a

    (4)Stack.java:栈接口

    //栈接口
    public interface Stack {
    
        //入栈
        public void push(Object obj) throws Exception;
    
        //出栈
        public Object pop() throws Exception;
    
        //获得栈顶元素
        public Object getTop() throws Exception;
    
        //判断栈是否为空
        public boolean isEmpty();
    }

    (5)LinkStack.java:

    public class LinkStack implements Stack {
    
        Node head;  //栈顶指针
        int size;  //结点的个数
    
        public LinkStack() {
            head = null;
            size = 0;
        }
    
        @Override
        public Object getTop() throws Exception {
            // TODO Auto-generated method stub
            return head.getElement();
        }
    
        @Override
        public boolean isEmpty() {
            // TODO Auto-generated method stub
            return head == null;
        }
    
        @Override
        public Object pop() throws Exception {
            // TODO Auto-generated method stub
            if (isEmpty()) {
                throw new Exception("栈为空!");
            }
            Object obj = head.getElement();
            head = head.getNext();
            size--;
            return obj;
    
        }
    
        @Override
        public void push(Object obj) throws Exception {
            // TODO Auto-generated method stub
            head = new Node(obj, head);
            size++;
        }
    
    }

    (6)Test.java:测试类

    public class Test {
    
        public static void main(String[] args) throws Exception {
    
            String str1 = "ABCDCBA"; //是回文
            String str2 = "ABCDECAB"; //不是回文
    
            try {
                if (Test.isHuiWen(str1)) {
                    System.out.println(str2 + ":是回文!");
                } else {
                    System.out.println(str2 + ":不是回文!");
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
    
        //方法:判断字符串是否回文
        public static boolean isHuiWen(String str) throws Exception {
            int n = str.length();
            LinkStack stack = new LinkStack();//创建堆栈
            LinkQueue queue = new LinkQueue();//创建队列
            for (int i = 0; i < n; i++) {
                stack.push(str.subSequence(i, i + 1)); //把字符串每个字符压进堆栈
                queue.append(str.subSequence(i, i + 1));//把字符串每个字符压入队列
            }
            while (!queue.isEmpty() && !stack.isEmpty()) {
                if (!queue.delete().equals(stack.pop())) {  //出队列,出栈,同时判断是否相同
                    return false;
                }
            }
    
            return true;
        }
    
    }

    3、循环队列和链式队列的比较:

    (1)从时间上看,它们的基本操作都是常数时间,即O(1)的。不过循环队列是事先申请好空间,使用期间不释放;而链式队列,每次申请和释放结点也会存在一定的时间开销,如果入栈和出栈比较频繁,则两者还是有细微的差别。

    (2)从空间上看,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链式队列不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链式队列更加灵活。

    总结:总的来说,在可以确定队列长度的最大值的情况下,建议用循环队列,如果你无法估计队列的长度,那就用链式队列。

    五、优先级队列:

      优先级队列是带有优先级的队列。

        用顺序存储结构实现的优先级队列称作顺序优先级队列

        用链式存储结构存储的优先级队列称作链式优先级队列

    顺序优先级队列顺序循环队列相比主要有两点不同

    (1)对于顺序优先级队列来说,出队列操作不是把队头数据元素出队列,而是把队列中优先级最高的数据元素出队列。(入队操作没区别)

    (2)对于顺序优先级队列来说,数据元素由两部分组成,一部分是原先意义上的数据元素,另一部分是优先级。通常设计优先级为int类型的数值,并规定数值越小优先级越高

    1、顺序优先队列的实现:

    设计顺序优先级队列分为两个类:

      数据元素类

      优先级队列类

    代码实现:

    (1)Element.java:

    //优先级队列元素类
    public class Element {
    
        private Object element; // 数据
        private int priority; // 优先级
    
        public Element(Object obj, int priority) {
            this.element = obj;
            this.priority = priority;
        }
    
        public Object getElement() {
            return element;
        }
    
        public void setElement(Object element) {
            this.element = element;
        }
    
        public int getPriority() {
            return priority;
        }
    
        public void setPriority(int priority) {
            this.priority = priority;
        }
    
    }

    (2)Queue.java:

    //队列接口
    public interface Queue {
    
        //入队
        public void append(Object obj) throws Exception;
    
        //出队
        public Object delete() throws Exception;
    
        //获得队头元素
        public Object getFront() throws Exception;
    
        //判断对列是否为空
        public boolean isEmpty();
    }

    (3)PrioritySequenceQueue.java:

    //优先级队列
    public class PrioritySequenceQueue implements Queue {
    
        static final int defaultSize = 10; //默认队列长度
        int front; //队头
        int rear;  //队尾
        int count;  //计数器
        int maxSize; //队列最大长度
        Element[] queue; //队列
    
        public PrioritySequenceQueue() {
            init(defaultSize);
        }
    
        public PrioritySequenceQueue(int size) {
            init(size);
        }
    
        public void init(int size) {
            maxSize = size;
            front = rear = 0;
            count = 0;
            queue = new Element[size];
        }
    
        @Override
        public void append(Object obj) throws Exception {
            // TODO Auto-generated method stub
            //如果队列已满
            if (count >= maxSize) {
                throw new Exception("队列已满!");
            }
            queue[rear] = (Element) obj;
            rear++;
            count++;
        }
    
        @Override
        public Object delete() throws Exception {
            // TODO Auto-generated method stub
            if (isEmpty()) {
                throw new Exception("队列为空!");
            }
            //默认第一个元素为优先级最高的。
            Element min = queue[0];
            int minIndex = 0;
            for (int i = 0; i < count; i++) {
                if (queue[i].getPriority() < min.getPriority()) {
                    min = queue[i];
                    minIndex = i;
                }
            }
    
            //找的优先级别最高的元素后,把该元素后面的元素向前移动。
            for (int i = minIndex + 1; i < count; i++) {
                queue[i - 1] = queue[i]; //移动元素
            }
            rear--;
            count--;
            return min;
        }
    
        @Override
        public Object getFront() throws Exception {
            // TODO Auto-generated method stub
            if (isEmpty()) {
                throw new Exception("队列为空!");
            }
            //默认第一个元素为优先级最高的。
            Element min = queue[0];
            int minIndex = 0;
            for (int i = 0; i < count; i++) {
                if (queue[i].getPriority() < min.getPriority()) {
                    min = queue[i];
                    minIndex = i;
                }
            }
            return min;
        }
    
        @Override
        public boolean isEmpty() {
            // TODO Auto-generated method stub
            return count == 0;
        }
    
    }

    2、代码测试:

      设计一个程序模仿操作系统的进程管理问题。进程服务按优先级高的先服务,优先级相同的先到先服务的原则管理。

      模仿数据包含两个部分:进程编号和优先级。如下有五个进程:

        1      30

        2      20

        3      40

        4      20

        5      0       ----------优先级最高,先服务

    (4)Test.java:

    public class Test {
    
        public static void main(String[] args) throws Exception {
    
            PrioritySequenceQueue queue = new PrioritySequenceQueue();
            Element temp;
    
            //五个进程入队
            queue.append(new Element(1, 30));
            queue.append(new Element(2, 20));
            queue.append(new Element(3, 40));
            queue.append(new Element(4, 20));
            queue.append(new Element(5, 0));
    
            //按照优先级出队。
            System.out.println("编号  优先级");
            while (!queue.isEmpty()) {
                temp = (Element) queue.delete();
                System.out.println(temp.getElement() + " " + temp.getPriority());
            }
        }
    }

    运行效果:

    f22bc718-69dd-488e-a893-dd93109da4cf

    转载自:https://www.cnblogs.com/smyhvae/p/4793339.html

  • 相关阅读:
    Swift3 重写一个带占位符的textView
    Swift3 使用系统UIAlertView方法做吐司效果
    Swift3 页面顶部实现拉伸效果代码
    Swift3 倒计时按钮扩展
    iOS 获取当前对象所在的VC
    SpringBoot在IDEA下使用JPA
    hibernate 异常a different object with the same identifier value was already associated with the session
    SpringCloud IDEA 教学 番外篇 后台运行Eureka服务注册中心
    SpringCloud IDEA 教学 (五) 断路器控制台(HystrixDashboard)
    SpringCloud IDEA 教学 (四) 断路器(Hystrix)
  • 原文地址:https://www.cnblogs.com/llfy/p/9431140.html
Copyright © 2011-2022 走看看