双向循环链表实现

Posted guardwhy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双向循环链表实现相关的知识,希望对你有一定的参考价值。

1.1 基本介绍

双向循环链表就是在双线链表的基础上首尾相连(第一个节点的prev指向最后一个节点,最后一个节点的next指向第一个节点)。

1.2 添加操作

1、思路分析

头部插入

当整个链表都为空时,添加操作。

头结点和尾节点都指向自己。

当链表不为空时,添加操作

先把当前头节点的上一跳地址给新元素的上一跳

然后让新节点的后驱指针指向head结点,再让head的前驱指针指向新元素。

更新head结点,让head结点指向新结点,更新tail结点,让tail的下一跳重新指向head

尾部插入

将当前tail的下一跳给新节点的下一跳

tail的下一跳指向新结点,新结点的上一跳指向tail

tail重新指向新结点。

直接让head的上一跳重新指向tail

中间插入

1、离头部比较近

定义两个指针pq,找到要插入节点前驱,让p的上一跳指向新节点,让新节点的下一跳指向p

q的上一跳指向新节点,让新节点的下一跳指向q

2、离尾部比较近

q的下一跳指向新节点,让新节点的上一跳指向q

p的上一跳指向新节点,让新节点的下一跳指向p

2、代码示例

链表类: LinkedList

package cn.linkedlist.demo05;

import java.util.Iterator;

public class LinkedList<E> implements List<E>

    // 创建Node节点
    private class Node 
        // 数据域
        E data;
        // 指向直接前驱的指针
        Node prev;
        // 指向直接后继的指针
        Node next;

        // 构造函数
        public Node() 
            this(null, null, null);
        

        public Node(E data) 
            this(data, null, null);
        

        public Node(E data, Node prev, Node next) 
            this.data = data;
            this.prev = prev;
            this.next = next;
        

        @Override
        public String toString() 
            StringBuilder sb = new StringBuilder();
            if (prev != null) 
                sb.append(prev.data);
             else 
                sb.append("null");
            

            sb.append("->").append(data).append("->");

            if (next != null) 
                sb.append(next.data);
             else 
                sb.append("null");
            
            return sb.toString();
        
    

    // 链表元素的数量
    private int size;
    // 声明头结点
    private Node head;
    // 声明尾节点
    private Node tail;

    // 初始化头结点
    public LinkedList() 
        head = null;
        tail = null;
        size = 0;
    

    public LinkedList(E[] arr) 
        for (E e : arr) 
            add(e);
        
    

    //默认向表尾添加元素
    @Override
    public void add(E element) 
        add(size, element);
    

    //在链表当中指定索引index处添加一个元素
    @Override
    public void add(int index, E element) 
        if (index < 0|| index > size) 
            throw new ArrayIndexOutOfBoundsException("add index out of bounds");
        
        // 创建新的结点对象
        Node node = new Node(element);

        // 链表为空
        if(isEmpty())
            head = node;
            tail = node;
            tail.next = head;
            head.prev = tail;

        else if(index == 0) // 在链表头部添加元素
            // 头结点的上一跳指向新节点的上一跳
            node.prev = head.prev;
            node.next = head;
            head.prev = node;
            head = node;
            tail.next = head;
        else if(index == size) // 在链表尾部添加元素
            node.next = tail.next;
            tail.next = node;
            node.prev = tail;
            tail = node;
            head.prev = tail;
        else
            // 在链表中添加元素
            Node 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.prev = p;
                q.prev = node;
                node.next = q;
            else
                p = tail;
                for(int i=size -1; i > index; i--)
                    p = p.prev;
                
                q = p.prev;
                q.next = node;
                node.prev = q;
                p.prev = node;
                node.next = p;
            
        
        size++;
    

    @Override
    public int size() 
        return size;
    

    //查找元素在链表中第一次出现的索引
    @Override
    public int indexOf(E element) 
        if(isEmpty())
            return -1;
        
        Node p = head;
        int index = 0;
        while (!p.data.equals(element))
            p = p.next;
            index++;
            if(p == head)
                return -1;
            
        
        return index;
    

    //在链表中判断是否包含元素element
    @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;
    

    @Override
    public String toString() 
        StringBuilder res = new StringBuilder();
        res.append("size=").append(size).append(", [");
        Node node = head;
        for (int i = 0; i < size; i++) 
            if (i != 0) 
                res.append(", ");
            
            res.append(node);
            node = node.next;
        
        res.append("]");
        return res.toString();
    

    @Override
    public Iterator<E> iterator() 
        return new DoubleCircleLinkedListIterator();
    

    class  DoubleCircleLinkedListIterator implements Iterator<E>
        private Node cur = head;
        private boolean flag = true;

        @Override
        public boolean hasNext() 
            if(isEmpty())
                return false;
            
            return flag;
        

        @Override
        public E next() 
            E ret = cur.data;
            cur = cur.next;
            if(cur == head)
                flag = false;
            
            return ret;
        
    

测试类:LinkedListDemo

package cn.linkedlist.demo05;
public class LinkedListDemo 
    public static void main(String[] args) 
        LinkedList<Integer> list = new LinkedList<>();
        System.out.println("===链表头部插入===");
        // System.out.println(list);

        list.add(0, 1);
        list.add(0, 2);
        list.add(0, 3);
        System.out.println(list);

        System.out.println("===链表尾部插入===");
        list.add(list.size(), 4);
        list.add(list.size(), 5);
        list.add(list.size(), 6);
        System.out.println(list);
    


3、执行结果

1.3 删除操作

1、思路分析

删除头结点

node节点为head.next,最终node是新的头结点。

head的下一跳置空。

head的上一跳,给node的上一跳。

head的上一跳置空!!head重新指向node

最后让尾指针下一跳重新指向head即可。

删除尾节点

nodetail.pre前驱,最终node是新的尾结点。

tail的上一跳置空。

tail的下一跳给node的下一跳,tail的下一跳置空。

tail重新指向node

head的上一跳重新指向tail

删除中间节点

1、离头部比较近

定义三个指针,p是要删除节点的前驱,q是要删除节点,r是要删除节点的后继,p指针移动到要删除节点的前驱。

p的下一跳直接指向r,让r的上一跳重新指向p

q的上一跳和q的下一跳直接置空。

删除成功。

2、离尾部比较近

定义三个节点指针,p是要删除节点的前驱,q是要删除节点,R是要删除节点的后继。

R的上一跳指向p,让p的下一跳指向R,让q的两边同时置空!!!

最后删除成功!!!

2、代码示例

链表类: LinkedList

//删除链表中指定的元素element
@Override
public void remove(E element) 
    int index = index0f(element);
    if(index != -1)
        remove(index);
    


//删除链表中指定角标处index的元素
@Override
public E remove(int index) 
    if (index < 0|| index > size) 
        throw new ArrayIndexOutOfBoundsException("remove index out of bounds");
    
    // 定义ret变量
    E ret = null;
    Node node;
    // 当链表只剩一个元素
    if(size ==1)
        ret = head.data;
        head = null;
        tail = null;
        // 删除表头
    else if(index == 0)
        ret = head.data;
        node = head.next;
        head.next = null;
        node.prev = head.prev;
        head.prev = null;
        head = node;
        tail.next = head;
        // 删除表尾
    else if(index == size -1)
        ret = tail.data;
        node = tail.prev;
        tail.prev = null;
        node.next = tail.next;
        tail.next = null;
        tail = node;
        head.prev = tail;
    else
        // 删除链表中间的某一个元素
        Node p, q, r;
        if(index <= size / 2)
            p = head;
            for(int i=0; i < index-1; i++)
                p = p.next;
            
            q = p.next;
            ret = q.data;
            r = q.next;
            p.next = r;
            r.prev = p;
            q.next = null;
            q.prev = null;
        else
            p = tail;
            for(int i = size -1; i > index + 1; i--)
                p = p.prev;
            
            q = p.prev;
            ret = q.data;
            r = q.prev;
            r.next = p;
            p.prev = r;
            q.next = null;
            q.prev = null;
        
    
    size --;
    return ret;

测试类:LinkedListDemo

package cn.linkedlist.demo05;
public class LinkedListDemo 
    public static void main(String[] args) 
        LinkedList<Integer> list = new LinkedList<>();
        System.out.println("===链表头部插入===");
        //System.out.println(list);

        list.add(0, 1);
        list.add(0, 2);
        list.add(0, 3);
        System.out.println(list);

        System.out.println("===链表尾部插入===");
        list.add(list.size(), 4);
        list.add(list.size(), 5);
        list.add(list.size(), 6);
        System.out.println(list);

        System.out.println("==删除元素==");
        System.out.println(list.remove(3));
        System.out.println(list);
    

3、执行结果

1.4 修改和获取操作

1、代码示例

链表类: LinkedList

//获取链表中指定索引处的元素
@Override
public E get(int index) 
    if (index < 0|| index > size) 
        以上是关于双向循环链表实现的主要内容,如果未能解决你的问题,请参考以下文章

《链表》之带头双向循环链表

数据结构开发:循环链表与双向链表

C语言实现双向非循环链表的逆序打印

C语言实现双向非循环链表的节点插入

带头双向循环链表 代码实现 review

数据结构:如何用C语言快速实现带头双向循环链表