数据结构-队列的链表实现以及操作

队列的概述 

正如上次文章所述。队列的实现方式有很多种,其中一种就是使用最简单的数组来实现。还有使用链表的方式来实 现队列。这里简单回顾下:

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行 插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队 头。队列中没有元素时,称为空队列。 队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从 队列中删除一个队列元素称为出队。因 为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才 能最先从队列中删除,故队列又称为先 进先出(FIFO—first in first out)线性表。 

02数组和链表的存储数据的特点 

☑ 数组

在内存中,数组是一块连续的区域,申请的内存必须是连续的,而且数组申请的时候需要指定长度(也就是说需要 提前申请)。例如:申请了长度为10 ,但是如果里面只存储了5个元素。那么其他的内存就浪费了。

另外,数据的插入 和 删除 效率低下,因为数组是连续的,如果在插入的时候,就要求后续数据,在内存中往后移 动。例如(就像看电影座位有5个座位,中间有人坐了,如果来一个人坐中间,原本的人就要移动座位才行)扩展不容易,因为是提前申请内存空间,如果不够就没办法继续存储。如果申请太多,没用就是资源浪费了。

读取效率高,因为数组的地址是连续的,并且知道地址之后,很快就可以根据地址查询数据了。

☑ 链表

链表中申请的内存地址是随机的,可以不重复,也就意味着 可以充分利用资源,不会有像数组那样的连续内存。增加和删除数据 效率高,因为链表(包括单列表和双链表),有头结点和尾结点,从头部或者从尾部添加结点(数 据)的时候只需要记住原头不结点的地址或者是尾部结点的地址即可。很快便能申请内存地址。

扩展容易,因为不需要连续申请内存,数据可以存储在任意的内存地址中。

查询数据的时候效率低下,因为链表中的数据都是通过头来找到的,(要找链表中的一个数据,必须就是从头开始找起)。

03链表的队列实现 

实现思路:

·创建一个双链表类 

·创建一个以链表实现的队列类,比如实现Queue的队列接口 

·在队列类中使用链表作为数据存储 

·简单实现入队和出队的模拟 

☑  创建双链表类 

package com.itheima.link;

import java.util.Comparator;

import java.util.Iterator;

/**

 * 双链表

 *

 * @author

 *

 */

public class MyDoubleLink2<E> implements Iterable<E> {

    /**

     * 节点的封装类

     *

     * @author Administrator

     *

     */

    private class Node {

        Node prev; // 上一个节点

        E data;// 数据

        Node next;// 下一个节点

    }

   

    private Node head;// 头节点

    private Node rear;// 尾节点

   /**

     * 删除数据: 1.只有一个数据 2.删除头节点 3.删除尾节点 4.删除中间节点

     *

     * @param data

     */

    public void remove(Object data) {

        // 1. 查找数据是否存在,查找数据所在的节点

        Node node = findData(data);

        // 2.判断node是否存在

        if (node != null) {

            remove(node);

        }

    }

   

    /**

     * 是否有数据

    * @return

     */

    public boolean isEmpty(){

        return head == null;

    }

   

    /**

     * 移除尾部的数据

     * @return

     */

    public E removeRear(){

        E data = null;

        if (rear != null) {

            data = rear.data;

            //移除

            rear = rear.prev;

            if (rear == null) {

                //没有节点

                head = null;

            } else {

                rear.next = null;

            }

        }

        return data;

    }

    /**

     * 移除头部的数据

     * @return

     */

    public E removeHead(){

        E data = null;

        if (head != null){

            data = head.data;

           //下一个数据成为头节点

            head = head.next;

            //后面没有

            if (head != null)

                head.prev = null;

            else {

                //没有数据

                rear = null;

            }

        }

        return data;

    }

    /**

     * 是否包含数据

     *

     * @param data

     * @return

     */

    public boolean contains(Object data) {

        return findData(data) != null;

    }

    /**

     * 删除节点

     *

     * @param node

     */

    private void remove(Node node) {

        if (node == head && node == rear) {

            // 1.只有一个数据

           head = null;

            rear = null;

        } else if (node == head) {

            // 2.删除头节点,后面肯定有节点

            head = head.next;

            head.prev = null;

        } else if (node == rear) {

            // 3.删除尾节点,前面肯定有节点

            rear = rear.prev;

            rear.next = null;

        } else {

            // 4.删除中间节点,前后肯定有节点

            node.prev.next = node.next;

            node.next.prev = node.prev;

        }

    }

    /**

     * 查找数据所在的节点

    *

     * @param data

     * @return

     */

    private Node findData(Object data) {

        Node node = head;

        while (node != null) {

            if (node.data.equals(data)

                    && node.data.hashCode() == data.hashCode()) {

                // 找到数据

                break;

            } else {

                // 继续下一个

                node = node.next;

            }

        }

        return node;

    }

    /**

     * 从头部添加数据

     *

     * @param data

     */

    public void addHead(E data) {

        // 1. 创建新的节点

        Node node = new Node();

        // 2. 把数据放入节点中

        node.data = data;

        // 3. 把节点链接到链表中

        // 从链表的尾部添加数据

        if (head == null) {

            // 说明链表是空的 ,node就成头节点

            rear = node;

            head = node;

        } else {

            // 有头节点

            head.prev = node;

            node.next = head;

            head = node;

        }

    }

    /**

     * 添加数据,从链表的尾部添加数据

     *

     * @param data

     */

    public void add(E data) {

        // 1. 创建新的节点

        Node node = new Node();

        // 2. 把数据放入节点中

        node.data = data;

        // 3. 把节点链接到链表中

        // 从链表的尾部添加数据

        if (rear == null) {

            // 说明链表是空的 ,node就成头节点

            rear = node;

            head = node;

        } else {

            // 有头节点

            rear.next = node;

            node.prev = rear;

            rear = node;

        }

    }

    @Override

    public String toString() {

        StringBuilder mess = new StringBuilder("[");

        // 遍历链表中所有的数据

        Node node = head;// 从头节点开始遍历数据

        while (node != null) {

            // 有数据

            // 拼接数据

            // 判断是否是尾节点

            if (node != rear) {

                mess.append(node.data + ", ");

            } else {

                mess.append(node.data);

            }

            // 条件的改变

            node = node.next;

        }

        mess.append("]");

        return mess.toString();

    }

    @Override

    public Iterator iterator() {

        class MyIte implements Iterator {

            private Node node = head;// 从头节点遍历

            @Override

            public boolean hasNext() {

                return node != null;

            }

            @Override

            public Object next() {

                // 获取数据

                Object data = node.data;

                // 设置下一个数据

                node = node.next;

                return data;

            }

            @Override

            public void remove() {

                // 删除next方法返回的数据

                MyDoubleLink2.this.remove(node.prev);

            }

        }

        return new MyIte();

    }

}

从尾部结点的数据方法如下:

public void add(E data) {

        // 1. 创建新的节点

        Node node = new Node();

        // 2. 把数据放入节点中

        node.data = data;

        // 3. 把节点链接到链表中

        // 从链表的尾部添加数据

        if (rear == null) {

            // 说明链表是空的 ,node就成头节点

            rear = node;

            head = node;

        } else {

            // 有头节点

            rear.next = node;

            node.prev = rear;

            rear = node;

        }

 }

☑ 创建以链表存储数据的队列类 

package com.itheima.queue;

import com.itheima.link.MyDoubleLink;

import java.util.Collection;

import java.util.Iterator;

import java.util.Queue;

/**

 * 描述

 *

 * @author 三国的包子

 * @version 1.0

 * @package com.itheima.queue *

 * @since 1.0

 */

public class MyLinkQueue<E> implements Queue<E> {

      

    private MyDoubleLink<E> datas = new MyDoubleLink2<>();

    @Override

    public boolean add(E e) {

        datas.add(e);

        return true;

    }

   @Override

    public E poll() {

        return datas.removeHead();

    }

    @Override

    public String toString() {

        return datas.toString();

    }

    @Override

    public int size() {

        return 0;

    }

    @Override

    public boolean isEmpty() {

        return datas.isEmpty();

    }

    @Override

    public boolean contains(Object o) {

        return false;

    }

    @Override

    public Iterator<E> iterator() {

        return null;

    }

    @Override

    public Object[] toArray() {

        return new Object[0];

    }

    @Override

    public <T> T[] toArray(T[] a) {

        return null;

    }

    @Override

    public boolean remove(Object o) {

        return false;

    }

    @Override

    public boolean containsAll(Collection<?> c) {

        return false;

    }

    @Override

    public boolean addAll(Collection<? extends E> c) {

        return false;

    }

    @Override

    public boolean removeAll(Collection<?> c) {

        return false;

    }

    @Override

    public boolean retainAll(Collection<?> c) {

        return false;

    }

    @Override

    public void clear() {

    }

    @Override

    public boolean offer(E e) {

        return false;

    }

    @Override

    public E remove() {

        return null;

    }

    @Override

    public E element() {

        return null;

    }

    @Override

    public E peek() {

        return null;

    }  

}

方法解析:

为了以后的扩展性 可以实现Queue的接口,此处为演示 只实现了其中的几个方法:

·add(E e)  添加数据(入队)

·poll()   移除数据(出队)

·isEmpty() 判断队列是否为空

·toString() 重写方法 便于打印

☑ 创建双链表的数据储存对象 

在队列类中创建

 private MyDoubleLink<E> datas = new MyDoubleLink2<>();

☑ 入队方法 

入队方法如下,参考刚才的方法。入队的元素从尾部结点添加即可。

@Override 

public boolean add(E e) {    

datas.add(e);    

return true; 

}

如下图所示:

解释:

·将原尾结点的NEXT指向新的结点 

·将新结点的prev指向原尾结点

·将新结点作为尾结点(也就是将尾结点的引用指向那个新的结点)

理解图如下:

☑ 出队方法

 @Override    

public E poll() {        

return datas.removeHead();    

}

队列的方式是:先进先出,该方法表示从头开始删除节点。也就是出队。头先添加,最先出去的也是头结点。

如下图:

·先判断是否有头 如果有,那么将头结点引用指向下一个结点。

·再将头结点的prev赋值为空即可。

04总结

以上演示了队列的链表实现方式。

对于想快速访问,不经常进行插入和删除的操作 可以使用数组。对于经常进行添加和删除,对查询没有特别要求的可以使用链表。 ​​​​

猜你喜欢

转载自blog.csdn.net/qq_39581763/article/details/89372691