~目录

一.结点的定义

二.双向链表的定义

三.双向链表的操作

1.头插法

2.尾插法

3.任意位置插入(第一个数据节点为0号下标 )

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

5.得到单链表的长度

6.打印单链表

7.删除第一次出现关键字为key的节点

8.删除所有值为key的节点


单链表的基本操作详解: 

一.结点的定义

在c语言中,我们使用一个结构体来定义一个结点。但是在java中,我们使用一个类(class)来定义结点。

其中data表示当前结点中存放的数据,next表示当前结点的下一个结点,pre表示的是当前结点的前一个结点。这里还定义了一个构造方法用于初始化data的值。

public class ListNode {
        public int data;
        ListNode next;
        ListNode prev;

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

二.双向链表的定义

这里我们定义的单链表为不带头结点的非循环双向链表,但是要有一个虚拟的头结点,来表示我们这个单链表的第一个结点。同时还有一个尾结点,表示链表的最后一个结点。

public class RealLinkedList {
    public ListNode head;
    public ListNode tail;   //指向链表的最后一个结点,有了tail以后,尾插法的时间复杂度变成O(1)
}

三.双向链表的操作

下面要介绍的是双向链表的相关操作,这些函数放在上面的单链表RealLinkedList中。经测试,所有功能均能正确实现

1.头插法

就是在最前插入,注意对链表为空的特判

这里需要注意对tail的处理。头插法的话,tail始终是最开始那个head,所以只需要赋值一次

先创建一个node,把数据放进这个node,再把这个node头插

public void addFirst(int data){
        ListNode node = new ListNode(data);
        if (this.head == null){
            this.head = node;
            this.tail = node;    //头插法的话,tail始终是最开始那个head,所以只需要赋值一次
        }else{
            node.next = this.head;
            this.head.prev = node;
            this.head = node;
        }
    }

2.尾插法

就是在最后面插入,注意对链表为空的特判

先创建一个node,把数据放进这个node,再把这个node尾插

注意:在双向链表中,我们引入了tail结点表示链表的最后一个结点,所以和单链表不同,此时我们不需要再像单链表那样去寻找最后一个结点,直接让tail.next = node即可,注意这里要对tail不断得进行更新

public void addLast(int data){
        ListNode node = new ListNode(data);
        if (this.head == null){
            this.head = node;
            this.tail = node;
        }else{
            this.tail.next = node;
            node.prev = this.tail;
            this.tail = node;
        }
    }

3.任意位置插入(第一个数据节点为0号下标 )

就主要是对index的特判,如果index不合法(<0或者>length),返回false,若index为0,那就是头插,若Index为length,那就是尾插。

其他位置不需要像单链表那样去找到index前面的结点了,直接去找Index当前结点即可

public boolean addIndex(int index,int data){
        if (index < 0 || index > this.size()){
            return false;
        }
        if (index == 0){
            this.addFirst(data);
            return true;
        }
        if (index == this.size()){
            this.addLast(data);
            return true;
        }

        ListNode node = new ListNode(data);
        ListNode cur = searchIndex(index);
        
        node.prev = cur.prev;
        node.next = cur;
        cur.prev.next = node;
        cur.prev = node;
        return true;
    }

 searchIndex(int index)函数

public ListNode searchIndex(int index){
        ListNode cur = this.head;
        int i = 0;
        while (i < index){
            cur = cur.next;
            i++;
        }
        return cur;
    }

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

这个就很简单了,直接一轮循环结束

public boolean contains(int key){
        if (this.head == null){
            System.out.println("链表为空");
            return false;
        }
        ListNode p = this.head;
        while(p != null){
            if(p.data == key){
                return true;
            }
            p = p.next;
        }
        return false;
    }

5.得到单链表的长度

这个也是非常简单,一轮循环结束

public int size(){
        ListNode p = this.head;
        int len = 0;
        while (p != null){
            p = p.next;
            len++;
        }
        return len;
    }

6.打印单链表

也是一轮循环结束,这里我是每个结点的数据一行这样打印了,也可以打印成一行

public void display(){
        ListNode cur = head;
        while (cur != null){
            System.out.println(cur.data);
            cur = cur.next;
        }
    }

7.删除第一次出现关键字为key的节点

主要还是一些特判,1.链表为空;2.key值为头结点上的data;3.寻找到最后一个结点的时候防止空指针异常

注意特判里面要return,以为只需要删除第一次出现的结点。

这里要注意,和单链表不同,这里如果要删除的是第一个结点,光head = head.next不够,还要把prev置空, 是因为单链表head=head.next后,已经没有引用是第一个结点的了,堆中的空间会被jvm自动回收,而这里的还有prev引用着第一个结点,固需要置空

此外就是在处理cur.next.prev的时候,如果此时的cur是最后一个结点,则会出现空指针异常,固需要判断此时cur.next是否为空,同时也要注意对tail的值进行更新

其他的位置,直接找值为key的结点就行,不需要像单链表那样去找它的前一个及诶点

public void remove(int key){
        if (this.head == null){
            System.out.println("链表为空");
            return ;
        }
        if (this.head.data == key){    //如果要删除的是第一个结点,进行一波特判
            this.head = head.next;
            this.head.prev = null;
            //注意这里要把prev置空,在单向链表中不需要置空
 // 是因为单链表head=head.next后,已经没有引用是第一个结点的了,堆中的空间会被自动回收,而这里的还有prev引用着第一个结点,固需要置空
            return;
        }
        ListNode cur = this.head;
        while (cur != null){
            if (cur.data == key){
                cur.prev.next = cur.next;
                if (cur.next == null){
                    this.tail = cur.prev;    //如果删除的是最后一个结点,不要忘记对tail的值进行修改
                    break;
                }
                cur.next.prev = cur.prev;    //如果cur为最后一个结点,这里会出现空指针异常
                break;
            }
            cur = cur.next;
        }
    }

8.删除所有值为key的节点

有了上面的基础,这个就比较简单了,把上面删除第一个值为key结点的代码稍加改动即可。把找到data就break的break删除即可;但是很重要,详细的过程可以参考本人单链表的博客,有详细的图解。


public void removeAllKey(int key){
        if (this.head == null){
            System.out.println("链表为空");
            return ;
        }
        ListNode cur = head.next;
        while (cur != null){
            if (cur.data == key){
                cur.prev.next = cur.next;
                if (cur.next == null){
                    this.tail = cur.prev;    //如果删除的是最后一个结点,不要忘记对tail的值进行修改
                    break;
                }
                cur.next.prev = cur.prev;    //如果cur为最后一个结点,这里会出现空指针异常
            }
            cur = cur.next;
        }

        if (this.head.data == key){
            this.head = this.head.next;
            this.head.prev = null;
        }
    }