双向链表的实现(双向链表与单向链表的简单区别联系和实现)

Posted 小海浪.

tags:

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

🎓前言

之前写了一些文章简单地介绍顺序表,链表的基础知识,还总结了几道链表的笔试题,今天继续开干,向老铁们简单地介绍一下无头双向循环链表及其代码的实现。
为什么要引入无头双向循环链表呢?????
我们可以简单地这样去思考,单向链表只能单向地从头节点去访问其他的节点,不能会退的访问其他的节点,也不能循环地访问,与单向链表不同,双向链表在插入删除的时候不需要寻找前驱节点,因为本身就能回到前面一个节点,**查找时,我们可以用二分发的思路,从首节点(head)向后查找操作和last(尾结点)向前查找操作同步进行,这样双链表的查找可以提高一倍,**在实现双向链表的增删改查的时候他们的操作也有不同,我们可以前后对比一下,就能更好地理解他们了。

🎓单向链表和双向链表的优缺点及使用场景

单向链表:只有一个指向下一个节点的指针。 优点:单向链表增加删除节点简单。遍历时候不会死循环
缺点:只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进。 适用于节点的增加删除。

双向链表:有两个指针,一个指向前一个节点,一个指向后一个节点。 优点:可以找到前驱和后继,可进可退;
缺点:增加删除节点复杂,需要多分配一个指针存储空间。 适用于需要双向查找节点值的情况

下面是需要注意的地方:

1、引入了前驱域,解决了单链表只能单向访问的痛点。
2、对于双向链表来说,要注意:第一个节点的前驱是null和最后一个节点后继是null
3、引入一个last,标志尾巴节点。

📝双向链表的简单实现及图示分析

双向链表的简单模型如图:

声明两个类一个 ListNode,节点这个类它的成员属性包括节点的数值data,prev前驱域,next后继域,一个 MyLinkedList链表这个类它的成员属性,包括头结点head,尾巴节点last,curNext节点等。
代码如下:

//节点类
 class ListNode {

        public int data;
        public ListNode prev;
        public ListNode next;

        public ListNode(int data) {
            this.data = data;
        }
    }
//链表这个类
 public class MyLinkedList {
        public ListNode head;//头结点
        public ListNode last;//尾结点
        ListNode curNext;//记录下一个节点
}

头插法
基本思路
**新插入的节点是头结点,**如果是第一次插入,之前没有一个节点,新插入的节点就是头节点,如果在头插之前有节点,则使node的next指向头节点,head的prev指向node ,之后新的头结点变为新插入的节点即可。
如图分析:

代码实现:

 //头插法
        public void addFirst(int data) {
            ListNode node = new ListNode(data);
            if (head == null) {
                this.head = node;
                this.last = node;//可写可不写
            } else {
                node.next = head;
                this.head.prev = node;
                this.head = node;

            }

        }

尾插法:
插入的节点是尾巴节点,如果是第一插入,插入的节点既是头也是尾,如果不是第一插入,使最后一个节点的next指向node,node的prev指向last节点即可。
如图:

 //尾插法
        public void addLast(int data) {
            ListNode node = new ListNode(data);
            if (head == null) {
                this.head = node;
                this.last = node;
            } else {
                last.next = node;
                node.prev = last;
                last = node;
            }

        }

任意位置插入,第一个数据节点为0号下标:
首先判断插入位置的合法性如果,插入位置index为负或者大于链表的长度,则输入的位置不合法,如果插入的位置等于链表的长度,则为尾插法,插入位置为0位置,则为头插法,中间的位置则正常插入。在插入的时候我们还用到一个查找某一位置的函数 findIndex,通过遍历的方式,寻找插入的位置,返回给插入函数。在插入的时候插入的节点和前后的节点要连起来,连接的顺序不管,但一定要连接对。
图示分析:

代码实现:

//任意位置插入,第一个数据节点为0号下标
        public void addIndex(int index, int data) {
            //判断位置的合法性,任意位置插入,第一个数据节点为0号下标
            if (index < 0 || index > size()) {
                System.out.println("输入的位置不合法");
                return;
            } else {
                if (index == 0) {
                    addFirst(data);
                    return;
                }
                if (index == size()) {
                    addLast(data);
                } else {
                    ListNode node = new ListNode(data);
                    ListNode cur;
                    cur = findIndex(index);
                    cur.prev.next = node;//当index==size时,如果不采用尾插法,会出现空指针异常
                     node.prev = cur.prev;
                    cur.prev = node;
                   
                    node.next = cur;
                }
            }

        }

 //找到某一位置的节点
 //从头节点开始,要到0位置走0步,要到n位置走n步
        public ListNode findIndex(int index) {
            ListNode cur = head;
            while (index != 0) {
                cur = cur.next;
                index--;

            }
            return cur;
        }

删除第一次出现关键字为key的节点
大致思路就是,遍历链表寻找要删除的节点,如果找到删除就结束删除了,没找到一直遍历完链表,找到的节点又要分是头结点还是尾结点中间节点,找到头结点还要分是不是只有头结点一个节点还是有多个节点,尾结点不用再分了,中间节点正常删除即可。
下面结合代码和图示再理解下:

 //删除第一次出现关键字为key的节点
        public void remove(int key) {

            ListNode cur = this.head;
            while (cur != null) {
                //
                if (cur.data == key) {
                    //判断是不是头结点
                    if (cur == this.head) {
                        this.head =this.head.next;
                        //头结点是唯一的一个节点
                        if (this.head == null) {
                            this.last = null;

                        } else {
                            //头结点不是唯一的节点
                            this.head.prev = null;
                        }
                    } else {
                        cur.prev.next = cur.next;
                        //判断是不是最后一个节点
                        if (cur.next == null) {
                            this.last = cur.prev;
                        } else {
                            cur.next.prev = cur.prev;
                        }


                    }
                    return;

                } else {
                    cur = cur.next;
                }
            }

        }

删除所有值为key的节点
这个思路和上面删除第一次出现的key值几乎一样,只是要删除所有出现的key值,我们只要在判断是否是key值if语句后面加个cur=cur.next,这样如果找到要删除的元素继续向后找了删除,没有找到也继续向后找,直到遍历完链表即可。

 //删除所有值为key的节点
        public void removeAllKey(int key) {
            ListNode cur = head;
            while (cur != null) {
                //
                if (cur.data == key) {
                    //判断是不是头结点
                    if (cur == this.head) {
                        this.head = this.head.next;
                        //头结点是唯一的一个节点
                        if (cur.next == null) {
                            this.last = null;

                        } else {
                            //头结点不是唯一的节点
                            this.head.prev = null;
                        }
                    } else {
                        cur.prev.next = cur.next;
                        //判断是不是最后一个节点
                        if (cur.next == null) {
                            this.last = cur.prev;
                        } else {
                            cur.next.prev = cur.prev;
                        }


                    }

                }
                cur = cur.next;继续往后走  不要停 直到为null 的时候
            }
        }

得到单链表的长度:
这个就很简单,遍历链表节点不为空,计数器count加一,一直遍历完即可。

 //得到单链表的长度
        public int size() {
            ListNode cur =this.head;
            int count = 0;
            while (cur != null) {
                count++;
                cur = cur.next;
            }
            return count;
        }

打印链表

 public void display() {

            ListNode cur = this.head;
            while (cur != null) {
                System.out.print(cur.data + " ");
                cur = cur.next;

            }
            System.out.println();

        }

查找是否包含关键字key是否在单链表当中

//查找是否包含关键字key是否在单链表当中
        public boolean contains(int key) {
            /
            if(this.head==null) return false;
            ListNode cur = this.head;
            while (cur != null) {
                if (cur.data == key) {
                    return true;
                }
                cur=cur.next;

            }
            //return false;
            return false;
        }

清空链表
清空链表不能简单的把head置为null,last置为null,要遍历链表把每个节点的前驱后继都置为null,但要注意在把节点的值置为null的时候要提前记录下一个节点的位置。

   public void clear() {
            ListNode cur =  this.head;

            while (cur != null) {
                curNext = cur.next;
                cur.prev = null;
                cur.next = null;
                cur = curNext;//提前记录下一个节点的位置

            }
            this.head = null;
            this.last = null;


        }

完整代码:
MyLinkedList.java

 class ListNode {

        public int data;
        public ListNode prev;
        public ListNode next;

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

    public class MyLinkedList {
        public ListNode head;//头结点
        public ListNode last;//尾结点
        ListNode curNext;//记录下一个节点

        //找到某一位置的节点
        public ListNode findIndex(int index) {
            ListNode cur = head;
            while (index != 0) {
                cur = cur.next;
                index--;

            }
            return cur;
        }


        //头插法
        public void addFirst(int data) {
            ListNode node = new ListNode(data);
            if (head == null) {
                this.head = node;
                this.last = node;//可写可不写
            } else {
                node.next = head;
                this.head.prev = node;
                this.head = node;

            }

        }

        //尾插法
        public void addLast(int data) {
            ListNode node = new ListNode(data);
            if (head == null) {
                this.head = node;
                this.last = node;
            } else {
                last.next = node;
                node.prev = last;
                last = node;
            }

        }

        //任意位置插入,第一个数据节点为0号下标
        public void addIndex(int index, int data) {
            //判断位置的合法性,任意位置插入,第一个数据节点为0号下标
            if (index < 0 || index > size()) {
                System.out.println("输入的位置不合法");
                return;
            } else {
                if (index == 0) {
                    addFirst(data);
                    return;
                }
                if (index == size()) {
                    addLast(data);
                } else {
                    ListNode node = new ListNode(data);
                    ListNode cur;
                    cur = findIndex(index);
                    cur.prev.next = node;//当index==size时,如果不采用尾插法,会出现空指针异常
                    node.prev = cur.prev;
                    cur.prev = node;

                    node.next = cur;
                }
            }

        }

        //查找是否包含关键字key是否在单链表当中
        public boolean contains(int key) {
            //修改错误
            if(this.head==null) return false;
            ListNode cur = this.head;
            while (cur != null) {
                if (cur.data == key) {
                    return true;
                }
                cur=cur.next;

            }
            //return false;
            return false;
        }

        //删除第一次出现关键字为key的节点
        public void remove(int key) {

            ListNode cur = this.head;
            while (cur != null) {
                //
                if (cur.data == key) {
                    //判断是不是头结点
                    if (cur == this.head) {
                        this.head =this.head.next;
                        //头结点是唯一的一个节点
                        if (cur.next == null) {
                            this.last = null;

                        } else {
                            //头结点不是唯一的节点
                            this.head.prev = null;
                        }
                    } else {
                        cur.prev.next = cur.next;
                        //判断是不是最后一个节点
                        if (cur.next == null) {
                            this.last = cur.prev;
                        } else {
                            cur.next.prev = cur.prev;
                        }


                    }
                    return;

                } else {
                    cur = cur.next;
                }
            }

        }

        //删除所有值为key的节点
        public void removeAllKey(int key) {
            ListNode cur = head;
            while (cur != null) {
                //
                if (cur.data == key) {
                    //判断是不是头结点
                    if (cur == this.head) {
                        this.head = this.head.next;
                        //头结点是唯一的一个节点
                        if (cur.next == null) {
                            this.last = null;

                        } else {
                            //头结点不是唯一的节点
                            this.head.prev = null;
                        }
                    } else {
                        cur.prev.next = cur.next;
                        //判断是不是最后一个节点
                        if (cur.next == null) {
                            this.last = cur.prev;
                        } else {
                            cur.next.prev = cur.prev;
                        }


                    }

                }
                cur = cur.next;继续往后走  不要停 直到为null 的时候
            }
        }

        //得到单链表的长度
        public int size() {
            ListNode cur =this.head;
            int count = 0;
            while (cur != null) {
                count++;
                cur = cur.next;
            }
            return count;
        }

        public void display() {

            ListNode cur = this.head[单向链表与双向链表的实现]

单向链表和双向链表的原理及其相关实现

链表的java实现(单向双向链表,单向链表的反转)

链表的java实现(单向双向链表,单向链表的反转)

JavaScript 实现双向链表的操作

常用数据结构:单向链表和双向链表的实现