数据结构(JAVA)单向,双向链表


1. 链表

链表是一种物理存储结构上非连续 存储结构,数据元素的逻辑顺序 是通过链表中的引用链接次序实现的 。

链表中包含两种域:

  1. 存储数值的域

  2. 存储引用(相当于c语言中的地址)的域

2. 链表的分类

链表根据3种分类方式分类

  1. 单向或双向

  2. 带头或不带头

  3. 循环或不循环

更据以上3种分类方式,共有8种。

3. 单向链表

这里我们实现的是单向不带头不循环的链表。

java 复制代码
public class SingleLinkedList {
    //头插法
    public void addFirst(int data){
    }
    //尾插法
    public void addLast(int data){
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        return false;
    }
    //删除第一次出现关键字为key的节点
    public void remove(int key){
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
    }
    //得到单链表的长度
    public int size(){
        return -1;
    }
 
    public void clear() {
    }
     
 }

3.1 内部类

首先我们要创建一跟内部类包含存储数值的域和存储引用的域,之后我们需要引用来把我们的链表串联起来,第一个节点用head来表示。

java 复制代码
    static class ListNode {
        public int val;
        public ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;

3.2 头插法

思路:

将插入节点的下一个节点(next)指向头节点(head)

把插入节点改为头节点

java 复制代码
    //头插法
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        node.next = head;
        head = node;
    }

3.3 尾插法

思路:

先判断链表是否为空(因为尾插涉及到next域,链表如果为空会空指针异常)

遍历链表,找到尾节点,使尾点的next指向尾插节点

java 复制代码
    //尾插法
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            return;
        }
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
    }

3.4 任意位置插入,第一个数据节点为0号下标

思路:

判断下标是否违法

判断是否是头结点或尾结点

遍历链表找到插入下标

java 复制代码
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
        ListNode node = new ListNode(data);
        if (index < 0 || index >= size()) {
            System.out.println("index下标不合法");
            return;
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }        
        ListNode cur = head;
        while (index - 1 >= 0) {
            cur = cur.next;
            index--;
        }
        node.next = cur.next;
        cur.next = node;
    }

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

思路:

遍历链表找到关键字即可

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

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

思路:

判断链表是否为空,为空直接返回

判断头节点是否为要删的节点,是就直接头节点指向下一个节点

遍历链表找到要删的节点的前一个节点,当前节点的next指向下一个节点的next

java 复制代码
    //查找关键字key
    private ListNode findNodeOfKey(int key) {
        ListNode cur = head;
        while (cur.next != null) {
            if (cur.next.val == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

    //删除第一次出现关键字为key的节点
    public void remove(int key) {
        if (head == null) {
            return;
        }
        if (head.val == key) {
            head = head.next;
            return;
        }
        ListNode cur = findNodeOfKey(key);
        if(cur == null) {
            return;
        }
        cur.next = cur.next.next;
    }

3.7 删除所有值为key的节点

思路:

判断链表是否为空,为空返回

遍历链表当前节点的val值为关键字key,prev的next指向cur的next

判断头节点的val是否为要删除为key的节点,是就直接头节点指向下一个节点

java 复制代码
    //删除所有值为key的节点
    public void removeAllKey(int key) {
        if (head == null) {
            return;
        }
        ListNode prev = head;
        ListNode cur = head.next;
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
            } else {
                prev = cur;
                cur = cur.next;
            }
        }
        if (head.val == key) {
            head = head.next;
        }
    }

3.8 得到单链表的长度

思路:

定义一个计数器,遍历链表,只要节点不为空,就计数器++

java 复制代码
    //得到单链表的长度
    public int size() {
        int len = 0;
        ListNode cur = head;
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        return len;
    }

3.9 清空单链表

思路:

遍历链表,定义一个临时变量存储当前节点的下一个节点,然后将当前节点的next置为空,最 后将头节点置为空

java 复制代码
    public void clear() {
        ListNode cur = head;
        while (cur != null) {
            ListNode curN = cur.next;
            cur.next = null;
            cur = curN;
        }
        head = null;
    }

3.10 单向链表的优缺点

优点:对于增删查改操作简单

缺点:只能从头到尾遍历,只能找到后驱,不能找到前驱

4. 双向链表

这里我们实现一个双向不带头不循环的链表

java 复制代码
 public class MyLinkedList {
   //头插法
    public void addFirst(int data){ }
    //尾插法
    public void addLast(int data){}
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){}
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){}
    //删除第一次出现关键字为key的节点
    public void remove(int key){}
    //删除所有值为key的节点
    public void removeAllKey(int key){}
    //得到链表的长度
    public int size(){}
    //清空链表
    public void clear(){}
 }

4.1 内部类

和上面的单向链表差不多,只需新增一个存储前驱的域。第一个节点用head来表示,最后一个节点用last来表示。

java 复制代码
    static class ListNode {
        public int val;
        public ListNode next;
        public ListNode prev;

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

4.2 头插法

思路:

判断链表是否为空,是就将头节点,尾节点指向插入节点

否则,插入节点的next指向头节点,头节点的prev指向插入节点,头节点指向插入节点

java 复制代码
    //头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(head == null && last == null) {
            head = node;
            last = node;
        }else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }

4.3 尾插法

思路:

判断链表是否为空,是就将头节点,尾节点指向插入节点

否则,尾节点的后驱next指向插入节点,插入节点的前驱prev指向尾节点,尾节点指向插入节 点

java 复制代码
    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null && last == null) {
            head = node;
            last = node;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

4.4 任意位置插入,第一个数据节点为0号下标

思路:

判断下标是否合法,不合法直接返回

如果插入的位置是头节点,直接用头插法 ,如果插入的位置是尾节点,直接用尾插法

如果插入的位置是中间,遍历链表,找到插入的对应位置,然后插入节点的后驱next指向对应 节点,插入节点的前驱prev指向对应节点的前驱prev,对应节点的前驱prev的后驱next指向插 入节点,对应节点的前驱prev指向插入节点

java 复制代码
    //判断下标是否合法 
    public boolean checkIndex(int index) {
        if(index < 0 || index >= size()) {
            System.out.println("下标违法");
            return false;
        }
        return true;
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        ListNode node = new ListNode(data);
        if(checkIndex(index) == false) {
            return;
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }
        ListNode cur = head;
        while (index - 1 >= 0) {
            cur = cur.next;
            index--;
        }
        node.next = cur;
        node.prev = cur.prev;
        cur.prev.next = node;
        cur.prev = node;

    }

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

思路:

遍历链表找到关键字即可

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

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

思路:

如果链表为空,直接返回

遍历链表,找关键字为key的节点,如果头节点是,直接头接点指向头节点的下一个节点,然 后把头节点的前驱prev置为空,如果是尾节点,尾节点指向尾节点的前驱prev,尾节点的后 驱next置为空,其他位置,删除节点的前驱prev的后驱next指向删除节点的后驱next,删除节 点的后驱next的前驱prev指向删除节点的前驱prev

java 复制代码
    //删除第一次出现关键字为key的节点
    public void remove(int key){
        if(head == null) {
            return;
        }
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == key) {
                if(cur == head) {
                    head = head.next;
                    head.prev = null;
                }else {
                    if(cur.next == null) {
                        last = last.prev;
                        last.next = null;
                    }else {
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }

4.7 删除所有值为key的节点

思路:

和删除第一次出现关键字为key的节点的思路一样

java 复制代码
    //删除所有值为key的节点
    public void removeAllKey(int key){
        if(head == null) {
            return;
        }
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == key) {
                if(cur == head) {
                    head = head.next;
                    head.prev = null;
                }else {
                    if(cur.next == null) {
                        last = last.prev;
                        last.next = null;
                    }else {
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }

4.8 得到链表的长度

思路:

定义一个计数器,遍历链表,只要节点不为空,就计数器++

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

4.9 清空链表

思路:

遍历链表,定义一个临时变量存储当前节点的下一个节点,然后将当前节点的后驱next,前 驱prev置为空,最后将头节点,尾节点置为空

java 复制代码
    //清空链表
    public void clear(){
        ListNode cur = head;
        while(cur != null) {
            ListNode curNext = cur.next;
            cur.next = null;
            cur.prev = null;
            cur = curNext;
        }
        head = last = null;
    }

4.10 双向链表的优缺点

优点:有了前驱,增加链表的灵活型

缺点:删除节点操作复杂

5. LinkedList

  1. LinkedList的底层使用了双向链表

  2. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问

  3. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)

5.1 LinkedList的构造

5.2 LinkedList的常用方法

5.3 ArrayList 和 LinkedList 的对比

相关推荐
mghio8 小时前
Dubbo 中的集群容错
java·微服务·dubbo
咖啡教室13 小时前
java日常开发笔记和开发问题记录
java
咖啡教室13 小时前
java练习项目记录笔记
java
鱼樱前端14 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea14 小时前
6. RabbitMQ 死信队列的详细操作编写
java·消息队列·rabbitmq
RainbowSea15 小时前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq
我不会编程55516 小时前
Python Cookbook-5.1 对字典排序
开发语言·数据结构·python
李少兄16 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝16 小时前
【设计模式】原型模式
java·设计模式·原型模式