概述
1 链表
1.1 概念
那么什么是链表呢?链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
1.从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续。
2.现实中的结点一般都是从堆上申请出来的。
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。
有时,我们为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,谁叫它是第一个呢,有这个特权。也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个节点的指针。
1.2 头结点与头指针的异同
头指针
- 1.头指针是链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
- 2.头指针具有标识作用,所以常用头指针冠以链表的名字。
- 3.无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
头结点
- 1.头结点是为了操作的统一和方便而设立的。放在第一元素的结点之前,其数据域一般无异议(也可以存放链表的长度)。
- 2.有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了。
- 3.头结点不一定是链表必须要素。
实际中链表的结构非常多样:
1.单向或者双向
2.带头或者不带头
3.循环或者非循环
2 链表的实现
2.1 节点的创建
链表是由一个个节点所组成的,后一个结点依靠前一个才能找到,而节点是由数据域(value),以及指针域(next)组成,对于数据域,其是引用类型,存放下一个节点的地址,同时设置构造函数对val进行初始化。
public class MySingleLinkedList {
class ListNode{
public int val;
public ListNode next;
public ListNode(int val, ListNode next) {
this.val = val;
next = null;
}
}
public ListNode head;//代表链表的头结点
}
2.2 创建链表
创建完节点后,我们就要来创建一个链表
public void createList() {
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
ListNode node5 = new ListNode(56);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
this.head = node1;
}
2.3 链表的操作
对于一个创建好节点的链表,它的操作有以下这些:
//头插法
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() {
}
public void display() {}
2.4 链表的插入
链表的插入有三种,头插法,尾插法和指定位置插入。
1.头插法
链表的头插法(Head Insertion)是指在链表的头部添加新节点的操作。在单链表中,这种操作相对简单,因为不需要改变已有节点的指针,只需要创建一个新的节点,并将其链接到当前链表的头部。
-
创建新节点:为新节点分配内存,并设置它的数据域(通常是新插入的数据)和前驱指针(指向当前链表的头节点)。
-
更新头指针:将新节点的后继指针设置为原来的头节点,这样新节点就成为了新的链表头。
-
如果原链表为空(即头指针是 或者指向空),则新节点同时是头节点和尾节点。
//头插法
public void addFirst(int val) {
Node node = new Node(val);
//判断链表是否为空
if(size == 0){
this.head = node;
}else {
//链表不为空
node.next = head;
head = node;
}
//元素个数+1
this.size++;
}
代码语句的顺序不能变,即
node.next = head.next
head.next = node
如果编写顺序调换,head.next的原始数据会丢失。
2.尾差法
链表的尾插法是一种在链表的末尾添加新节点的方法,它通常用于动态数据结构中,因为不需要像数组那样预先知道链表的长度。
-
创建新节点:首先,你需要创建一个新的节点,包含你要插入的数据。
-
获取尾指针:找到当前链表的最后一个节点。如果链表为空,新节点就是第一个节点(头节点)。
-
更新指针:将新节点的指针指向当前的尾节点,表示新节点是下一个元素。
next
-
设置尾节点:如果当前节点就是链表的最后一个节点,那么它的指针也需要更新为新节点,这样新节点就成为了新的尾节点。
public void addLast(int val) {
ListNode node = new ListNode(val);
if(head == null) {
head = node;
return;
}
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
3.任意位置插入
现在,我们要在index位置插入一个整形的value,我们要如何操作呢?既然是在index的位置插入,那么我们就需要知道这个位置前一个位置的信息,所以我们要把cur走index-1步,即在第3位置插入我们的cur就要走2步。
public void addIndex(int index,int val) {
//1.判断index的合法性
try {
checkIndex(index);
}catch (IndexNotLegalException e) {
e.printStackTrace();
}
//2.index == 0 || index == size()
if(index == 0) {
addFirst(val);
return;
}
if(index == size()) {
addLast(val);
return;
}
//3. 找到index的前一个位置
ListNode cur = findIndexSubOne(index);
//4. 进行连接
ListNode node = new ListNode(val);
node.next = cur.next;
cur.next = node;
}
private ListNode findIndexSubOne(int index) {
int count = 0;
ListNode cur = head;
while (count != index-1) {
cur = cur.next;
count++;
}
return cur;
}
private void checkIndex(int index) throws IndexNotLegalException{
if(index < 0 || index > size()) {
throw new IndexNotLegalException("index不合法");
}
}
2.5 查找是否包含关键字key是否在单链表当中
怎么查找呢?我们只要对链表进行遍历即可
public boolean contains(int val) {
ListNode cur = head;
while (cur != null) {
if(cur.val == val) {
return true;
}
cur = cur.next;
}
return false;
}
2.6 链表的删除
1 删除第一次出现关键字为key的节点
现在我们有这么一个链表
我们要删除其中的34这个值,我们应该如何操作?
这个问题的关键所在就是找到34的前一个value,并且删除当前34位置的值。
public void remove(int val) {
//判断是否为空
if(head == null) {
return;
}
if(head.val == val) {
head = head.next;
return;
}
ListNode cur = head;
while (cur.next != null) {
if(cur.next.val == val) {
ListNode del = cur.next;
cur.next = del.next;
return;
}
cur = cur.next;
}
}
2 删除所有值为key的节点
与删除一个元素不同的是,删除所有值为key的节点,在循环判断时找到指定元素时不退出,继续进行查找,直到链表遍历完成.
public void removeAllKey(int val) {
//1. 判空
if(this.head == null) {
return;
}
//2. 定义prev 和 cur
ListNode prev = head;
ListNode cur = head.next;
//3.开始判断并且删除
while(cur != null) {
if(cur.val == val) {
prev.next = cur.next;
//cur = cur.next;
}else {
prev = cur;//prev = prev.next;
//cur = cur.next;
}
cur = cur.next;
}
//4.处理头节点
if(head.val == val) {
head = head.next;
}
}
3 清空链表
要想清空一个链表,我们直接head = null其实就行了,不过我们也可以对每个节点都置空。
public void clear() {
//head = null;
ListNode cur = head;
while (cur != null) {
ListNode curN = cur.next;
//cur.val = null;
cur.next = null;
cur = curN;
}
head = null;
}
2.7 链表的翻转
public ListNode reverseList() {
if(head == null) {
return head;
}
ListNode cur = head.next;
head.next = null;
while(cur != null) {
ListNode curN = cur.next;
//开始翻转
cur.next = head;
head = cur;
cur = curN;
}
return head;
}
对于链表而言,上述代码均为对无头单向非循环链表实现,而我们的链表还有很多,比如无头双向链表实现。
2.8 无头双向链表的实现
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 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;
}
System.out.println();
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListNode cur = head;
while (cur != null) {
if(cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
if(head == null) {
//是不是第一次插入节点
head = 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 = last = node;
}else {
last.next = node;
node.prev = last;
last = last.next;
}
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
try {
checkIndex(index);
}catch (IndexNotLegalException e) {
e.printStackTrace();
}
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
//1. 找到index位置
ListNode cur = findIndex(index);
ListNode node = new ListNode(data);
//2、开始绑定节点
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
private ListNode findIndex(int index) {
ListNode cur = head;
while (index != 0) {
cur = cur.next;
index--;
}
return cur;
}
private void checkIndex(int index) {
if(index < 0 || index > size()) {
throw new IndexNotLegalException("双向链表插入index位置不合法: "+index);
}
}
//删除第一次出现关键字为key的节点
public void remove(int key){
ListNode cur = head;
while (cur != null) {
if(cur.val == key) {
//开始删除 处理头节点
if(cur == head) {
head = head.next;
if(head != null) {
head.prev = null;
}else {
//head == null 证明只有1个节点
last = null;
}
}else {
cur.prev.next = cur.next;
if(cur.next == null) {
//处理尾巴节点
last = last.prev;
}else {
cur.next.prev = cur.prev;
}
}
return;//删完一个就走
}
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;
if(head != null) {
head.prev = null;
}else {
//head == null 证明只有1个节点
last = null;
}
}else {
cur.prev.next = cur.next;
if(cur.next == null) {
//处理尾巴节点
last = last.prev;
}else {
cur.next.prev = cur.prev;
}
}
//return;//删完一个就走
}
cur = cur.next;
}
}
//清空链表
public void clear(){
ListNode cur = head;
while (cur != null) {
ListNode curN = cur.next;
//cur.val = null;
cur.prev = null;
cur.next = null;
cur = curN;
}
head = last = null;
}
}
3 LinkedList
3.1 什么是LinkedList
在集合框架中,LinkedList也实现了List接口:
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
3.2 LinkedList的使用
public static void main(String[] args) {
//无参构造
List<Integer> list1 = new LinkedList<>();
//有参构造
List<String> list2 = new ArrayList<>();
list2.add("JavaSE");
list2.add("JavaWeb");
list2.add("JavaEE");
// 使用ArrayList构造LinkedList
List<String> list3 = new LinkedList<>(list2);
}
4 ArrayList和LinkedList的区别