链表的概念和单向链表的实现
文章目录
- 一:链表的概念及结构
- 二:单向链表
- (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);//实例化一个节点对象nodenode.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;
}