前言
- 这篇文章主要谈到了链表的概念及基本用法,适合初学者。
- 最近在教链表,我想着算法还没开始学,得自己补一下。
一、链表基础
1.链表的基本概念
- 链表是一种常见的数据结构,它由一系列节点(Node)组成。每个节点包含两部分:数据部分和指针部分。数据部分用于存储实际的数据,比如一个整数、一个字符或者一个结构体等。指针部分则用于存储下一个节点的地址,通过这种方式将多个节点连接起来,形成一个链状结构。
- 与数组不同,数组在内存中是连续存储的,而链表的节点在内存中的位置可以是不连续的。这使得链表在插入和删除操作时具有一定的优势。
2.链表节点的定义
- 假设我们要创建一个存储整数的简单链表,节点的结构体定义如下:
struct Node {
int data;
struct Node *next;
};
- 这里
struct Node
是节点的类型,data
成员用于存储整数,next
是一个指针,它指向struct Node
类型的另一个节点。
3.链表的基本操作
a.创建链表:
- 首先要创建一个头节点(head),头节点通常用于标记链表的开始位置。例如:
struct Node *head = NULL;
- 然后可以逐个创建节点并插入到链表中。比如创建一个新节点并插入到链表头部:
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = 1;
newNode->next = head;
head = newNode;
- 这里
malloc
函数用于在内存中分配足够的空间来存储一个struct Node
类型的节点。newNode->data = 1
是将数据部分赋值为 1,newNode->next = head
是将新节点的下一个节点指针指向当前的头节点,然后head = newNode
将新节点赋值为头节点。
b. 遍历链表:
- 遍历链表是为了访问链表中的每个节点。可以使用一个指针来遍历链表。例如:
struct Node *current = head;
while (current!= NULL) {
printf("%d ", current->data);
current = current->next;
}
- 这里首先将
current
指针指向头节点,然后在while
循环中,只要current
指针不为NULL
(即还没有到达链表的末尾),就打印当前节点的数据部分,并将current
指针移动到下一个节点(通过current = current->next
)。
c.插入节点: - 在链表中插入节点有多种情况,如在头部插入(前面已经示例)、在中间插入和在尾部插入。在中间插入节点时,假设要在值为
x
的节点之后插入一个新节点,代码如下:
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = newData;
struct Node *current = head;
while (current!= NULL && current->data!= x) {
current = current->next;
}
if (current!= NULL) {
newNode->next = current->next;
current->next = newNode;
}
- 首先找到值为
x
的节点,然后将新节点插入到它后面。先将新节点的next
指针指向当前节点的下一个节点,再将当前节点的next
指针指向新节点。
d.删除节点:
- 同样有多种情况,以删除值为
y
的节点为例。
struct Node *current = head;
struct Node *prev = NULL;
while (current!= NULL && current->data!= y) {
prev = current;
current = current->next;
}
if (current!= NULL) {
if (prev == NULL) {
head = current->next;
} else {
prev->next = current->next;
}
free(current);
}
- 这里在遍历链表寻找值为
y
的节点过程中,使用prev
指针记录当前节点的前一个节点。当找到要删除的节点后,如果prev
为NULL
,说明要删除的是头节点,直接将head
指向当前节点的下一个节点;否则,将prev
节点的next
指针指向当前节点的下一个节点,最后使用free
函数释放被删除节点占用的内存。
4.链表的类型
- 单向链表:前面介绍的就是单向链表,它的节点只包含一个指向下一个节点的指针,只能沿着一个方向遍历。
- 双向链表:双向链表的节点包含两个指针,一个指向前一个节点,一个指向下一个节点。它的节点结构体定义可能如下:
struct DoublyNode {
int data;
struct DoublyNode *prev;
struct DoublyNode *next;
};
- 双向链表在某些操作上(如反向遍历)比单向链表更方便,但占用的空间相对多一些,因为每个节点多了一个指针。
- 循环链表:循环链表可以是单向循环链表或双向循环链表。在单向循环链表中,最后一个节点的next指针指向头节点,形成一个环。在双向循环链表中,头节点的prev指针指向最后一个节点,最后一个节点的next指针指向头节点,也形成一个环。这种链表结构在某些特定的应用场景(如循环缓冲区)中很有用。
二、例题讲解
1.题目(此处我们选择比较基础的题目)
//Q504.(语言: C)在一个有序(按非递减顺序)的链表中插入一个元素为x的结点,
//使插入后的链表仍然有序(链表数据域为整型数,N为6)。
//* *输入提示:"输入数组6个元素的值。\n"
//* *输入格式:"%d"
//* *输出提示:"此链表各个结点的数据域为:"
//* *输出格式:"%d " (注:所有数据输出结束后有一个回车)
//* *输入提示:"输入要插入的数据x:"
//* *输入格式:"%d"
//* *输出提示:"插入后链表各个结点的数据域为:"
//* *输出格式:"%d " (注:所有数据输出结束后有一个回车)
//程序运行示例如下:
//输入数组6个元素的值。↙
//12 23 34 45 56 67
//此链表各个结点的数据域为:12 23 34 45 56 67 ↙
//输入要插入的数据x : 36
//插入后链表各个结点的数据域为:12 23 34 36 45 56 67↙
2.题解
a.头文件:
#include<stdio.h>
#include<stdlib.h>
b.节点的定义:
typedef struct listnode {
int num;
struct listnode* next;
}lsnd;
lsnd
是struct listnode
的别名。
c.创建链表:
lsnd* createNode(int arr[],int ipt) {
lsnd* head = NULL;
lsnd* tail = NULL;
for (int n = 0; n < ipt; n++) {
lsnd* newNode = (lsnd*)malloc(sizeof(lsnd));
newNode->num = arr[n];
newNode->next = NULL;
if (head == NULL) {
head = newNode;
tail = newNode;
}
else {
tail->next = newNode;
tail = newNode;
}
}
return head;
}
- 此部分有个比较难理解的点(个人感觉):
tail->next = newNode;tail = newNode;
- 我们可以类比一下,比如你有三个钩子(节点),钩子的尾部系了一段绳子(.next),第一个钩子叫
tail
,第二个叫newNode
,第三个叫tailNext
。 - 本来
tail
的尾绳被tailNext
钩住,但是你现在改变了他的状态。 - 用
newNode
钩住tail
的尾绳(tail->next = newNode;
),再用tailNext
钩住newNode
(tail = newNode;
),这样newNode
就插在了tail
和tailNext
的中间。
- 我们可以类比一下,比如你有三个钩子(节点),钩子的尾部系了一段绳子(.next),第一个钩子叫
d.插入节点:
lsnd* insert(lsnd* head, int ipt) {
lsnd* iptNode = (lsnd*)malloc(sizeof(lsnd));
iptNode->num = ipt;
if (head == NULL) {
return iptNode;
}
if (ipt < head->num) {
iptNode->next = head;
return iptNode;
}
lsnd* tmp = head;
while (tmp != NULL && tmp->next != NULL && tmp->next->num < ipt) {
tmp = tmp->next;
}
iptNode->next = tmp->next;
tmp->next = iptNode;
return head;
}
e.打印链表:
void printList(lsnd* head) {
lsnd* tmp = head;
while (tmp != NULL) {
printf("%d ", tmp->num);
tmp = tmp->next;
}
printf("\n");
}
f.主函数:
int main() {
int arr[6] = { 0 };
printf("输入数组6个元素的值。\n");
for (int n = 0; n < 6; n++) {
scanf("%d", &arr[n]);
}
lsnd* head = createNode(arr, 6);
printList(head);
printf("输入要插入的数据x:");
int ipt = 0;
scanf("%d", &ipt);
head = insert(head, ipt);
printf("插入后链表各个结点的数据域为:");
printList(head);
return 0;
}
结语
- 这篇文章主要是留下日后复习。
- 可能存在部分错误欢迎指正。
- 讲解比较清楚,例题比较简单,希望这篇文章能够帮到你。