前言:
上篇我们讲了ArraysList(顺序表)并且知道顺序表的接口大概是怎么实现的,但也知道ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后移,这样来,时间复杂度为O(n),因此ArrayList不适合做任何位置插入和删除比较多的场景。
接下里,我们要学到LinkedList(链表)--这个可以完美的解决这个问题,使插入和删除的时间复杂度达到O(1).
目录
1.链表的概念和结构
概念:
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链表次序实现的。
结构:
注意:
1,从图可以看出,链式结构在逻辑上是连续的,但在物理(空间)上不一定连续的。
2,现实中的节点一般都是在堆上申请出来的。
3,从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。
实际中链表的结构非常多种,组合起来就有8种链表结构。
分为 带头,不带头 双向,单向 循环 和非循环。
2.链表的实现
要先知道链表的接口是怎么实现的,我们应该知道链表是怎么构建的。
2.1:链表的构建
class ListNode{//这是内部节点
public int val;
public ListNode next;//需要指向下一个节点,开辟一个指针。
public ListNode(int val) {//这是构造方法
this.val = val;
}
}
public ListNode head;//没有初始化,默认是null.
}
2.2:插
2.2.1:头插
public void addFirst(int data){
//创建一个数据域为data的节点
ListNode node=new ListNode(data);
node.next=head;
head=node;
}
//在main函数中
public static void main(String[] args) {
LinkedList list=new LinkedList();//创建一个list对象
list.addFirst(1);
list.addFirst(2);
list.addFirst(3);
list.addFirst(4);
}
//结果
4 3 2 1
2.2.2:尾插
public void addLast(int data){
ListNode node=new ListNode(data);
ListNode cur=head;
if(head==null){
head=node;
}
//找尾巴节点
while(cur.next!=null){
cur=cur.next;
}
//这时候cur已经是尾巴节点
cur.next=node;
}
2.2.3:任意位置插入----第一个数据节点为0下标
public void addIndex(int index,int data){
ListNode node=new ListNode(data);
//判断插入的位置是否合法
if(index<0||index>size()){
throw new IndexWrongException("插入位置错误");
}
//这个要分情况而讨论
//1.头插
if(head==null){
addFirst(data);
return;
}
//2.尾插
if(index==size()){
addLast(data);
return;
}
//3.中间插入
ListNode cur=head;
while(index>0){
cur=cur.next;
index--;
}
node.next=cur.next;
cur.next=node;
}
//就单链表的长度
public int size(){
ListNode cur=head;
int size=0;
while (cur!=null){
size++;
cur=cur.next;
}
return size;
}
2.3:删
2.3.1:删除第一次出现的关键字
//判断该链表是否为空
if(isEmpty()){
System.out.println("该单链表为空,无法删除");
}
//查找单链表中是否存在这个关键字
ListNode flag=contains(key);
if(flag==null){
System.out.println("该单链表无此数");
return;
}
flag.next=flag.next.next;
}
//判断单链表是否为空
//true 空
public boolean isEmpty(){
return size()==0;
}
public ListNode contains(int key){
ListNode cur=head;
while (cur.next!=null){
if(cur.next.val==key){
return cur;//包含,返回关键字的前一个节点。
}
cur=cur.next;
}
return null;
}
接下来,我们来了解一个比较难的删除。
2.3.2:删除所有关键字的节点
public void removeAll(int key){
//检查该单链表是否为空
if(isEmpty()){
System.out.println("此链表为空,无法删除");
}
ListNode cur=head.next;
ListNode prev=head;
while (cur!=null) {
if (cur.val == key) {
prev.next = cur.next;
cur=cur.next;
}else{
prev=cur;
cur=cur.next;
}
}
if(head.val==key){//这是判断头节点
head=head.next;
}
}
3.链式面试题
3.1反转链表
https://leetcode.cn/problems/reverse-linked-list/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode newHead= null;
ListNode ret=head;
while(ret!=null){
ListNode cur=ret.next;
ret.next=newHead;
newHead=ret;
ret=cur;
}
return newHead;
}
}
3.2:将两个有序链表合成一个新的链表
https://leetcode.cn/problems/merge-two-sorted-lists/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//创建一个新节点
ListNode newhead=new ListNode(-1);
ListNode tmp = newhead;
//遍历两个链表
while(list1!=null&&list2!=null){
if(list1.val<list2.val){
tmp.next=list1;
list1=list1.next;
}else{
tmp.next=list2;
list2=list2.next;
}
tmp=tmp.next;
}
if(list1!=null){//链表1不为空
tmp.next=list1;
}
if(list2!=null){
tmp.next=list2;//链表2不为空
}
return newhead.next;
}
}
链表题还有很多,我就先举例这两个基本的编程题了,接下来,我们了解一下ArrayList和LinkedList的区别
4:ArrayList和LinkedList的区别
不同点 | ArrayList | LinkedList |
存储空间 | 空间上连续 | 空间上不一定连续,逻辑上连续 |
随机访问 | 0(1)可以通过下标访问 | 0(n)遍历链表 |
头插 | 需要搬移元素,效率低于O(n) | 只需要修改引用的指向,O(1) |
插入 | 空间不够需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和频繁删除 |
总结:
以上就是我总结的链表的知识点,若有遗漏的,希望各位老铁,留言补充,若感觉不错,希望各位铁子可以一键三连。感谢感谢!