java数据结构--双端队列

一.概念

  双端队列的意思是可以在头部和尾部添加和删除元素,更一般的单向链表队列比起来更加的灵活,下面我们用双向循环带哨兵链表和数组来分别实现

二.定义接口Dequeue

/**
 * 双端队列
 */
public interface Dequeue<E> {

    //队头添加元素
    boolean offerTop(E e);

    //队尾添加元素
    boolean offerTail(E e);

    //队头取元素并删除该元素
    E pollTop();

    //队尾取元素并删除该元素
    E pollTail();


    //队头取元素
    E peekTop();

    //队尾取元素
    E peekTail();

    //是否为空
    boolean isEmpty();

    //是否为满
    boolean isFull();

}

三.双向循环带哨兵链表链表实现

 1.图示

  

 

(1)向队头添加节点

(2)向队尾添加节点

 

 对于移除节点,和上面的类似,代码注释中也都做了解释,这里就不在展示了。

2.代码实现 

/**
 * 双向链表实现双端队列
 * @param <E>
 */
public class LinkedDequeue<E> implements Dequeue<E> ,Iterable<E>{


    //内部节点类
    static class Node<E>{
        Node<E> pre;
        E value;
        Node<E> next;

        public Node(Node<E> pre,E value,Node<E> next){
            this.pre = pre;
            this.value = value;
            this.next = next;
        }

    }

    //容量
    int capacity;
    //队列中元素的个数
    int size;
    //哨兵
    Node<E> sentinel = new Node<>(null,null,null);

    //初始化
    public LinkedDequeue(int capacity){
        //初始化容量
        this.capacity = capacity;
        //开始时哨兵的下一个节点是本身
        sentinel.next = sentinel;
        //上一个节点也是本身
        sentinel.pre = sentinel;
    }



    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            //获取第一个元素
            Node<E> p = sentinel.next;
            @Override
            public boolean hasNext() {
                //如果p==sentinel就说明遍历完成了,没有下一个了
                return p != sentinel;
            }

            @Override
            public E next() {
               E value = p.value;
               p = p.next;
               return value;
            }
        };
    }

    /**
     * 向头部添加节点
     *  a  -> added ->  b
     *     <-       <-
     *  a是哨兵, b是哨兵的下一个节点
     * @param e
     * @return
     */
    @Override
    public boolean offerTop(E e) {
       if(isFull()){
           return  false;
       }
       Node<E> a = sentinel;
       Node<E> b = sentinel.next;
       Node<E> added = new Node<>(a,e,b);
       a.next = added;
       b.pre = added;
       size++;
       return true;
    }

    /**
     * 向尾部添加节点
     *
        a    -> added ->  b
     *       <-       <-
     * a是哨兵的上一个,b是哨兵
     *
     * @param e
     * @return
     */
    @Override
    public boolean offerTail(E e) {
        if(isFull()){
            return false;
        }
        Node<E> b = sentinel;
        Node<E> a = sentinel.pre;
        Node<E> added = new Node<>(a,e,b);
        a.next = added;
        b.pre = added;
        size++;
        return true;
    }

    /**
     * 将头部节点移除
     * @return 头部元素的值
     *
     * a removed b
     * a是哨兵,removed是哨兵的下一个,b是removed的下一个
     * a -> b
     *   <-
     */
    @Override
    public E pollTop() {
       if(isEmpty()){
           return null;
       }
       Node<E> a = sentinel;
       Node<E> removed = sentinel.next;
       Node<E> b = removed.next;
       a.next = b;
       b.pre = a;
       size--;
       return removed.value;
    }
    /**
     * 将尾部节点移除
     * @return 尾部元素的值
     *
     * a removed b
     * b是哨兵,removed是b的上一个,a是removed的上一个
     * a -> b
     *   <-
     */
    @Override
    public E pollTail() {
       if(isEmpty()){
           return null;
       }
       Node<E> b = sentinel;
       Node<E> removed = sentinel.pre;
       Node<E> a = removed.pre;
       a.next = b;
       b.pre = a;
       size--;
       return removed.value;
    }

    /**
     * 获取第一个元素的值,但是不移除
     * @return
     */
    @Override
    public E peekTop() {
        if(isEmpty()){
            return null;
        }
        return sentinel.next.value;
    }

    /**
     * 获取最后一个元素的值,但是不移除
     * @return
     */
    @Override
    public E peekTail() {
        if(isEmpty()){
            return null;
        }
        return sentinel.pre.value;
    }

    @Override
    public boolean isEmpty() {
        return  size == 0;
    }

    @Override
    public boolean isFull() {
        return  size == capacity;
    }
}

四.数组带头尾指针实现

1.图示

(1)刚开始时head和tail都在同一位置,表示空

当在尾部tail添加元素时,先在tail处添加元素,然后让tail加一,

注意:这里的加一不是直接tail++,因为是循环队列,所以若到了array.length处,也就是末尾,下一次加一就要跑到索引0的位置了,一般我们之前是用的求余数运算来找到合法的索引位置,但是我们这里会用一个新的方式来运算索引

 (2)tail处添加元素

(3)head处添加元素

 我们在head处添加元素时,我们是让head先减一,然后再在head处添加,为啥呢?因为我们是循环数组,让head减一,相当于改变了头的位置,好像是在向左移动一样,向左扩展,

注意:这里的减一也是要算出合法的索引,而且如果head本来是在索引0处,那么head就要移动到array.length处,这样就好像是在向前移动

 对于头部尾部移除元素,都是类似的,在此不过多描述。

(4)代码实现

/**
 * 数组实现双端队列
 * @param <E>
 */
public class ArrayDequeue<E> implements Dequeue<E>,Iterable<E>{

    E array[];
    //头指针
    int head;
    //尾指针
    int tail;

    @SuppressWarnings("all")
    public ArrayDequeue(int capacity){
        //实际容量要多空出一个来判断空满
        array = (E[]) new Object[capacity+1];
    }

    //加一方法
    public static int inc(int i,int length){
        if(i + 1 >= length){
            return 0;
        }
        return i + 1;
    }
    //减一方法
    public static int dec(int i,int length){
        if(i - 1 < 0){
            return  length - 1;
        }
        return i - 1;
    }





    /**
     * 头部添加元素
     *
     * 先让head--,然后再在head处添加元素
     *
     * @param e
     * @return
     */
    @Override
    public boolean offerTop(E e) {
       if(isFull()){
           return false;
       }
       head = dec(head,array.length);
       array[head] = e;
       return true;
    }

    /**
     * 在尾部添加依赖
     *
     * 先在tail处添加元素,然后让tail++
     * @param e
     * @return
     */
    @Override
    public boolean offerTail(E e) {
       if(isFull()){
           return false;
       }
       array[tail] = e;
       tail = inc(tail,array.length);
       return true;
    }

    /**
     * 移除队头元素
     *
     * 先获取head处元素,然后让head++
     *
     * @return
     */
    @Override
    public E pollTop() {
       if(isEmpty()){
           return null;
       }
        E  e  =  array[head];
        array[head] = null; //help GC
        head = inc(head,array.length);

       return e;
    }

    /**
     * 移除队尾元素
     *
     * 先让tail--,然后返回tail处的值
     *
     * @return
     */
    @Override
    public E pollTail() {
        if(isEmpty()){
            return null;
        }
        tail = dec(tail,array.length);
        E e = array[tail];
         array[tail] = null; //help GC
        return e;
    }

    @Override
    public E peekTop() {
        if(isEmpty()){
            return null;
        }
        return array[head];
    }

    @Override
    public E peekTail() {
        if(isEmpty()){
            return null;
        }
        return array[dec(tail, array.length)];
    }

    /**
     * 判断是否为空
     * @return
     */
    @Override
    public boolean isEmpty() {
        return  head == tail;
    }

    /**
     * 判断是否为满
     * @return
     */
    @Override
    public boolean isFull() {
        //当 head > tail 时,
        /**
         * 需要判断 head - tail == 1
         */

        /**
         * 当 tail > head 时
         * 需要判断 tail - head == array.length - 1;
         */

        if(head > tail){
            return head - tail == 1;
        }else if( head < tail){
            return  tail - head == array.length - 1;
        }else{
            return false;
        }

    }

    /**
     * 遍历
     * @return
     */
    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {

            int p = head;

            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public E next() {
                E e = array[p];
                p = inc(p,array.length);
                return e;
            }
        };
    }
}

五.及时释放元素

我们在之前的队列和栈的移除元素操作时,每次都是直接跳过元素,相当于移除了该元素,其实这里有误区:

1.如果元素类型为基本类型,比如Int类型,那么一个数字占四个字节,直接跳过和设置为0都占用内存空间,设置为0意义不大,所以直接跳过就可以了。

2.如果元素类型为引用类型,比如Student学生对象,那么如果你直接跳过该元素,其实他并没有被释放,也就是不能被GC回收,这时需要我们手动释放元素,我们可以让array[x]=null;来释放内存空间

另外说一点,在jdk中有一个类LinkedList,这个类可用当栈,也可以当队列,也实现了双端队列的功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 基于双向链表实现双端队列结构 */ package dsa; public class Deque_DLNode implements Deque { protected DLNode header;//指向头节点(哨兵) protected DLNode trailer;//指向尾节点(哨兵) protected int size;//队列中元素的数目 //构造函数 public Deque_DLNode() { header = new DLNode(); trailer = new DLNode(); header.setNext(trailer); trailer.setPrev(header); size = 0; } //返回队列中元素数目 public int getSize() { return size; } //判断队列是否为空 public boolean isEmpty() { return (0 == size) ? true : false; } //取首元素(但不删除) public Object first() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return header.getNext().getElem(); } //取末元素(但不删除) public Object last() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return trailer.getPrev().getElem(); } //在队列前端插入新节点 public void insertFirst(Object obj) { DLNode second = header.getNext(); DLNode first = new DLNode(obj, header, second); second.setPrev(first); header.setNext(first); size++; } //在队列后端插入新节点 public void insertLast(Object obj) { DLNode second = trailer.getPrev(); DLNode first = new DLNode(obj, second, trailer); second.setNext(first); trailer.setPrev(first); size++; } //删除首节点 public Object removeFirst() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); DLNode first = header.getNext(); DLNode second = first.getNext(); Object obj = first.getElem(); header.setNext(second); second.setPrev(header); size--; return(obj); } //删除末节点 public Object removeLast() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); DLNode first = trailer.getPrev(); DLNode second = first.getPrev(); Object obj = first.getElem(); trailer.setPrev(second); second.setNext(trailer); size--; return(obj); } //遍历 public void Traversal() { DLNode p = header.getNext(); while (p != trailer) { System.out.print(p.getElem()+" "); p = p.getNex

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值