目录
[1 链表的优势:](#1 链表的优势:)
[2 链表的组成:](#2 链表的组成:)
[5.3 查找是否存在某个值](#5.3 查找是否存在某个值)
[5.4. 查找链表中最后一个节点](#5.4. 查找链表中最后一个节点)
[5.5 查找链表中倒数第 k 个节点](#5.5 查找链表中倒数第 k 个节点)
[6.1 删除头节点](#6.1 删除头节点)
1 链表的优势:
- 动态大小:链表的大小可以根据需要动态调整,而数组在声明时大小固定。
- 插入和删除操作高效:在链表中,插入和删除操作不需要移动元素,只需修改指针即可,效率更高。
- 内存利用率高:链表可以根据需要分配内存,不会浪费空间。
2 链表的组成:
-
节点结构:链表由多个节点组成,每个节点通常包含两部分:
- 数据域:存储实际的数据。
- 指针域:指向下一个节点的指针(在双向链表中,还会有指向前一个节点的指针),第一个节点的指针域保存第二个节点的地址。
-
头指针 :链表通常有一个头指针,指向链表的第一个节点。如果链表为空,头指针为NULL。
-
尾指针(可选):在某些实现中,链表还可能包含一个指向最后一个节点的指针,以便于在尾部插入节点时提高效率
3.一般使用结构体的形式来实现链表:
objectivec
struct Node {
int data; // 数据域
struct Node* next; // 指针域,指向下一个节点
};
在正式手搓单向链表前,先复习一下二级指针:
指针 :一个变量,它存储另一个变量的地址。例如,Node* head
是一个指向 Node
类型的指针。
二级指针 :一个指向指针的指针。比如,Node** head
表示这是一个指向 Node*
的指针,通常用于传递指针的地址,以便在函数内可以修改这个指针的值。在链表的实现中,使用二级指针来传递指向头指针的地址,这样就可以在函数内部修改头指针的值。
4.单向链表实现(创建,遍历,释放):
下面是一个代码的示例:
objectivec
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构体
struct Node {
int data; // 节点数据
struct Node* next; // 指向下一个节点的指针
};
// 创建链表节点并添加到链表末尾
void createNode(struct Node** head, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // 分配新节点内存
newNode->data = value; // 设置节点的数据
newNode->next = NULL; // 新节点的下一个指针初始化为 NULL
// 检查链表是否为空
if (*head == NULL) {
*head = newNode; // 如果链表为空,则将新节点设置为头节点
} else {
struct Node* temp = *head; // 使用 *head 访问当前头节点
while (temp->next != NULL) { // 遍历链表到最后一个节点
temp = temp->next;
}
temp->next = newNode; // 将新节点链接到链表末尾
}
}
// 打印链表中的所有节点
void printList(struct Node* head) {
struct Node* temp = head; // struct Node* temp = head; 这一行定义了一个
//新的指针变量 temp,并将 head 的值(即头节点的地址)赋给它。
while (temp != NULL) {
printf("%d -> ", temp->data); // 打印当前节点的数据
temp = temp->next; // 移动到下一个节点
}
printf("NULL\n"); // 链表结束标志
}
// 释放链表内存
void freeList(struct Node** head) {
struct Node* temp;
while (*head != NULL) {
temp = *head; // 备份当前头节点
*head = (*head)->next; // 移动头指针到下一个节点
free(temp); // 释放备份的节点内存
}
}
int main() {
struct Node* head = NULL; // 初始化链表头节点为空
int n, value;
// 输入节点的个数
printf("请输入链表节点的个数: ");
scanf("%d", &n);
// 使用 for 循环创建链表
for (int i = 0; i < n; i++) {
printf("请输入第 %d 个节点的值: ", i + 1);
scanf("%d", &value);
createNode(&head, value); // 通过二级指针传入头指针
}
// 打印链表内容
printf("链表内容: ");
printList(head);
// 释放链表内存
freeList(&head);
return 0;
}
4.1代码关键点备注:
在void createNode中:
head
是一个指向链表 头节点 的 指针的地址。(&head)- 通过传入二级指针,
createNode
函数可以在链表为空时直接修改头指针的值,让新节点成为链表的头节点(即*head = newNode;
的情况)。
在void printList(struct Node* head)中:
head
是指向链表 头节点 的 指针,用于从链表的第一个节点开始进行遍历。struct Node* temp = head;
这一行定义了一个新的指针变量temp
,并将head
的值(即头节点的地址)赋给它。(head)printList
函数并不修改链表的结构或内容,只是逐一访问每个节点的数据并打印。因此,只需传入一个指向头节点的普通指针,而不需要使用二级指针(不需要修改头指针的值)。
5.查找节点:
5.1.按值查找节点
- 在链表中查找第一个数据与指定值匹配的节点,并返回该节点的指针。
- 如果找不到该值,通常返回
NULL
。
objectivec
struct Node* searchByValue(struct Node* head, int value) {
struct Node* temp = head;
while (temp != NULL) {
if (temp->data == value) {
return temp; // 找到匹配值的节点,返回指针
}
temp = temp->next; // 移动到下一个节点
}
return NULL; // 没有找到
}
5.2.按位置查找节点
- 按照链表中的位置(索引)查找节点。位置通常从
0
开始,即第0
个节点是头节点。 - 如果位置超出链表长度,可以返回
NULL
。
objectivec
struct Node* searchByPosition(struct Node* head, int position) {
struct Node* temp = head;
int currentIndex = 0;
while (temp != NULL) {
if (currentIndex == position) {
return temp; // 找到指定位置的节点
}
temp = temp->next;
currentIndex++;
}
return NULL; // 位置超出链表长度
}
5.3 查找是否存在某个值
- 在链表中查找是否存在某个值,只返回
1
(找到)或0
(未找到),而不返回节点。
objectivec
int containsValue(struct Node* head, int value) {
struct Node* temp = head;
while (temp != NULL) {
if (temp->data == value) {
return 1; // 找到该值,返回 1
}
temp = temp->next;
}
return 0; // 没找到该值,返回 0
}
5.4. 查找链表中最后一个节点
- 可以用来获取链表的尾节点。
objectivec
struct Node* getLastNode(struct Node* head) {
if (head == NULL) return NULL; // 链表为空
struct Node* temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
return temp; // 返回最后一个节点
}
5.5 查找链表中倒数第 k
个节点
- 经典问题,常用 双指针 方法实现。用两个指针
fast
和slow
,它们都从链表的头节点head
出发,但fast
会比slow
先走k
步。然后,让两个指针同时移动,直到fast
到达链表的末尾。此时,slow
就刚好停在倒数第k
个节点的位置。
objectivec
struct Node* getKthFromEnd(struct Node* head, int k) {
struct Node *fast = head, *slow = head;
// 让 fast 指针先走 k 步
for (int i = 0; i < k; i++) {
if (fast == NULL) return NULL; // 链表长度小于 k
fast = fast->next;
}
// 同时移动 fast 和 slow 指针,直到 fast 到达链表末尾
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
return slow; // slow 指针就是倒数第 k 个节点
}
6.删除节点
6.1 删除头节点
objectivec
void deleteHead(struct Node** head) {
if (*head == NULL) return; // 链表为空,无法删除
struct Node* temp = *head; // 备份头节点
*head = (*head)->next; // 更新头节点为下一个节点
free(temp); // 释放备份的头节点内存
}
6.2删除尾节点
删除链表的最后一个节点。需要遍历链表,找到倒数第二个节点并将其 next
指针设为 NULL
。
objectivec
void deleteTail(struct Node** head) {
if (*head == NULL) return; // 链表为空,无法删除
if ((*head)->next == NULL) { // 如果只有一个节点
free(*head);
*head = NULL; // 更新头指针为 NULL
return;
}
struct Node* temp = *head;
while (temp->next->next != NULL) { // 遍历到倒数第二个节点
temp = temp->next;
}
free(temp->next); // 释放最后一个节点
temp->next = NULL; // 将倒数第二个节点的 next 设为 NULL
}
1 -> 2 -> 3 -> NULL
在执行删除尾节点的操作时:
temp
会遍历到节点2
(倒数第二个节点),temp->next
会指向节点3
(最后一个节点)。- 当我们执行
free(temp->next);
,节点3
被释放。 - 然后执行
temp->next = NULL;
,使得节点2
变成链表的新尾节点,链表现在变为:
1 -> 2 -> NULL
6.3.删除指定位置的节点
根据给定的位置删除节点。
objectivec
void deleteAtPosition(struct Node** head, int position) {
if (*head == NULL) return; // 链表为空,无法删除
if (position == 0) { // 如果要删除的是头节点
deleteHead(head);
return;
}
struct Node* temp = *head;
for (int i = 0; i < position - 1; i++) {
if (temp == NULL || temp->next == NULL) {
printf("位置超出链表长度\n");
return; // 位置超出链表长度,无法删除
}
temp = temp->next; // 遍历到要删除节点的前一个节点
}
struct Node* nodeToDelete = temp->next; // 要删除的节点
if (nodeToDelete == NULL) return; // 如果没有下一个节点,无法删除
temp->next = nodeToDelete->next; // 将前一个节点的 next 指向要删除节点的下一个节点
free(nodeToDelete); // 释放要删除的节点内存
}
6.4.删除指定值的节点
删除链表中第一个值匹配指定值的节点
objectivec
void deleteByValue(struct Node** head, int value) {
if (*head == NULL) return; // 链表为空,无法删除
struct Node* temp = *head;
// 如果头节点的值就是要删除的值
if (temp->data == value) {
deleteHead(head);
return;
}
// 遍历链表查找匹配的节点
while (temp->next != NULL) {
if (temp->next->data == value) { // 找到匹配的节点
struct Node* nodeToDelete = temp->next; // 备份要删除的节点
temp->next = nodeToDelete->next; // 链接前一个节点与后一个节点
free(nodeToDelete); // 释放要删除的节点内存
return; // 删除完毕,退出函数
}
temp = temp->next; // 移动到下一个节点
}
}
6.5.释放整个链表
如果需要删除整个链表,释放所有节点内存。
objectivec
void freeList(struct Node** head) {
struct Node* temp;
while (*head != NULL) {
temp = *head; // 备份当前头节点
*head = (*head)->next; // 移动头指针到下一个节点
free(temp); // 释放备份的节点内存
}
}