链表linked list
链表的基本概念
- 链表由一系列节点组成,每个节点包含两部分,一部分存储数据,另一部分存储指向下一个节点的指针。每个节点都可以在内存中分配一块独立的空间,节点之间通过指针连接起来,形成一个链表。
- 优点:可以在任意位置插入或删除节点,适合对数据进行动态操作。
- 缺点:无法像数组那样随机访问,需要遍历整个链表才能找到指定位置的节点。
- 单向链表:每个节点只存储指向下一个节点的指针。
- 双向链表:在单向链表的基础上每个节点还存储指向前一个节点的指针。
- 头节点:指链表中第一个节点。它不存储任何数据,只是作为链表的起点,用于指向链表中的第一个实际节点。
链表的具体代码示例
1.简单链表Java 代码示例:
// 定义一个链表节点类 ListNode
public class ListNode {
int val; // 节点中存储的值
ListNode next; // 指向下一个节点的指针
// 构造函数,用于创建一个新的节点
ListNode(int x) {
val = x; // 初始化节点的值
}
}
创建链表:
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
较为全面的链表Java 代码示例:
class Node {
int data; // 数据域
Node next; // 指针域,指向下一个节点
public Node(int data) {
this.data = data;
this.next = null;
}
}
class LinkedList {
Node head; // 头节点
public LinkedList() {
this.head = null;
}
//添加节点的方法,在文章后面会介绍
public void add(int data) {
Node newNode = new Node(data); // 创建新节点
if (head == null) { // 如果链表为空,则将新节点设置为头节点
head = newNode;
} else { // 如果链表不为空,则将新节点插入到链表的末尾
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
}
//删除节点的方法,在文章后面会介绍
public void remove(int data) {
if (head == null) { // 如果链表为空,则直接返回
return;
}
if (head.data == data) { // 如果头节点就是要删除的节点,则将头节点指向下一个节点
head = head.next;
} else { // 如果头节点不是要删除的节点,则在链表中查找要删除的节点并删除
Node current = head;
while (current.next != null && current.next.data != data) {
current = current.next;
}
if (current.next != null) {
current.next = current.next.next;
}
}
}
}
创建链表:
LinkedList list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
链表的遍历
- 链表的遍历可以通过一个指针依次访问链表中的每个节点来实现。具体来说,可以从链表的头节点开始,通过一个指针不断向后移动,直到访问到链表的尾节点为止。在访问每个节点时,我们可以对其进行一些操作,比如输出节点的值、修改节点的值等。
下面是一个简单的 Java 代码示例,通过一个 while 循环遍历链表并输出每个节点的值:
// 定义一个指针 cur,初始时指向链表的头节点
Node cur = head;
// 遍历链表,直到 cur 指向 null,即到达链表的尾部
while (cur != null) {
// 对当前节点进行操作,这里是输出当前节点的值
System.out.println(cur.val);
// 将指针 cur 指向下一个节点
cur = cur.next;
}
- 其中,head 表示链表的头节点,cur 是一个指针,初始时指向头节点。在 while 循环中,我们通过判断 cur 是否为 null
来判断是否到达链表的尾部,如果没有到达尾部,则输出当前节点的值,并将指针指向下一个节点。
链表的释放
在 Java 中,链表的释放由 Java 虚拟机自动管理,Java 中的垃圾回收机制会在对象不再被引用时自动回收其所占用的内存空间。这意味着,在 Java 中不需要手动释放链表所占用的内存空间,Java 虚拟机会根据需要自动回收内存空间。
查找链表节点
- 链表节点查找需要遍历整个链表,从链表头开始,依次访问每个节点,直到找到目标节点或者遍历到链表尾部。
下面是一个简单的 Java 代码示例,用于在链表中查找目标节点:
/**
1. 在链表中查找目标节点
2.
3. @param head 链表头节点
4. @param target 目标节点的值
5. @return 找到的目标节点,如果未找到则返回 null
*/
public Node search(Node head, int target) {
// 从链表头开始遍历
Node current = head;
while (current != null) { // 如果当前节点不为 null,则继续遍历
if (current.value == target) { // 如果当前节点的值等于目标节点的值,则返回该节点
return current;
}
// 否则继续访问下一个节点
current = current.next;
}
// 如果遍历完整个链表仍未找到目标节点,则返回 null
return null;
}
添加节点
具体实现如下:
- 创建一个新节点,并将需要添加的值存储在该节点中。
- 首先判断链表是否为空,如果为空,则将新节点设置为头节点。
- 如果链表不为空,则需要遍历到链表的末尾,找到最后一个节点。
- 将新节点插入到链表的末尾,即将最后一个节点的 next 指针指向新节点。
下面是一个简单的 Java 代码示例,实现了单链表的添加节点功能:
public class LinkedList {
private Node head;
public void add(int data) {//data为要添加的数据
Node newNode = new Node(data); // 创建新节点
if (head == null) { // 如果链表为空,则将新节点设置为头节点
head = newNode;
} else { // 如果链表不为空,则将新节点插入到链表的末尾
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
}
}
删除节点
要删除链表中的一个节点,需要先找到该节点,并将其前一个节点的指针指向该节点的下一个节点即可。具体步骤如下:
- 遍历链表,找到要删除的节点。
- 将该节点的前一个节点的 next 指针指向该节点的下一个节点。
- 如果要删除的节点是链表头节点,则需要将头节点指向下一个节点。
- 释放被删除的节点的内存空间。
下面为java代码示例
public void remove(int data) {//data为要删除的数据
if (head == null) { // 如果链表为空,则直接返回
return;
}
if (head.data == data) { // 如果头节点就是要删除的节点,则将头节点指向下一个节点
head = head.next;
} else { // 如果头节点不是要删除的节点,则在链表中查找要删除的节点并删除
Node current = head; // 从头节点开始遍历链表
while (current.next != null && current.next.data != data) { // 找到要删除的节点
current = current.next; // 移动指针
}
if (current.next != null) { // 如果找到了要删除的节点
current.next = current.next.next; // 删除节点
}
}
}
插入节点
链表中插入一个节点,需要先找到插入位置的前一个节点,然后将新节点插入到该节点后面。具体步骤如下:
- 创建一个新的节点,将需要插入的值存储在该节点中。
- 找到插入位置的前一个节点。可以从链表的头节点开始遍历,一直遍历到目标位置的前一个节点。
- 将新节点插入到该节点后面。即将新节点的 next 指针指向该节点的下一个节点,同时将该节点的 next 指针指向新节点。
下面是一个简单的 Java 代码示例:
public void insertNode(ListNode head, int val, int index) {
ListNode newNode = new ListNode(val);
ListNode curr = head;
int count = 0;
while (count < index - 1) {
curr = curr.next;
count++;
}
newNode.next = curr.next;
curr.next = newNode;
}
其中,ListNode 是链表节点的定义,包含一个 val 值和一个 next 指针。head 是链表的头节点,val 是需要插入的值,index 是插入位置的索引。
对链表进行排序
- 链表的排序有多种方法,其中最常用的是快速排序和归并排序。
- 快速排序的基本思想是选择一个数作为基准,然后将链表分成两个部分,一个部分包含所有比基准小的元素,另一个部分包含所有比基准大的元素,然后递归地对这两个部分进行排序。
- 归并排序的基本思想是将链表分成两个部分,对每个部分分别进行排序,然后将两个有序的部分合并成一个有序的链表。
下面是一个简单的 Java 实现示例,用快速排序对链表进行排序:
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) { // 如果链表为空或只有一个节点,直接返回
return head;
}
ListNode pivot = head; // 选择头节点作为 pivot
ListNode cur = head.next; // 从第二个节点开始遍历
ListNode lessHead = new ListNode(0); // 新建一个虚拟头节点,用于保存小于 pivot 的节点
ListNode lessTail = lessHead; // lessTail 指向 lessHead,用于添加节点
ListNode greaterHead = new ListNode(0); // 新建一个虚拟头节点,用于保存大于 pivot 的节点
ListNode greaterTail = greaterHead; // greaterTail 指向 greaterHead,用于添加节点
while (cur != null) { // 遍历整个链表
if (cur.val < pivot.val) { // 如果当前节点的值小于 pivot 的值
lessTail.next = cur; // 将当前节点添加到 lessTail 的后面
lessTail = lessTail.next; // lessTail 向后移动一位
} else { // 否则
greaterTail.next = cur; // 将当前节点添加到 greaterTail 的后面
greaterTail = greaterTail.next; // greaterTail 向后移动一位
}
cur = cur.next; // cur 向后移动一位
}
lessTail.next = null; // 将 lessTail 的下一个节点置为 null,用于断开 lessHead 和 greaterHead 的连接
greaterTail.next = null; // 将 greaterTail 的下一个节点置为 null,用于断开 greaterHead 和后面的节点的连接
ListNode lessSorted = sortList(lessHead.next); // 对小于 pivot 的链表部分进行快速排序
ListNode greaterSorted = sortList(greaterHead.next); // 对大于 pivot 的链表部分进行快速排序
pivot.next = greaterSorted; // 将 pivot 的下一个节点指向大于 pivot 的链表部分
if (lessSorted == null) { // 如果小于 pivot 的链表部分为空
return pivot; // 直接返回 pivot,即为排序后的链表
}
ListNode tail = lessSorted; // 将 tail 指向 lessSorted,用于寻找 lessSorted 的末尾节点
while (tail.next != null) { // 找到 lessSorted 的末尾节点
tail = tail.next;
}
tail.next = pivot; // 将 lessSorted 的末尾节点指向 pivot
return lessSorted; // 返回 lessSorted,即为排序后的链表
}
下面是一个简单的 Java 实现示例,用归并排序对链表进行排序:
public ListNode sortList(ListNode head) {
// 如果链表为空或只有一个节点,直接返回
if (head == null || head.next == null) {
return head;
}
// 使用快慢指针找到链表的中间节点
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode mid = slow.next; // 中间节点
slow.next = null; // 将链表断成两个部分
ListNode left = sortList(head); // 对左半部分排序
ListNode right = sortList(mid); // 对右半部分排序
return merge(left, right); // 合并排序后的左右两部分链表
}
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0); // 新建一个虚拟头节点
ListNode cur = dummy; // cur 指向当前节点
while (l1 != null && l2 != null) {
if (l1.val < l2.val) { // 如果 l1 的值小于 l2 的值
cur.next = l1; // 将 cur 的下一个节点指向 l1
l1 = l1.next; // l1 向后移动一位
} else {
cur.next = l2; // 否则将 cur 的下一个节点指向 l2
l2 = l2.next; // l2 向后移动一位
}
cur = cur.next; // cur 向后移动一位
}
if (l1 != null) { // 如果 l1 不为空
cur.next = l1; // 将 cur 的下一个节点指向 l1
}
if (l2 != null) { // 如果 l2 不为空
cur.next = l2; // 将 cur 的下一个节点指向 l2
}
return dummy.next; // 返回虚拟头节点的下一个节点,即合并后的链表
}