实用指南:链表的概念和单向链表的实现

文章目录一:链表的概念及结构二:单向链表(1)基础结构定义(2)手动创建链表(3)打印链表(4)得到单链表的⻓度(5)查找是否包含关键字key是否在单链表当中(6)头插法(7)尾插法(8)下标不合法异常(9)任意位置前面插⼊,第一个数据节点为0号下标(10)删除第⼀次出现关键字为key的节点(11)删除所有值为key的结点(12)清空链表中所有元素:

一:链表的概念及结构1.1链表是⼀种物理存储结构上⾮连续存储结构,数据元素的逻辑顺序是通过链表中的引⽤链接次序实现 的。 (1))物理非连续:链表的节点在内存中不一定连续,每个节点包含数据域(val)和引用域(next)(指向其他节点)。 (2)逻辑连续:通过引用域,节点之间形成链式关系,保证了数据在逻辑上的连续。 (3))节点来源:现实中,链表的节点一般从堆内存中申请,两次申请的空间可能连续也可能不连续。 1.2 链表的常见结构 链表的结构多样,通过以下三个维度组合,可形成 8 种不同的链表结构: (1))单向 / 双向:单向链表节点只有一个引用域,指向后继节点;双向链表节点有两个引用域,分别指向前驱和后继节点。 (2))带头 / 不带头:带头链表有一个头节点(不存储实际数据),用于简化操作;不带头链表直接从存储数据的节点开始。 (3))循环 / 非循环:循环链表的尾节点引用指向头节点(或头节点相关节点),形成闭环;非循环链表的尾节点引用为 null。示例: (1)单向不带头非循环 (2)单向带头非循环(3)单向不带头循环 (4)单向带头循环 在实际应用中,我们重点掌握两种核心结构:

无头单向非循环链表:结构简单,一般不单独用于存储数据,常作为其他数据结构(如哈希桶、图的邻接表)的子结构,也是笔试面试中的高频考点。无头双向非循环链表:Java 中 LinkedList 的底层实现就是这种结构,能兼顾插入、删除和查询操作的性能。

二:单向链表

我们自己实现一个⽆头单向⾮循环链表

(1)基础结构定义

public class MySingleList {

// 内部静态类:链表节点

static class ListNode{

public int val; // 数据域

public ListNode next; // 引用域(下一个节点引用)

// 初始化数据域

public ListNode(int val) {

this.val = val;

}

}

ListNode head; // 链表的头引用,是访问链表的入口

}

(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;

}

(3)打印链表

public void display() {

//不要让head动

ListNode cur = this.head;

while (cur != null) {

System.out.print(cur.val + " ");

cur = cur.next;

}

System.out.println();

}

测试:

public class Test {

public static void main(String[] args) {

MySingleList mySingleList = new MySingleList();

mySingleList.createList();

mySingleList.display();

}

}

在这里我们会思考如果我们把循环条件改为cur.next !=null会怎么样

根据上面的链表图,我们可以推出不会打印最后一个val;

我们来修改一下代码来看结果确实少打印了56,说明我们的推断是正确的 所以我们可以总结出一个结论:如果要停在最后一个节点,那么cur.next !=null,如果要遍历完所有节点,那么cur !=null

(4)得到单链表的⻓度

public int size(){

int count = 0;

ListNode cur = this.head;

while(cur !=null){

count++;

cur = cur.next;

}

return count;

}

(5)查找是否包含关键字key是否在单链表当中

public boolean contains(int key){

ListNode cur = this.head;

while(cur != null){

if(cur.val == key){

return true;

}

cur = cur.next;

}

return false;

}

(6)头插法1实例化一个节点对象node 2.修改指向

//时间复杂度为O(1)

public void addFirst(int data){

ListNode node = new ListNode(data);//实例化一个节点对象node

node.next = this.head;//修改指向

this.head = node;//修改指向

}

注意:修改指向这两步不能调换,因为调换之后相当于自己指向自己 结论:所有插入的时候先绑定后面

这时我们会有一个疑问,如果我们在一个空链表里使用头插法要不要特殊处理?

答案是不用!

因为你一开始节点的引用域本来就是null,你的head也是null,第一步修改指向没什么变化,第二步修改指向,head就存node的地址了,也就是正常插入了!

(7)尾插法1看一下链表是不是空链表(一个节点都没有)head==null 2.找尾巴

//时间复杂度O(N)

public void addLast(int data){

ListNode node = new ListNode(data);

if(this.head == null){

this.head = node;

return;

}

//找尾巴

ListNode cur = this.head;

while(cur.next != null){

cur = cur.next;

}

cur.next = node;

}

(8)下标不合法异常

public class illegalIndexException extends RuntimeException {

public illegalIndexException() {

}

public illegalIndexException(String message) {

super(message);

}

}

(9)任意位置前面插⼊,第一个数据节点为0号下标假如插入2位置,那就是插入2之前,所以要找到index位置的前一个节点

//任意位置前面插⼊,第一个数据节点为0号下标

public void addIndex(int index,int data){

int len = size();

if(index < 0 || index > len){

throw new illegalIndexException("下标不合法");

}

//头插法

if(index == 0){

addFirst(data);

return;

}

//尾插法

if(index == len){

addLast(data);

return;

}

//中间位置

ListNode cur = searchIndex(index);

if(cur == null){

return;

}

ListNode node = new ListNode(data);

node.next = cur.next;

cur.next = node;

}

/**

* 找到index位置的前一个节点

* @param index

* @return

*/

private ListNode searchIndex(int index){

int len = size();

if(index < 0 || index >len){

return null;

}

ListNode cur = this.head;

int count = 0;

while(count !=index-1){

cur = cur.next;

count++;

}

return cur;

}

(10)删除第⼀次出现关键字为key的节点

/**

* 查找关键字key的前一个节点,找到返回地址

* 找不到返回null

* @param key

* @return

*/

private ListNode findNode(int key){

if(this.head == null){

return null;

}

ListNode prev = this.head;

while(prev.next != null){

if(prev.next.val == key){

return prev;

}

prev = prev.next;

}

return null;

}

//删除第⼀次出现关键字为key的节点

public void remove(int key){

if(this.head == null){

return;

}

if(this.head.val == key){

this.head = this.head.next;

return;

}

//走到这里 第一个节点如果是要删除的节点 此时已经删除完毕

ListNode prev = findNode(key);

if(prev == null){

return;

}

ListNode del = prev.next;

prev.next = del.next;

}

(11)删除所有值为key的结点

public ListNode removeAllKey(int key) {

if(this.head == null) {

return null;

}

ListNode prev = this.head;

ListNode cur = this.head.next;

while(cur != null) {

if(cur.val == key) {

prev.next = cur.next;

cur = cur.next;

} else {

prev = cur;

cur = cur.next;

}

}

//最后处理头

if(this.head.val == key) {

this.head = this.head.next;

}

return this.head;

}

(12)清空链表中所有元素:

public void clear() {

while (this.head != null) {

ListNode curNext = head.next;

head.next = null;

head.prev = null;

head = curNext;

}

last = null;

}


TOP