1.链表的概念

链表是一种动态数据结构,由一系列节点(Node)通过指针连接而成,每个节点存储着数据和指向下一个节点的指针。链表最大的特点是节点的存储地址不连续,因此可以灵活地进行插入和删除操作,而不需要像数组那样移动其他元素。

链表的组成

节点(Node):链表中的基本单元,每个节点包含两部分:

1.数据域(Data):存储节点中的数据。

2.指针域(Next):存储指向下一个节点的指针,指向链表的下一个节点。头节点(Head Node):链表的第一个节点。通常我们会使用一个指针来指向链表的头节点,以便对链表进行操作。尾节点(Tail Node):链表的最后一个节点,尾节点的指针域通常指向 NULL,表示链表的结束。

链表的特点:

动态内存分配:链表在需要时动态分配节点,内存利用率高。插入和删除效率高:在链表中插入或删除一个节点,只需要改变相关节点的指针,不需要像数组一样移动元素。随机访问困难:由于链表的节点地址不连续,因此无法通过下标直接访问某个节点,需要从头节点开始依次遍历,访问效率较低。

链表的构成

链表由一个个节点构成,每个节点一般采用结构体的形式组织,例如:

typedef struct student

int num;

char name[20]; // 数据域

struct student *next;//指针域

}STU;

2.创建链表

#include

#include

// 定义链表节点结构

struct Node {

int data; // 数据域

struct Node* next; // 指针域,指向下一个节点

};

// 头节点指针,初始化为空

struct Node* head = NULL;

// 尾插法插入节点

void insertAtTail(int value) {

struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // 为新节点分配内存

newNode->data = value; // 设置新节点的数据

newNode->next = NULL; // 新节点的next指向NULL(因为它将成为尾节点)

if (head == NULL) { // 如果链表为空

head = newNode; // 新节点成为头节点

} else {

struct Node* temp = head;

while (temp->next != NULL) { // 遍历链表,找到尾节点

temp = temp->next;

}

temp->next = newNode; // 将尾节点的next指向新节点

}

}

// 打印链表

void printList() {

struct Node* temp = head;

while (temp != NULL) {

printf("%d -> ", temp->data);

temp = temp->next;

}

printf("NULL\n"); // 表示链表结束

}

// 释放链表内存

void freeList() {

struct Node* temp;

while (head != NULL) {

temp = head; // 临时保存当前节点

head = head->next; // 移动头指针到下一个节点

free(temp); // 释放当前节点

}

}

int main() {

// 插入节点

insertAtTail(1);

insertAtTail(2);

insertAtTail(3);

insertAtTail(4);

// 打印链表

printf("链表内容:");

printList();

// 释放链表内存

freeList();

return 0;

}

3.链表的遍历

4.链表的查找

查找的过程就是从链表头节点开始,逐个访问节点的数据,并与目标数据进行比较,直到找到相匹配的节点或到达链表的末尾。

由于链表的节点地址不连续,因此无法通过索引直接访问某个位置的节点,必须从头节点开始依次遍历。

// 查找指定值的节点

struct Node* find(int target) {

struct Node* temp = head;

while (temp != NULL) { // 从头节点开始遍历链表

if (temp->data == target) { // 如果找到目标数据

return temp; // 返回该节点

}

temp = temp->next; // 否则继续下一个节点

}

return NULL; // 未找到返回NULL

}

5.链表的删除

链表的删除操作是指从链表中移除一个节点,并正确地更新链表结构,以确保链表的连续性。删除节点时,我们需要找到目标节点,并将其前一个节点的指针指向目标节点的下一个节点。根据删除位置的不同,链表删除操作通常分为以下几种情况:

删除头节点: 这是指删除链表的第一个节点(即头节点),需要更新头指针使其指向链表的第二个节点。删除指定位置的节点: 删除链表中的某个特定节点时,需要找到该节点和它的前一个节点,然后修改前一个节点的指针,使其绕过目标节点直接指向下一个节点。删除尾节点: 删除链表的最后一个节点,需要找到倒数第二个节点,并将其 next 指针设为 NULL。

// 删除指定值的节点

void deleteNode(int value) {

struct Node* temp = head;

struct Node* prev = NULL;

// 情况1:删除头节点

if (temp != NULL && temp->data == value) {

head = temp->next; // 更新头节点

free(temp); // 释放原头节点内存

return;

}

// 情况2:删除非头节点

while (temp != NULL && temp->data != value) {

prev = temp; // 保存当前节点为前一个节点

temp = temp->next; // 移动到下一个节点

}

// 如果链表中不存在该值

if (temp == NULL) return;

// 将前一个节点的 next 指针绕过当前节点,指向当前节点的下一个节点

prev->next = temp->next;

free(temp); // 释放当前节点内存

}

6.链表插入节点

#include

#include

// 定义链表节点结构

struct Node {

int data;

struct Node* next;

};

// 头节点指针

struct Node* head = NULL;

// 顺序插入节点

void insertInOrder(int value) {

// 创建新节点

struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

newNode->data = value;

newNode->next = NULL;

// 情况1:插入到头部或链表为空

if (head == NULL || head->data >= value) {

newNode->next = head;

head = newNode;

} else {

// 情况2:插入到链表的中间或尾部

struct Node* temp = head;

// 找到第一个大于或等于新节点的前一个节点

while (temp->next != NULL && temp->next->data < value) {

temp = temp->next;

}

// 插入节点到合适的位置

newNode->next = temp->next;

temp->next = newNode;

}

}

// 打印链表内容

void printList() {

struct Node* temp = head;

while (temp != NULL) {

printf("%d -> ", temp->data);

temp = temp->next;

}

printf("NULL\n");

}

// 释放链表内存

void freeList() {

struct Node* temp;

while (head != NULL) {

temp = head;

head = head->next;

free(temp);

}

}

int main() {

// 顺序插入节点

insertInOrder(3);

insertInOrder(1);

insertInOrder(5);

insertInOrder(2);

insertInOrder(4);

printf("按顺序插入后的链表:\n");

printList();

// 释放链表内存

freeList();

return 0;

}

7.双向链表

双向链表是一种链表结构,其中每个节点不仅包含指向下一个节点的指针 next,还包含指向前一个节点的指针 prev。这种结构允许我们在链表中双向遍历,即可以从头节点遍历到尾节点,也可以从尾节点遍历回头节点。

typedef struct Node {

int data; // 数据域

struct Node* next; // 指向下一个节点的指针

struct Node* prev; // 指向前一个节点的指针

} Node;

删除节点: 在双向链表中删除节点时,需要注意更新被删除节点的前后节点的指针。

void deleteNode(Node** head, int value) {

Node* temp = *head;

while (temp != NULL && temp->data != value) { // 找到要删除的节点

temp = temp->next;

}

if (temp == NULL) return; // 未找到节点

if (temp->prev != NULL) { // 更新前一个节点的 next 指针

temp->prev->next = temp->next;

} else { // 删除的是头节点

*head = temp->next;

}

if (temp->next != NULL) { // 更新后一个节点的 prev 指针

temp->next->prev = temp->prev;

}

free(temp); // 释放被删除节点的内存

}

插入节点: 双向链表的插入操作根据插入位置不同,头部、尾部、中间

// 插入节点到指定位置

void insertNode(Node** head, int value, int position) {

Node* newNode = (Node*)malloc(sizeof(Node));

newNode->data = value;

newNode->next = NULL;

newNode->prev = NULL;

// 情况1:插入到头部

if (position == 0 || *head == NULL) {

newNode->next = *head; // 新节点的next指向当前头节点

if (*head != NULL) {

(*head)->prev = newNode; // 当前头节点的prev指向新节点

}

*head = newNode; // 更新头节点为新节点

return;

}

Node* temp = *head;

int i;

// 找到插入位置的前一个节点

for (i = 0; i < position - 1 && temp->next != NULL; i++) {

temp = temp->next;

}

// 情况2:插入到尾部(position 超过链表长度时)

if (temp->next == NULL) {

temp->next = newNode; // 当前尾节点的next指向新节点

newNode->prev = temp; // 新节点的prev指向当前尾节点

}

// 情况3:插入到中间位置

else {

newNode->next = temp->next; // 新节点的next指向插入位置的下一个节点

newNode->prev = temp; // 新节点的prev指向插入位置的前一个节点

temp->next->prev = newNode; // 插入位置的下一个节点的prev指向新节点

temp->next = newNode; // 前一个节点的next指向新节点

}

}