数据结构——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:链表的实现
package linkedlist; import java.util.LinkedList; public class Test1 { public static void main(String[] args) { LinkedListlist = 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~~~
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= 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
关于clear:
一旦head==null,此时1就没有无人指向
1这个节点,就会被GC给释放掉了
1被释放之后,2就没有人指向了,2也会被GC释放,后面也差不多,依此类推。
四:总结
本篇博客讲述了链表的概念以及使用方法,最后模拟实现了链表。
如果有不明白的可以评论哦。






浙公网安备 33010602011771号