动态链表----双向循环链表的实现

1. 双向循环链表的定义:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。如图:

2. 双向循环链表不只可以实现线性表List,也可以实现队列,双端队列,栈这些数据结构。因此我们在实现双端循环链表时,使其实现List,Queue,DoubleQueue,Stack这些接口。

//双向循环链表 即可以实现列表List的方法 也可以实现双端循环队列DoubleLoopQueue 队列Queue 栈Stack的方法
public class LinkedDoubleEndCircularList<E> implements List<E>, DoubleLoopQueue<E>, Stack<E> {

    /*将数据封装成节点 节点包括三部分 数据域 前驱指针域 和 后继指针域
   数据域中存放的是数据内容
   前驱指针域中存放的是当前节点的上一跳节点的地址 即前驱指针域指向当前节点的上一跳节点
   后继指针域中存放的是当前节点的下一跳节点的地址 即后继指针域指向当前节点的下一跳节点
   因为是双向循环链表 因此尾节点的下一跳始终指向头节点 头节点的上一跳也始终指向尾节点
    */

    class Node{ //封装数据域和指针域的类
        E data; //表示节点的数据域
        Node next; //表示节点的后继指针域
        Node pre; //表示节点的前驱指针域

        public Node(){ //节点的无参构造
            data = null;
            next = null;
            pre = null;
        }
        public Node(E data){ //节点的带参构造 参数为节点的数据
            this.data = data;
            next = null;
            pre = null;
        }
        public Node(E data, Node next, Node pre){ //节点的带参构造 参数为节点的数据 当前节点的下一跳节点和上一跳节点
            this.data = data;
            this.next = next;
            this.pre = pre;
        }

        //格式化节点输出时的格式 即输出数据内容即可
        @Override
        public String toString() {
            return data.toString();
        }
    }

    //表示头指针 指向链表中的头节点
    private Node head;
    //表示尾指针 指向链表中的尾节点
    private Node tail;
    //表示链表中有效元素的个数
    private int size;

    public LinkedDoubleEndCircularList(){ //双向链表的无参构造 为head tail size初始化
        head = null;
        tail = null;
        size = 0;
    }

    public LinkedDoubleEndCircularList(E[] arr){ //双向链表的带参构造 参数为一个数组 将数组中的元素依次添加到链表中
        if(arr == null){
            throw new IllegalArgumentException("arr can not be null");
        }
        for(int i = 0; i < arr.length; i++){
            add(arr[i]);
        }
    }

    //向链表的表尾追加元素
    @Override
    public void add(E element) {
        add(size, element);
    }

    //向链表的指定位置添加元素
    @Override
    public void add(int index, E element) {
        if(index < 0 || index > size){ //判断索引是否存在
            throw new IllegalArgumentException("add index out of range");
        }
        Node node = new Node(element); //创建要添加的节点对象

        if(size == 0){ //表空时添加元素
            /*
            表空时添加元素:
            1.使头指针 和 尾指针都指向要添加的节点node
            2.使头节点的上一跳指向尾节点
            3.使尾节点的下一跳指向头节点
             */
            head = node;
            tail = node;
            head.pre = tail;
            tail.next = head;
        }else if(index == 0){ //向表头添加元素
            /*
            向表头添加元素:
            1.将当前头节点的上一跳赋值给要添加节点的上一跳
            2.使要添加节点的下一跳指向当前头节点
            3.使当前头节点的上一跳指向要添加节点
            4.使头指针指向要添加节点
            5.使尾节点的下一跳指向要添加节点
             */
            node.pre = head.pre;
            node.next = head;
            head.pre = node;
            head = node;
            tail.next = head;
        }else if(index == size){ //向表尾添加元素
            /*
            向表尾添加元素:
            1.将当前尾节点的下一跳赋值给要添加节点的下一跳
            2.使要添加节点的上一跳指向当前尾节点
            3.使当前尾节点的下一跳指向要添加节点
            4.使尾指针指向要添加节点
            5.使头节点的上一跳指向要添加节点
             */
            node.next = tail.next;
            node.pre = tail;
            tail.next = node;
            tail = node;
            head.pre= tail;
        }else{ //向表中间添加元素
            /*
            向表中间添加元素:
            1.先判断要添加位置的索引离头节点近还是尾节点近
            2.定义两个节点 一个为要添加节点的前驱 一个为要添加节点的后继
            3.使前驱节点的下一跳指向要添加节点 要添加节点的上一跳指向前驱节点
            4.使后继节点的上一跳指向要添加节点 要添加节点的下一跳指向后继节点
             */
            Node p, q; //节点p表示要添加节点的前驱节点 节点q表示要添加节点的后继节点
            if(index <= size / 2){
                p = head;
                for(int i = 0; i < index - 1; i++){
                    p = p.next;
                }
                q = p.next;
                p.next = node;
                node.pre = p;
                node.next = q;
                q.pre = node;
            }else{
                p = tail;
                for(int i = size - 1; i > index; i--){
                    p = p.pre;
                }
                q = p.pre;
                q.next = node;
                node.pre = q;
                node.next = p;
                p.pre = node;
            }
        }
        size++;
    }

    //删除链表中的指定元素
    @Override
    public void remove(E element) {
        int index = indexOf(element);
        remove(index);
    }

    //删除链表中的指定位置的元素 并返回要删除元素的内容
    @Override
    public E remove(int index) {
        if(index < 0 || index > size){ //判断索引是否存在
            throw new IllegalArgumentException("add index out of range");
        }
        E ele = null;
        if(size == 1){ //当表中只有一个元素时删除
            /*
            当表中只有一个元素时删除: 先获取要删除节点的数据 再使头指针 和 尾指针都指向空即可
             */
            ele = head.data;
            head = null;
            tail = null;
        }else if(index == 0){ //删除表头元素
            /*
            删除表头元素:
            1.获取头节点的数据
            2.将表头结点的下一跳置null
            3.将表头结点的上一跳赋值给要删除节点的后继节点的上一跳
            4.使头节点的上一跳置null
            5.更新头指针 使其指向要删除节点的后继节点
            6.重新使尾节点的下一跳指向新的头节点
             */
            Node p = head.next;
            ele = head.data;
            head.next = null;
            p.pre = head.pre;
            head.pre = null;
            head = p;
            tail.next = head;
        }else if(index == size - 1){ //删除表尾元素
            /*
            删除表尾元素:
            1.获取尾节点的数据
            2.将表尾结点的上一跳置null
            3.将表尾结点的下一跳赋值给要删除节点的前驱节点的下一跳
            4.使尾节点的下一跳置null
            5.更新尾指针 使其指向要删除节点的前驱节点
            6.重新使头节点的上一跳指向新的尾节点
             */
            Node p = tail.pre;
            ele = tail.data;
            tail.pre = null;
            p.next = tail.next;
            tail.next = null;
            tail = p;
            head.pre = tail;
        }else{ //删除表中间元素
            /*
            删除表中间元素:
            1.先判断要删除节点的索引离头节点近还是尾节点近
            2.定义三个节点 一个为要删除节点的前驱 一个为要删除节点的后继 一个为要删除的节点
            3.使前驱节点的下一跳指向后继节点
            4.使后继节点的上一跳指向前驱节点
            5.使要删除节点的下一跳置null
            6.使要删除节点的上一跳置null
             */
            Node p, q, r; //p节点表示删除节点的前驱 q节点表示要删除的节点 r节点表示要删除节点的后继
            if(index <= size / 2){
                p = head;
                for(int i = 0; i < index - 1; i++){
                    p = p.next;
                }
                q = p.next;
                r = q.next;
                p.next = r;
                r.pre = p;
                q.next = null;
                q.pre = null;
            }else{
                p = tail;
                for(int i = size - 1; i > index + 1; i--){
                    p = p.pre;
                }
                q = p.pre;
                r = q.pre;
                r.next = p;
                p.pre = r;
                q.next = null;
                q.pre = null;
            }
        }
        size--;
        return ele;
    }

    //获取指定索引处的节点的值
    @Override
    public E get(int index) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("get index out of range");
        }
        if(index == 0){ //获取表头元素 即头指针指向的节点的值
            return head.data;
        }else if(index == size - 1){ //获取表尾元素 即尾指针指向的节点的值
            return tail.data;
        }else{ //获取表中间某一索引的值
            /*
            获取表中间某一索引的值:定义一个新节点p使其指向头节点 从表头开始遍历 遍历到索引位置 每次更新p节点
            最后p节点就是要获取节点 返回该节点的值即可
             */
           Node p = head;
           for(int i = 0; i < index; i++){
               p = p.next;
           }
           return p.data;
        }
    }

    //修改指定位置的节点的值 并返回要修改节点的值
    @Override
    public E set(int index, E element) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("get index out of range");
        }
        E ele;
        if(index == 0){ //修改表头节点的值 即先获取表头结点的值 再将表头结点的值修改为指定的值element即可
            ele = head.data;
            head.data = element;
        }else if(index == size - 1){ //修改表尾节点的值 即先获取表尾结点的值 再将表尾结点的值修改为指定的值element即可
            ele = tail.data;
            tail.data = element;
        }else{ //修改表中间位置的节点的值
            /*
            修改表中间位置的节点的值:定义一个新节点p使其指向头节点
            从头节点开始遍历 遍历到索引位置的前驱位置 每次更新p节点为p节点的下一个节点
            遍历结束后 p节点即指定要修改的索引处的节点 获取p节点的值
            修改p节点的值为element
             */
            Node p = head;
            for(int i = 0; i < index; i++){
                p = p.next;
            }
            ele = p.data;
            p.data = element;
        }
        return ele;
    }

    //获取链表有效元素的个数
    @Override
    public int size() {
        return size;
    }

    //获取指定元素第一次在链表出现的索引
    @Override
    public int indexOf(E element) {
         /*
        定义一个新节点p使其指向头节点
        遍历链表 直到p节点的数据与指定数据相等即可
        每次更新p节点为p节点的下一个节点 索引index的值
        判断 如果p节点再次指向头节点 则表示已经遍历了一圈了 都没有找到指定元素 则返回-1
         */
        Node p = head;
        int index = 0;
        while (!p.data.equals(element)){
            p = p.next;
            index++;
            if(p == head){
                return -1;
            }
        }
        return index;
    }

    //判断链表中是否包含指定元素
    @Override
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    //判断链表是否为空
    @Override
    public boolean isEmpty() {
        return size == 0 && (head == null && tail == null);
    }

    //清空链表中的所有元素
    @Override
    public void clear() {
        head = null;
        tail = null;
        size =0;
    }

    //对链表进行排序 插入排序的思想 时间复杂度为O(n^2)
    @Override
    public void sort(Comparator<E> c) {
        if(c == null){ //判断比较器是否为空
            throw new IllegalArgumentException("comparator can not be null");
        }
        if(size == 0 || size == 1){ //如果链表中有效元素的个数等于0或1 就不需要在对链表进行排序
            return;
        }
        Node nodeA, nodeB, nodeC; //定义三个节点
        for(nodeA = head.next; nodeA != head; nodeA = nodeA.next){ //外层循环 更新nodeA节点
            E e = nodeA.data; //获取nodeA节点的数据
            /*
            内层循环: 更新nodeB 和 nodeC节点
            nodeB节点(相当于j)开始指向nodeA节点 nodeC(相当于j-1)节点开始指向nodeB节点的前驱节点
            当nodeC节点不等于尾节点 且 nodeC节点的数据大于nodeA节点的数据 则更新nodeB节点 和 nodeC节点分别为当前节点的前驱节点
            满足条件时 将nodeC节点的值赋值给nodeB节点的数据
            否则 将nodeA节点的数据赋值给nodeB节点的数据
             */
            for (nodeB = nodeA, nodeC = nodeB.pre; nodeC != tail && c.compare(nodeC.data, e) > 0; nodeB = nodeB.pre, nodeC = nodeC.pre){
                nodeB.data = nodeC.data;
            }
            nodeB.data = e;
        }
    }

    //获取链表中指定索引范围的子链表 [fromIndex, toIndex]
    /*
    先定义一个节点A 使其指向头节点 遍历链表 更新节点A 使其到达fromIndex位置处
    再定义一个节点B 也使其指向头节点 遍历链表 更新节点B 使其到达toIndex位置处
    定义一个新节点使其指向节点A 从fromIndex位置遍历到toIndex位置 最后使其指向节点B
    将[fromIndex, toIndex]范围内的节点重新依次加入到新的子链表中
    该获取子链表的方法时间复杂度为O(n)
     */
    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        if(fromIndex < 0 || fromIndex > toIndex || toIndex >= size){ //判断两个索引是否存在
            throw new IllegalArgumentException("must 0 <= fromIndex <= toIndex <= size - 1");
        }
        Node nodeA = head;
        for(int i = 0; i < fromIndex; i++){
            nodeA = nodeA.next;
        }
        Node nodeB = head;
        for(int i = 0; i < toIndex; i++){
            nodeB = nodeB.next;
        }
        Node p = nodeA;
        LinkedDoubleEndCircularList<E> linkedList = new LinkedDoubleEndCircularList<>();
        while (true){
            linkedList.add(p.data);
            if(p == nodeB){
                break;
            }
            p = p.next;
        }
        return linkedList;
    }

    //格式化链表输出是的格式
    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append("LinkedDoubleEndCircularList: " + size + " [");
        if(isEmpty()){
            str.append(']');
        }
        Node p = head;
        for(int i = 0; i < size; i++){
            str.append(p.data);
            if(p == tail){
                str.append(']');
            }else{
                str.append(',');
                str.append(' ');
            }
            p = p.next;
        }
        return str.toString();
    }

    //获取当前这个数据结构/容器 的 迭代器
    //通过迭代器对象 更方便挨个取出每一个元素
    //同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
    @Override
    public Iterator<E> iterator() {
        return new LinkedDoubleEndCircularListIterator();
    }

    class LinkedDoubleEndCircularListIterator implements Iterator<E> {

        private Node cur = head; //定义一个游标节点
        private boolean flag = true; //表示是否在第一圈内 在第一圈是true 遍历到第二圈是false

        @Override
        public boolean hasNext() { //判断是否有下一个节点
            return flag; //flag == true时 表示有下一个节点
        }

        @Override
        public E next() { //获取下一个节点的值
            E ele = cur.data; //先获取节点cur的值
            cur = cur.next; //再更新cur节点为当前cur节点的下一个节点
            if(cur == head){ //判断cur游标节点是否重新指向头节点 如果是则表示已经遍历一圈了 反之还在第一圈
                flag = false;
            }
            return ele;
        }
    }

    //实现双端循环队列的方法
    //向双端循环队列的队首添加元素
    @Override
    public void addFirst(E element) {
        add(0, element);
    }

    //向双端循环队列的队尾添加元素
    @Override
    public void addLast(E element) {
        add(size, element);
    }

    //删除双端循环队列的队首元素
    @Override
    public E removeFirst() {
        return remove(0);
    }

    //删除双端循环队列的队尾元素
    @Override
    public E reomveLast() {
        return remove(size - 1);
    }

    //获取双端循环队列的队首元素
    @Override
    public E getFirst() {
        return get(0);
    }

    //获取双端循环队列的队尾元素
    @Override
    public E getLast() {
        return get(size - 1);
    }

    //实现队列的方法
    //向队尾添加元素
    @Override
    public void offer(E element) {
        addLast(element);
    }

    //删除队首元素
    @Override
    public E poll() {
        return removeFirst();
    }

    //查看队首元素
    @Override
    public E element() {
        return getFirst();
    }

    //实现栈的方法
    //入栈 进栈一个元素 在线性表的表尾添加一个元素
    @Override
    public void push(E element) {
        addLast(element);
    }

    //出栈 弹出一个元素 在线性表的表尾删除一个元素
    @Override
    public E pop() {
        return reomveLast();
    }

    //查看当前栈顶元素 并不是移除 查看线性表中最后一个元素
    @Override
    public E peek() {
        return getLast();
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值