【数据结构】LinkedList与链表

 【引言】

  • 上篇文章我们介绍了ArrayList的使用以及它的一些相关知识,还模拟实现了简单的顺序表
  • 我们在这个过程中,有了以下的思考:ArrayList的底层是使用数组来存储元素的
  • 这就导致当ArrayList的任意位置插入或删除元素时,就需要将后续元素整体向后移或者向前移,所以时间复杂度为O(N)
  • 因此ArrayList不适合做任意位置插入和删除比较多的场景
  • 所以,Java集合中又引入了LinkedList,即链表结构

目录

1.链表

1.1链表的概念和结构

 1.2单向非循环链表实现

单向非循环链表实现过程的分析 

1.创建链表

 2.如何遍历链表

3.头插

4.尾插

 5.任意位置插入

6.删除 

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

2.LinkedList的模拟实现

3.LinkedList的使用

3.1LinkedList的简单介绍 

3.2LinkedList的使用 

1.LinkedList的构造 

2.LinkedList的一些常用方法

3.LinkedList的遍历

4.LinkedList和ArrayList的区别


 

【数据结构】LinkedList与链表_第1张图片

目录

1.链表

1.1链表的概念和结构

 1.2单向非循环链表实现

单向非循环链表实现过程的分析 

1.创建链表

 2.如何遍历链表

3.头插

4.尾插

5.任意位置插入

6.删除 

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

2.LinkedList的模拟实现

3.LinkedList的使用

3.1LinkedList的简单介绍 

3.2LinkedList的使用 

1.LinkedList的构造 

2.LinkedList的一些常用方法

3.LinkedList的遍历

4.LinkedList和ArrayList的区别


1.链表

1.1链表的概念和结构

           链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的
 【数据结构】LinkedList与链表_第2张图片

 实际中链表的结构非常多样,有单向和双向、带头和不带头、循环和非循环

1. 单向或者双向
【数据结构】LinkedList与链表_第3张图片

2. 带头或者不带头
【数据结构】LinkedList与链表_第4张图片

3. 循环或者非循环
【数据结构】LinkedList与链表_第5张图片

以上情况组合起来,就有8种链表结构

下面我们看一张图

【数据结构】LinkedList与链表_第6张图片

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。这种结构在笔试面试中出现很多。
 

另一个用得较多的是无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表
 

下面我们介绍一下链表是如何实现的

首先简要介绍一下节点的概念

【数据结构】LinkedList与链表_第7张图片

 如图便是一个简单的节点的示意图,它有两个域:数值域和next域

【数据结构】LinkedList与链表_第8张图片

 1.2单向非循环链表实现

我们要实现哪些功能呢?

//头插法
public void addFirst(int data);
//尾插法
public void addLast(int data);
//任意位置插入,第一个数据节点为0号下标
public boolean 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 display();
public void clear();

先看一下整体的代码  

package listdemo;

public class MySingleList {
    //节点是链表的一个组成部分,一般情况下把节点作为内部类
    static class ListNode{
        public int val;//数值域
        public ListNode next;//存储下一个节点的地址

        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;//代表单链表的头节点的引用
    /**
     * 这里只是简单的进行链表的构造
     */
    public void createList(){
        ListNode listNode1 = new ListNode(12);
        ListNode listNode2 = new ListNode(23);
        ListNode listNode3 = new ListNode(34);
        ListNode listNode4 = new ListNode(45);
        ListNode listNode5 = new ListNode(56);

        listNode1.next = listNode2;
        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;

        this.head = listNode1;
    }
    public void display() {
        ListNode cur = head;
        while(cur != null){
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     *头插法
     * @param data
     */
    public void addFirst(int data){
         ListNode node = new ListNode(data);
         if(this.head == null){
             this.head =node;
         }else{
             node.next = this.head;
             this.head = node;
         }
        /*可以直接这样写
        node.next = this.head;
        this.head = node;*/
    }
    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(this.head == null){
            this.head = node;
        }else{
            ListNode cur = head;
            while(cur.next != null){
                cur = cur.next;
            }

            //cur.next==null时
            cur.next = node;
        }

    }
    private void checkIndexAdd(int index){
        if(index<0 || index>size()){
            throw new MySingleListIndexOutOfException("任意插入位置的时候index不合法");
        }
    }

    /**
     * 找到index位置的前一个位置处的节点
     * @param index
     * @return 该节点的地址
     */
    private ListNode findIndexSubOne(int index){
        ListNode cur = this.head;
        int count = index-1;
        while(count!=0){
            cur = cur.next;
            count--;
        }
        return cur;
    }

    /**
     * 任意位置插入,第一个数据节点为0号下标
     * @param index
     * @param data
     */
    public void addIndex(int index,int data) throws MySingleListIndexOutOfException {
        checkIndexAdd(index);
        if(index == 0){
            addFirst(data);//头插法
            return ;
        }
        if(index == size()){
            addLast(data);//尾插法
            return;
        }
        ListNode node = new ListNode(data);
        ListNode cur = findIndexSubOne(index);//找到index位置的前一个节点,记为cur
        //插入
        node.next = cur.next;
        cur.next = node;
    }

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

    /**
     * 删除第一次出现关键字为key的节点
     * @param key
     */
    public void remove(int key){
        if(this.head == null){
            System.out.println("此时链表为空,不能进行删除");
            return ;
        }
        if(this.head.val == key){//判断第一个节点是不是我要删除的节点
            this.head = this.head.next;
            return ;
        }
        ListNode cur = this.head;
        while(cur.next != null){
            if(cur.next.val == key){
                //进行删除
                ListNode del = cur.next;
                cur.next = del.next;
                return ;
            }
            cur = cur.next;
        }

    }

    /**
     * 删除所有值为key的节点
     * 1.要求时间复杂度为 O(N)
     * 2.要求只遍历单链表一遍
      * @param key
     */
    public void removeAllKey(int key){
        //先判断 head 是否为空
        if(this.head == null){
            return ;
        }
        ListNode cur = this.head.next;
        ListNode prev = this.head;

        while(cur != null){
            if(cur.val == key){
                prev.next = cur.next;
                cur = cur.next;
            }else{
                prev = cur;
                cur = cur.next;
            }
        }

        //单独处理头节点问题
        if(this.head.val == key){
            this.head = head.next;
        }
    }
    //得到单链表的长度
    public int size(){
        int count = 0;
        ListNode cur = this.head;
        while(cur != null){
            count++;
            cur = cur.next;
        }
        return  count;
    }

    /**
     * 清空链表所有内容
     * 当我们调用clear函数的时候,会将这个链表当中的所有的节点回收
     */
    public void clear(){
        //做法一,简单粗暴
        //this.head = null;

        //做法二,一个一个删除
        ListNode cur = this.head;
        ListNode curNext = null;
        while(cur != null){
            curNext = cur.next;
            cur.next = null;
            cur = curNext;
        }
        head = null;
    }
}

单向非循环链表实现过程的分析 

1.创建链表

【数据结构】LinkedList与链表_第9张图片

注意:这只是简单意义上的创建链表 

【数据结构】LinkedList与链表_第10张图片

 2.如何遍历链表

【数据结构】LinkedList与链表_第11张图片

3.头插

【数据结构】LinkedList与链表_第12张图片

【数据结构】LinkedList与链表_第13张图片

4.尾插

【数据结构】LinkedList与链表_第14张图片

 5.任意位置插入

【数据结构】LinkedList与链表_第15张图片

【数据结构】LinkedList与链表_第16张图片

6.删除 

【数据结构】LinkedList与链表_第17张图片

一些问题

【数据结构】LinkedList与链表_第18张图片

测试:

【数据结构】LinkedList与链表_第19张图片

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

我们的要求是:

1.时间复杂度为 O(N)

2.最多遍历单链表一遍

首先我们需要清楚,这个删除的核心是:要找到需要删除的节点的上一个节点 

 由于在一开始就处理头节点就需要删除的特殊情况比较困难,所以我们最后再处理

  注:如果我们一开始就处理头节点的问题,那么我们需要用循环语句,直到头节点的值不等于key循环才停止,但是如果我们最后再处理头节点的问题,只需要一个 if 语句判断一下即可 

2.LinkedList的模拟实现

LinkedList底层就是一个双向链表,我们来实现一个双向链表。

首先看一下它有哪些方法

// 2、无头双向链表实现
public class MyLinkedList {
//头插法
public void addFirst(int data);
//尾插法
public void addLast(int data);
//任意位置插入,第一个数据节点为0号下标
public boolean 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 display();
public void clear();
}

 下面我们来模拟实现一下

package LinkedListDemo;

public class MyLinkedList {
    static class ListNode {
        public int val;
        public ListNode prev;//前驱
        public ListNode next;//后继

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

    public ListNode head;//标记头部
    public ListNode last;//标记尾部

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

    }
    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null){
            head = node;
            last = node;
        }else{
            last.next = node;
            node.prev = last;
            last = node;
        }
    }
   private ListNode searchIndex(int index){
        ListNode cur = head;
        while(index != 0){
            cur = cur.next;
            index--;
        }
        return cur;
   }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){

        if(index < 0 || index > size()) {
            System.out.println("index不合法");
        }
        if(index == 0){
            addFirst(data);
            return;
        }
        if(index == size()){
            addLast(data);
            return;
        }
        ListNode cur = searchIndex(index);
        //此时cur拿到了index下标的节点的地址
        ListNode node = new ListNode(data);
        node.next = cur;
        node.prev = cur.prev;
        cur.prev.next = node;
        cur.prev = node;
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = head;
        while(cur != null){
            if(cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    //删除第一次出现关键字为key的节点
    public void remove(int key){
        ListNode cur = head;
        while(cur != null){
            if (cur.val == key) {
                if(cur == head){
                    head = head.next;
                    head.prev = null;
                }else{
                    //中间和尾巴的情况
                    cur.prev.next = cur.next;
                    if(cur.next != null) {
                        //cur不是尾节点
                        cur.next.prev = cur.prev;
                    }else{
                        last = last.prev;
                    }
                }
                return;
            }else{
                cur = cur.next;
            }
        }

    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while(cur != null){
            if (cur.val == key) {
                if(cur == head){
                    head = head.next;
                    head.prev = null;
                }else{
                    //中间和尾巴的情况
                    cur.prev.next = cur.next;
                    if(cur.next != null) {
                        //cur不是尾节点
                        cur.next.prev = cur.prev;
                    }else{
                        last = last.prev;
                    }
                }
                cur = cur.next;
            }else{
                cur = cur.next;
            }
        }


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

    /**
     * 打印
     */
    public void display(){
        ListNode cur = head;
        while (cur != null){
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
    }

    /**
     * 清空链表的内容
     */
    public void clear(){
        ListNode cur = head;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        //最后别忘了把 head 和 last 也置为空
        head = null;
        last = null;
    }
}

3.LinkedList的使用

3.1LinkedList的简单介绍 

  LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后是通过引用将节点连接起来的,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
 

想了解LinkedList的官方文档,点我!!

【数据结构】LinkedList与链表_第20张图片 这是LinkedList的部分源码

下面我们通过一张图加深一下对LinkedList的了解吧 

【数据结构】LinkedList与链表_第21张图片

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

在集合框架中,LinkedList也实现了List接口
【数据结构】LinkedList与链表_第22张图片

3.2LinkedList的使用 

1.LinkedList的构造 

方法 解释
LinkedList() 无参构造
public LinkedList(Collection c) 使用其他集合容器中元素构造List

我们看一个例子

public static void main(String[] args) {
  // 构造一个空的LinkedList
  List list1 = new LinkedList<>();
  List list2 = new java.util.ArrayList<>();
  list2.add("JavaSE");
  list2.add("JavaWeb");
  list2.add("JavaEE");
  // 使用ArrayList构造LinkedList
  List list3 = new LinkedList<>(list2);
}

2.LinkedList的一些常用方法

方法 解释
boolean add(E e) 尾插 e
void add(int index, E element) 将 e 插入到 index 位置
boolean addAll(Collection c) 尾插 c 中的元素
E remove(int index) 删除 index 位置元素
boolean remove(Object o) 删除遇到的第一个 o
E get(int index) 获取下标 index 位置元素
E set(int index, E element) 将下标 index 位置元素设置为 element
void clear() 清空
boolean contains(Object o) 判断 o 是否在线性表中
int indexOf(Object o) 返回第一个 o 所在下标
int lastIndexOf(Object o) 返回最后一个 o 的下标
List subList(int fromIndex, int toIndex) 截取部分 list

几个注意事项:

add

【数据结构】LinkedList与链表_第23张图片

 我们通过源码可以知道,add默认是用的尾插

3.LinkedList的遍历

我们以 LinkedList里放的是Int类型为例

    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList<>();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        linkedList.add(4);
        //for-each 遍历
        for (int x:linkedList) {
            System.out.print(x+" ");
        }
        System.out.println();
        System.out.println("=================");
        //使用迭代器遍历 1
        Iterator it = linkedList.iterator();
        while(it.hasNext()){
            System.out.print(it.next()+" ");
            // it.next() 不仅会打印下一个 还会让it向后走一步
        }
        System.out.println();
        System.out.println("==================");
        //使用迭代器遍历 2
        ListIterator listIterator = linkedList.listIterator();
        while (listIterator.hasNext()){
            System.out.print(listIterator.next()+" ");
        }
        System.out.println();
        System.out.println("===================");;

        //使用迭代器遍历(反向遍历)
        ListIterator listIterator1 = linkedList.listIterator(linkedList.size());
        while(listIterator1.hasPrevious()){
            System.out.print(listIterator1.previous()+" ");
        }
        System.out.println();
    }

4.LinkedList和ArrayList的区别

不同点 ArrayList LinkedList
存储空间上 物理上一定连续 逻辑上连续,但物理上不一定连续
随机访问 支持O(1) 不支持:O(N)
头插 需要搬移元素,效率低O(N) 只需修改引用的指向,时间复杂度为O(1)
插入 空间不够时需要扩容 没有容量的概念
应用场景 元素高效存储+频繁访问 任意位置插入和删除频繁

我们简单来概括下:

1.存储方式上的不同:

ArrayList  是一块连续的内存,物理上和逻辑上都连续

LinkedList  物理上不一定连续,逻辑上是连续的

2.增删查改的区别

增加元素、删除元素比较频繁时,建议使用LinkedList,只需要修改指向就可以

如果要频繁地查找下标、修改,建议使用ArrayList


 


 

    

你可能感兴趣的:(数据结构,链表,数据结构,散列表,java)