单链表详解
一、前言
在上篇文章中,我们详细的讲解了顺序表,这里链接给大家放在下面。没看过的同学可以看一看,因为单链表就是在顺序表之上,做出优化的线性表。
新学期预习吗?保姆级讲解数据结构之顺序表的9个方法+手撕代码
大家还记得顺序表中的add方法吗?那写起来简直是一个麻烦,又要考虑pos下标是否合法,还得一个一个的把后面的元素移到前面,这样一来,不仅代码写起来麻烦,代码执行的时间复杂度也高。但也不是没有优点。
顺序表:由数组构成,那么它也就包含了数组的特性。
优点:
1、查询元素时非常方便快捷,可以通过一个下标,能够快速的找到当前的数据。O(1).
缺点:
1、每次都会浪费大小不等的内存,假设当前数组大小为20;如果你要放第21个元素的时候,就需要扩容,假设扩容2倍后,大小是40,只放一个元素,那么剩下19个就浪费掉了。
2、每次扩容的时候,都需要拷贝数组的内容,拷贝的过程中也是需要时间的,造成一定程度资源的浪费。
3、删除和插入数据的时候,都需要移动数据。
那么问题来了,有没有一种表,又不浪费内存,删除、插入数据的时候还方便快捷呢?
这时候能够解决上面三个问题的链表来了!
二、什么是链表?
2.1、链表的概念:
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
2.2、链表的分类:链表的结构非常多样化,根据带头和不带头,双向和单向,循环和不循环可以组成很多种链表。
随便链表的种类有很多,但是只需要重点掌握两种即可。
2.2、两种重要的单链表
1、无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
1、无头双向循环链表:在Java集合框架当中,LinkedList的底层就是由这种链表实现的。
注意:每个链表的400、200、300均为节点的引用地址,实际上是以16进制的方式存储的,这里我为了画图方便,进行了简写。
2.3、关于单链表的一些基础知识
1、链表在逻辑上是连续的,但是物理上不一定是连续的。这里的物理上,指的是内存地址上。
2、单链表的值域:
对于单链表来说,每一个节点都3个属性,在下图中最上面的123,指的是当前节点的地址(本来应该是16进制,这里为了方便说明用10进制)。val域:val是value的缩写,就是值的意思,这里存放的是当前节点需要存储的信息。
next域:它的类型是一个节点类型。由于单链表在逻辑上是连续的,故对于一个节点来说,它不仅需要存放自己的val值,它还需要存放自己的下一个节点的地址->next,以此来实现逻辑上的连续。
3、单链表的结构
头节点+数据节点
三、单链表的实现
光说不练假把式,我们了解了链表的结构、用处。现在我们来亲手实现一个自己的单链表。
3.1、穷举法创建一个简单的链表
这里我先用穷举的方式一一创建节点,这样方便理解,下文再讲述用其他方法创建链表
public class MylinkedList {
public ListNode head;//标识这个链表的头
/**
* 穷举法,最简单的方式把链表一个一个列举出来。
*/
public void createList(){
ListNode listNode1=new ListNode(12);
ListNode listNode2=new ListNode(13);
ListNode listNode3=new ListNode(23);
ListNode listNode4=new ListNode(33);
ListNode listNode5=new ListNode(44);
listNode1.next=listNode2;
listNode2.next=listNode3;
listNode3.next=listNode4;
listNode4.next=listNode5;
//head引用 引用的是listNode1引用 引用的对象
this.head=listNode1;
}
}
class ListNode{
public int val;//值
public ListNode next;//储存下一个节点的地址,它是一个引用
/**
* 不带参数的构造方法
*/
public ListNode(){
}
/**
* 带一个参数的构造方法
*/
public ListNode(int val){
this.val=val;
}
}
现在链表有了,那么问题来了,数组可以通过for循环的方式遍历链表,或者直接通过数组下标找到某个节点,那如何遍历链表当中的每个元素呢?
先看这样一行代码:
head=head.next<