数据结构——队列(java实现)及相应的oj题


前言


队列

队列的概念

队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表,一种先进先出的数据结构。
队尾:允许插入的一端。
队头:允许删除的一端。

队列的实现

队列的层次实现可以是链表,也可以是数组。

队列的链表实现

队列
如果采用单链表实现,若使用头插与尾删,则出队的时间复杂度为O(n),若使用头删与尾插,且具有last标记(可看作指针),则时间复杂度为O(1).
如果采用双链表实现,则时间复杂度一定为O(1).

实现的方法与属性
public class MyQueue {

    ListNode first;   //创建指向队列首部的标记
    ListNode last;   //创建指向队列尾部的标记

	//入队列
    public void offer(int val) {}
	//出队列
    public int poll() {}
    //获取队头元素 但是不删除
    public int peek() { }
    //判空
    public boolean isEmpty() { } 
    //获取队列元素个数
    public int size(){}      
}
内部类实现节点
 static  class  ListNode{
        //设定一个节点对象
          int data ;
          ListNode prev ;
          ListNode next;

        public ListNode(int data) {
            this.data = data;
        }
    }
入队列

实现思想:采用头插法,如果为空,则使得头标记与尾标记均指向新节点
不为空,则进行头插的操作。

   public void offer(int data) {
            //采用头插法
            ListNode cur = new ListNode(data);
            if(isEmpty()){
                first = last = cur;
            }else {
                cur.next = first;
                first.prev = cur;
                first = cur;
            }

        }
出队列

实现思路:

1 . 先判断队列是否为空,队列为空抛异常。
2. 队列不为空,将尾标记指向尾节点的前一个节点,然后将前一个节点的next指针置为空,删除尾结点。
因为我队入采取的是头插,所以队出采取尾删,如果大家采取尾插,则队出就采取头删了。

  public int poll() {
            try {
                if (isEmpty()) {
                    throw new EmptyException("空队列异常!!!");
                }
            }catch (EmptyException e){
                e.printStackTrace();
            }
            int ret = last.data;
            last = last.prev;
            last.next = null;  // 删除尾结点
            return ret;
        }
获取队头元素但不删除

获取队头元素 但是不删除
实现思路:

1.先判断队列是否为空,队列为空抛异常。
2 .队列不为空,返回尾节点的数据。(因为我采取的是链表尾部的队首)

  public int peek() {
            //获取队头元素
            try {
                if (isEmpty()) {
                    throw new EmptyException("空队列异常!!!");
                }
            }catch (EmptyException e){
                e.printStackTrace();
            }
            return last.data;
        }
判空

思想:直接返回头是否为空就行。

public boolean isEmpty(){
	return first == null;
}
获取队列元素个数

思想:直接遍历链表即可。

public int size(){
              //遍历整个链表
            ListNode cur = first;
            int count = 0 ;
            while (cur!=null){
                count++;
                cur = cur.next;
            }
            return  count;
        }

队列的数组实现

如果采用普通的数组实现队列,在下标大的一端进行插入数据,在下标小的一端删除数据,
在这里插入图片描述
我们可以在删除一个数据后,将所有的数据向左移动一位,但是时间复杂度就变成了O(N),能否用数组实现一个时间复杂度为O(1),则不浪费空间的队列呢?——即循环队列。

循环队列

循环队列图:
在这里插入图片描述
实现思想:我们有front标记指向队列的首部,用rear标记指向队列的尾部,用于指向要插入数据的位置,当下标的值超出数组长度时,便用求模计算找回对应的位置。

方法属性实现:
class MyCircularQueue {
        //实现的数组
        private  int[] elements;
        //指向首元素的标记
        int first = 0;
        //指向尾元素的标记
        int last = 0 ;
        //有效数组元素的个数
        int size = 0;
	//构造器,设置队列长度为 k
    public MyCircularQueue(int k) {}
    // 向循环队列插入一个元素。如果成功插入则返回真。
    public boolean enQueue(int value) {}
    //从循环队列中删除一个元素。如果成功删除则返回真。
    public boolean deQueue() {}
    //从队首获取元素。如果队列为空,返回 -1 
    public int Front() {}
    //获取队尾元素。如果队列为空,返回 -1 。
    public int Rear() {}
    //检查循环队列是否为空。
    public boolean isEmpty() {}
    //检查循环队列是否已满。
    public boolean isFull() {}
};
构造方法
 public MyCircularQueue(int k) {
        //用数组实现一个循环队列
        this.elements = new int[k];

    }
向循环队列插入一个元素,成功插入则为真。

实现思想:

  1. 判断队列是否已满,满了就返回false。
  2. 不满就在last处放。
  3. 在放完之后,将last的值+1然后模除队列的长度
 public boolean enQueue(int value) {
        //向循环队列中插入一个元素
        //先判断循环队列是否满
        if(isFull()){
            return false;
        }
        //未满
        this.elements[last] = value;
        last = (last+1)% elements.length;
        size ++;
        return true;
    }
从循环队列中删除一个元素,成功删除则为真

实现思想:

  1. 判断队列是否为空,空就返回false。
  2. 不空就直接将front指向下一个位置。
  3. 因为是循环队列,所以front+1再对数组长度进行求模。
public boolean deQueue() {
        //从队列中删除一个元素,删除队首的元素
        if(isEmpty()){
            return false;
        }
        //如果队列不为空,删除队首的元素,first+1,
        first = (first +1)% elements.length ; //必须保证first值到最大值时,能够再返回起点
        size = size-1;
        return true;

    }
从队首获取元素,如果队列为空,返回-1

实现思路:

  1. 先判断队列是否为空,为空返回-1。
  2. 不为空,返回front下标对应值。
  public int Front() {
        if(isEmpty()){
            return -1;
        }
        //如果队列不为空,返回队首元素
        return elements[first];
    }
获取队尾元素。如果队列为空,返回 -1

实现思想:

  1. 如果队列不为空,返回队尾元素
  2. 队尾元素的下标不一定为last -1 ,如果last 正好此时为0,那么last-1的结果为-1
    按照求模的方法:(last+数组长度-1)%数组长度,获取当前last之前的值
 public int Rear() {
        if(isEmpty()){
            return -1;
        }
        return elements[(last + elements.length-1 )%elements.length];
    }
检查循环队列是否为空

实现思想:通过size,判断size是否为0,来判断循环队列是否为空。

public boolean isEmpty() {
        //判断空,满足头标记与尾标记都相等,且无数值
        if( size ==0){
            return true;
        }
        return false;

    }
判断循环队列是否已满

实现思想:当front标记与last标记相同,且size不为0时,说明循环队列已满。

 public boolean isFull() {
        if(first ==last && size>0){
            return true;
        }
        return false;
    }
}

双端队列(Deque)

双端队列如图:
在这里插入图片描述
指可以两边都可以插入,都可以删除的队列。
Deque是一个接口,在java中有两个类实现了此接口

  1. ArrayDeque类,此类并没有在集合框架中
  2. LinkedList类
    ArrayDeque是双端队列的线性实现。
    LinkedList是双端队列的链式实现。

这两个类的对象可以当做栈与队列使用,因为他们实现了栈与队列的方法。

Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

队列Oj题

1. 用队列实现栈

实现思想:
1 . 队列是先进先出操作,栈是先进后出,所以一个队列不可能实现栈,我们采用两个队列实现栈
2. 入栈,则直接将数据插入队列1即可
3. 出栈:将队列1中的size-1个元素,全部放入到队列2中去,剩余的一个元素,则进行出队即可。
如果再进行出栈,则将队2中的size-1个元素,放入到队1中去,将剩余的一个元素再进行出队
在这里插入图片描述

class MyStack {
    Queue<Integer> qu1 = new LinkedList<Integer>();
    Queue<Integer> qu2 = new LinkedList<Integer>();

    public MyStack() {

    }

    public void push(int x) {
        //将数据压入有数据的队列中
        if(!qu1.isEmpty()){
            qu1.offer(x);
        }else if(!qu2.isEmpty()){
            qu2.offer(x);
        }else{
            qu1.offer(x);
        }

    }
    public int pop() {
        //执行出栈操作,先判断栈是否为空
        if (empty()) {
            return -1;
        }
        //如果不为空
        if (!qu1.isEmpty()) {
            int len = qu1.size()-1;
            for (int i = 0; i < len; i++) {
                int val = qu1.poll();
                qu2.offer(val);
            }
            //
            return qu1.poll();
        }
        int len = qu2.size() -1 ;
        for (int i = 0; i < len; i++) {
                int val = qu2.poll();
                qu1.offer(val);
            }
        return qu2.poll();
        }
        public int top() {
            // 获取栈顶元素
            //将所有的数据转移到另一个队列中,期间经过一个临时变量,最后临时变量中的值即为栈顶元素
            if(empty()){
                return -1;
            }
            int val = 0;
            if(!qu1.isEmpty()){
               // for(int i=0;i<qu1.size();i++){ //有问题,当qu1执行poll方法后,会出现判断条件发生改变的情况
                while (!qu1.isEmpty()){
                    val = qu1.poll();
                    qu2.offer(val);
                }
            }else{
                while (!qu2.isEmpty()){
                    val = qu2.poll();
                    qu1.offer(val);
                }
            }
            return val;
        }
    public boolean empty() {
        if(qu1.isEmpty()&&qu2.isEmpty()){
            return true;
        }
        return false;
    }
}

2. 用栈实现队列

实现思想:

  1. 通过两个栈来实现队列
  2. 对于入队操作,直接将所有的数据放入栈1中即可
  3. 对于出队操作,则先判断栈2中是否有数据,如果有则出栈2的数据,如果没有,则将栈1中的全部数据转入栈2,然后再出栈2中的数据即可。
    在这里插入图片描述
class MyQueue {
    Stack<Integer> stack1 = new Stack<>();
    Stack<Integer> stack2 = new Stack<>();


    public MyQueue() {

    }
    
    public void push(int x) {
       //将所有的数据压入栈1
       stack1.push(x);
    }
 public int pop() {
       //先判断栈是否为空
       if(empty()){
        return -1;
       }
       if(!stack2.isEmpty()){
        //如果栈2不等于空,
       return stack2.pop();
       }
       //如果栈2为空
       while(!stack1.isEmpty()){
         //将栈1的元素全部压入栈2中去
        int val  =  stack1.pop();
         stack2.push(val);
       }
        return stack2.pop();
      }
     public int peek() {
         //获取栈顶的元素
         //先判断栈是否为空
       if(empty()){
        return -1;
       }
       if(!stack2.isEmpty()){
        //如果栈2不等于空,
       return stack2.peek();
       }
       //如果栈2为空
       while(!stack1.isEmpty()){
         //将栈1的元素全部压入栈2中去
        int val  =  stack1.pop();
         stack2.push(val);
       }
        return stack2.peek();
    }
    
    public boolean empty() {
        return stack1.isEmpty()==true && stack2.isEmpty() ==true ;
      }
 }
  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值