数据结构——LinkedList和链表

目录

一:ArrayList的缺陷

二:链表

[2.1 :链表的概念及结构](#2.1 :链表的概念及结构)

2.2:链表的实现

三:LinkedList模拟实现

四:总结


一:ArrayList的缺陷

由于其底层是⼀段连续空间,当在ArrayList任意位置插⼊或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率⽐较低,因此ArrayList不适合做任意位置插⼊和删除⽐较多的场景。因此:java集合中⼜引⼊了LinkedList,即链表结构。

二:链表

2.1 :链表的概念及结构

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

白话一点:

链表不要求连续的内存空间(离散的结构)

元素和元素之间,内存是不连续的,并且这些元素的空间是没啥规律的(顺序上没有要求,内存上也没有要求)

如何知道链表中包含哪些元素、如何遍历链表中所以元素?

此处就把每个节点上面都引入一个引用变量,称为next

使用直观引用保存下一个元素对应的内存空间。

就是在a中存放一个next如何存放b的地址,以此类推,到d中就存放一个null,后面没有元素了。
链表的特点:

1.链表的元素在离散的内存空间上

2.每个元素中记录下一个元素地址(引用)

需要知道第一个元素是谁,后续整个链表就能拿到了
实际中链表的结构⾮常多样,以下情况组合起来就有8种链表结构:

1:单向或者双向

单链表只能指知道下一个,不知道上一个

双向链表,每个节点包含两个引用,prev、next(前一个元素地址、后一个元素地址)功能多了,但是消耗的空间更多了
2.带头或者不带头(是否带有"傀儡节点")

头节点:应该是链表第一个节点

傀儡节点:这个节点不存储数据,而是占位置,来简化后续代码

其实带头也有傀儡节点,只是不显示,如图带头,其实head的next指向傀儡节点,但是傀儡节点不存储数据,那么head的next就指向d1
3.循环或者⾮循环

重点关注:

⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。

⽆头双向链表:在Java的集合框架库中LinkedList底层实现就是⽆头双向循环链表
在java标准库中,LinkedList就是现成的实现,都是实现List的接口。

2.2:链表的实现

java 复制代码
package linkedlist;  
  
import java.util.LinkedList;  
  
public class Test1 {  
    public static void main(String[] args) {  
        LinkedList<Integer> list = new LinkedList<>();  
        list.add(1);  
        list.add(2);  
        list.add(3);  
        list.add(4);  
  
        list.add(0,5);  
        list.addFirst(6);  
        System.out.println(list);//[6, 5, 1, 2, 3, 4]  
  
        list.remove(2);// 删除索引为2的元素 1        System.out.println(list);//[6, 5, 2, 3, 4]  
  
        list.remove(Integer.valueOf(2));//删除值为2的元素 2        System.out.println(list);//[6, 5, 3, 4]  
  
        //头删  
        list.removeFirst();  
        System.out.println(list);//[5, 3, 4]  
        //尾删  
        list.removeLast();  
       System.out.println(list);//[5, 3]  
  
  
        list.add(1);  
        list.add(2);  
        list.add(3);  
        list.add(4);  
        list.add(5);  
        //通过get方法获取元素  
        System.out.println(list.get(2));//3  
        //通过set方法修改元素  
        list.set(2, 10);  
        System.out.println(list);//[1, 2, 10, 4, 1, 2, 3, 4, 5]  
  
  
        //通过contians方法判断是否包含元素  
        list.add(1);  
        list.add(2);  
        list.add(3);  
        list.add(4);  
        list.add(5);  
        System.out.println(list.contains(3));//true  
        System.out.println(list.contains(6));//false  
  
    }  
}

三:LinkedList模拟实现

以下代码模拟实现都有注释,如果有不理解的可以评论O~~~

java 复制代码
package linkedlist;  
//表示链表的节点  
class Node {  
    // 节点保存值  
    public String value;  
    // 指向下一个节点  
    public Node next;  
  
    public Node(String value) {  
        this.value = value;  
        this.next = null;  
    }  
}  
  
  
public class MyLinkedList {  
         //把链表的头节点表示出来,此时整个链表就能获取了  
    //此时不包含傀儡节点,head==null的时候表示空链表  
    private Node head=null;  
    //不像顺序表使用size表示区间的长度,链表使用length表示链表的长度  
    //但也可以使用size表示个数。  
  
    //插入元素  
    //1:尾插(先找到尾巴,再插入)  
    public void addlast(String value){  
        //如果链表为空  
        if(head==null){  
            //新建一个节点  
            Node newNode=new Node(value);  
            //把新节点放到头部  
            head=newNode;  
            return;        }  
        //先找道尾巴,把新的节点加道尾巴后面  
        //先创建一个头节点  
        Node tail=head;  
        for(;tail.next!=null;tail=tail.next){  
            if(tail.next==null){  
                break;  
            }  
        }//找到尾巴  
        //新建一个节点  
  
        Node newNode=new Node(value);  
        tail.next=newNode;  
        newNode.next=null;  
  
    }  
    //2:头插(  
    public void addfirst(String value) {  
            //新建一个节点  
            Node newNode = new Node(value);  
            //把新节点放到头部  
            //1:就要将newNode的next指向head指向的节点  
            newNode.next = head;  
            //2:然后我们的head在指向(新节点)newNode  
            head = newNode;  
    }  
  
    //3:指定位置插入  
    //在链表中没有下标的概念  
    //链表需要遍历,找位置。  
    //但是java标准库,LinkedList同样引入下标这个概念,使用List统一作为ArrayList和LinkedList的接口  
    public void add(int index,String value) {  
        //先判断index是否合法  
        if (index < 0 || index > size()) {//index==size等于尾插,没必要判断  
            throw new IndexOutOfBoundsException("Index is out of bounds");  
        }  
        //针对头插出现的情况  
        if(index==0){  
            addfirst(value);  
            return;        }  
        //根据当前value值,创建新的节点  
        Node cur = new Node(value);  
        //找到index位置的前一个节点  
        //由于当前链表是单向链表,每个节点稚只能找到next节点  
        //需要修改前一个节点next的值,让它指向当前节点  
        //插入新节点,需要找到index-1位置的节点  
        Node prev = head;  
        for(int i=0;i<index-1;i++){  
            prev=prev.next;  
        }  
        cur.next=prev.next;  
        prev.next=cur;  
    }  
    //虽然没有size()方法,我们可以自己写一个,遍历链表,计算长度  
    public int size() {  
        int size = 0;  
        for(Node cur =head;cur!=null;cur=cur.next){  
            size++;  
        }  
        return size;  
    }  
    //判断某个元素是否存在链表中  
    public Boolean contains(String value){  
        //遍历链表,判断是否有value  
        for(Node cur=head;cur!=null;cur=cur.next){  
            if(cur.value.equals(value)){  
                return true;  
            }  
        }  
        return false;  
    }  
  
    //indexOf方法,查找某个元素的位置  
    public int indexOf(String value){  
        int index=0;  
        for(Node cur=head;cur!=null;cur=cur.next){  
            //每次判断值是否相等,找到就返回index,没有找到就继续遍历index++  
            if(cur.value.equals(value)){  
                return index;  
            }  
            index++;  
        }  
        return -1;  
    }  
  
    //remove方法,删除列表中的元素,根据下标删除  
    //根据下标删除,需要先找到index位置的前一个节点,然后将前一个节点的next指向index+1位置的节点  
    public void remove(int index){  
        if(index<0||index >= size()){  
            throw new IndexOutOfBoundsException("Index is out of bounds");  
        }  
        //特殊处理index为0的情况  
        if(index==0){  
            head=head.next;  
            return;        }  
        //找到被删除元素前一个节点位置  
        Node prev=head;  
        for(int i=0;i<index-1;i++) {  
            prev = prev.next;  
        }  
        //循环结束,prev就指向待删除元素的前一个节点  
        Node delNode=prev.next;  
        //将prev的next指向待删除元素的下一个节点  
        prev.next=delNode.next;  
        delNode.next=null;  
    }  
    //remove方法,删除列表中的元素,根据值删除  
    //根据值删除,需要先找到值所在的节点,然后将前一个节点的next指向index+1位置的节点  
    public void remove(String value){  
        //判读链表是否为空  
        if(head==null){  
            return;  
        }  
        //要删除的元素是头节点  
        if(head.value.equals(value)){  
            //直接将头节点指向头节点的下一个节点  
            head=head.next;  
            return;        }  
        //先找到值所在的节点  
        Node prev=head;  
        for(;prev!=null;prev=prev.next){  
            if(prev.next!=null&&prev.next.value.equals(value)){  
                break;  
            }  
        }  
        //循环结束,prev就指向待删除元素的前一个节点  
        if(prev==null){  
            return ;  
        }  
        //将prev的next指向待删除元素的下一个节点  
        Node delNode=prev.next;  
        //将prev的next指向待删除元素的下一个节点  
        prev.next=delNode.next;  
        delNode.next=null;  
    }  
    public void clear() {  
        //清空链表  
        //直接没有指向头节点的指针,就表示链表为空  
        head=null;  
    }  
  
  
    @Override  
    public String toString() {  
         //通过这个方法,遍历链表,构成一个字符串,方便打印  
        //遍历的时候,需要从头节点开始,进行一个一个元素打印  
        StringBuilder stringbuilder=new StringBuilder();  
        stringbuilder.append("[");  
        for(Node cur=head;cur!=null;cur=cur.next){  
            //NOde cur=head : 指向头节点(开始遍历,取头节点)  
            //cur!=null : 循环条件,当cat不为null的时候,才进行循环  
            //cur.next : 指向下一个节点(把下一个节点取出来)  
            stringbuilder.append(cur.value);  
            //当前我们不希望最后一个元素后面带上逗号,所以这里判断一下  
            if(cur.next!=null) {  
                stringbuilder.append(",");  
            }  
        }  
        stringbuilder.append("]");  
        return stringbuilder.toString();  
    }  
  
    private static void test1(){  
        MyLinkedList list=new MyLinkedList();  
        list.addfirst("a");  
        list.addfirst("b");  
        list.addfirst("c");  
        list.addfirst("d");  
        list.addfirst("e");  
        //还需要遍历链表才能打印出元素  
        System.out.println(list.toString());  
        }  
        public static void  test2(){  
            MyLinkedList list=new MyLinkedList();  
            list.addlast("a");  
            list.addlast("b");  
            list.addlast("c");  
            list.addlast("d");  
            list.addlast("e");  
            //还需要遍历链表才能打印出元素  
            System.out.println(list.toString());  
        }  
        public static void  test3(){  
            MyLinkedList list=new MyLinkedList();  
            list.add(0,"a");  
            list.add(1,"b");  
            list.add(2,"c");  
            list.add(3,"d");  
            list.add(4,"e");  
            list.add(2,"lllllll");  
            System.out.println(list.toString());  
        }  
        public static void  test4(){  
            MyLinkedList list=new MyLinkedList();  
            list.add(0,"a");  
            list.add(1,"b");  
            list.add(2,"c");  
            list.add(3,"d");  
            list.add(4,"e");  
            System.out.println(list.contains("a"));//true  
            System.out.println(list.contains("f"));//false  
        }  
        public static void  test5(){  
            MyLinkedList list=new MyLinkedList();  
            list.add(0,"a");  
            list.add(1,"b");  
            list.add(2,"c");  
            list.add(3,"d");  
            list.add(4,"e");  
            System.out.println(list.indexOf("a"));  
            System.out.println(list.indexOf("f"));  
        }  
        public static void  test6(){  
            MyLinkedList list=new MyLinkedList();  
            list.add(0,"a");  
            list.add(1,"b");  
            list.add(2,"c");  
            list.add(3,"d");  
            list.add(4,"e");  
            list.remove(2);  
            System.out.println(list.toString());  
        }  
        public static void  test7(){  
  
        MyLinkedList list=new MyLinkedList();  
        list.add(0,"a");  
        list.add(1,"b");  
        list.add(2,"c");  
        list.add(3,"d");  
        list.add(4,"e");  
        list.remove("c");  
        System.out.println(list.toString());  
        }  
        public static void  test8(){  
            MyLinkedList list=new MyLinkedList();  
            list.add(0,"a");  
            list.add(1,"b");  
            list.add(2,"c");  
            list.add(3,"d");  
            list.add(4,"e");  
            list.clear();  
            System.out.println(list.toString());  
        }  
    public static void main(String[] args) {  
        test1();//头插  
        test2();//尾插  
        test3();//测试中间元素  
        test4();//测试元素是否存在  
        test5();//测试元素的位置  
        test6();//测试删除元素  
        test7();//测试根据值删除元素  
        test8();//测试情况链表为空  
    }  
  
}

关于clear:

一旦head==null,此时1就没有无人指向

1这个节点,就会被GC给释放掉了

1被释放之后,2就没有人指向了,2也会被GC释放,后面也差不多,依此类推。

四:总结

本篇博客讲述了链表的概念以及使用方法,最后模拟实现了链表。

如果有不明白的可以评论哦。

相关推荐
大飞pkz5 小时前
【设计模式】责任链模式
开发语言·设计模式·c#·责任链模式
gplitems1236 小时前
Gunslinger – Gun Store & Hunting WordPress Theme: A Responsible
开发语言·前端·javascript
fly-phantomWing6 小时前
Maven的安装与配置的详细步骤
java·后端·maven·intellij-idea
大飞pkz7 小时前
【设计模式】六大基本原则
开发语言·设计模式·c#·六大原则
iCxhust7 小时前
Intel8259汇编串口接收转C语言
c语言·开发语言·汇编
掘根8 小时前
【Qt】布局管理器
开发语言·qt
半夏知半秋8 小时前
skynet-socket.lua源码分析
服务器·开发语言·学习·架构·lua
2401_841495649 小时前
【数据结构】红黑树的基本操作
java·数据结构·c++·python·算法·红黑树·二叉搜索树
西猫雷婶9 小时前
random.shuffle()函数随机打乱数据
开发语言·pytorch·python·学习·算法·线性回归·numpy