数据结构——链表的基本操作

链表

链表的存储结构

c 复制代码
typedef struct Node{
	int data; // 数据域
	struct Node *next; // 指针域(就是和当前节点同类型的另一个节点)
	}Node;

链表初始化

  • 带头结点
c 复制代码
// 一般都采用动态的方式去创建
// 初始化带头结点的链表
Node* InitNode(){
	Node* head = (Node*)malloc(sizeof(Node));
	head->next = NULL;
	return head;
}
  • 不带头结点
c 复制代码
Node* InitNode(){
	return NULL;
}

链表的插入

注:下面的几个基本操作都是带头结点的

  • 按位置插入
c 复制代码
// 表头插入
void InsertNode(Node* head, int e)
{
	// 检查头结点是否有效
	if(head == NULL)
	{
		return;
	}
	Node* s = (Node*)malloc(sizeof(Node)); // 这里还可以加一步看是否初始化成功
	s->data = e; 
	s->next = head->next;
	head->next = s;
}

// 指定位置插入
// 思路:需要找到目标位置的前驱结点
void InsertNode(Node* head, int i, int e)
{
	// 检查头结点是否有效
	if(head == NULL)
	{
		return;
	}
	Node* s = (Node*)malloc(sizeof(Node)); 
	
	Node* p = head;
	int count = 0;
	while(p != NULL && count < i-1)
	{
		p = p->next;
		count++;
	}
	s->data = e; 
	s->next = p->next;
	p->next = s;	
}

链表的删除

c 复制代码
// 按值删除
void DeleteNode(Node* head, int e)
{
	Node* p = head;
	while(p != NULL && p->next->data != e)
	{
		p = p->next; // 待删除结点的前驱结点
	}
	if(p->next == NULL)
	{
		printf("Not find.");
		return;
	}
	Node* temp = p->next;
	p->next = temp->next;
	free(temp);
	temp = NULL; // 避免野指针
}

链表的查找

  • 按值查找,返回位置
c 复制代码
int FindNode(Node* head, int e)
{
	Node* p = head;
	int count = 1;
	while(p != NULL)
	{
		if(p->data == e)
		{
			return count
		}
		p = p->next; 
		count++;
	}
	return -1 // 返回值是int类型
}

链表的修改

  • 按位置修改
c 复制代码
void modifyNode(Node* head, int i, int e)
{
	Node* p = head;
	int count = 1;
	while(p != NULL && count < i)
	{
		p = p->next;
		count++;
	}
	if(p == NULL)
	{
		printf("Invalid location.");
		return;
	}
	p->data = e;
}

带头结点链表基本操作的完整代码

  • 在使用带头结点链表的过程中,一定要正确区分头节点和数据节点,请看下面错误示例
c 复制代码
// 错误示例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct Node {
    int data; // 数据域
    struct Node* next; // 指针域
}Node;

Node* InitNode() {
	Node* head = (Node*)malloc(sizeof(Node));
	head->next = NULL;
	return head;
}

void InsertNode(Node** head, int i, int e)
{
	// 检查头结点是否有效
	if (head == NULL)
	{
		return;
	}
	Node* s = (Node*)malloc(sizeof(Node));

	Node* p = *head;
	int count = 0;
	// 找到指定位置的前驱结点
	while (p != NULL && count < i - 1)
	{
		p = p->next;
		count++;
	}
	s->data = e;
	s->next = p->next;
	p->next = s;
}

 //按值删除
void DeleteNode(Node** head, int e)
{
	Node* p = *head;
	while (p != NULL && p->next->data != e)
	{
		p = p->next; // 待删除结点的前驱结点
	}
	if (p->next == NULL)
	{
		printf("Not find.");
		return;
	}
	Node* temp = p->next;
	p->next = temp->next;
	free(temp);
	temp = NULL; // 避免野指针
}

// 按位修改
void modifyNode(Node** head, int i, int e)
{
	Node* p = *head;
	int count = 1;
	while (p != NULL && count < i)
	{
		p = p->next;
		count++;
	}
	if (p == NULL)
	{
		printf("Invalid location.");
		return;
	}
	p->data = e;
}

// 按值查找,返回位置
int FindNode(Node* head, int e)
{
	Node* p = head;
	int count = 1;
	while (p != NULL)
	{
		if (p->data == e)
		{
			printf("Find it at position %d\n", count);
			return count;
		}
		p = p->next;
		count++;
	}
	return -1; // 返回值是int类型
}

void PrintNode(Node* head) {
	if (head == NULL) {
		printf("链表为空\n");
		return;
	}

	Node* p = head;
	printf("链表元素: ");
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}

int main() {
    Node* head = InitNode(); // 初始化顺序表

    // 测试插入操作
	InsertNode(&head, 1, 10);
	InsertNode(&head, 2, 20);
	InsertNode(&head, 3, 30);
	PrintNode(head);

	DeleteNode(&head, 30);
	PrintNode(head);           // 应该输出: 10 20 

	modifyNode(&head, 2, 25);
	PrintNode(head);           // 应该输出: 10 25

	FindNode(head, 25); // 应该返回: 2
	
    return 0;
}

输出:

原因:

  1. 头节点的 data 未初始化,导致打印出随机垃圾值
  2. 插入、查找、修改等操作都错误地包含了头节点,导致位置计算偏移

正确代码:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct Node {
    int data;          // 数据域
    struct Node* next; // 指针域
} Node;

// 初始化带头节点的链表(头节点不存储实际数据)
Node* InitNode() {
    Node* head = (Node*)malloc(sizeof(Node));
    if (head == NULL) {
        printf("内存分配失败\n");
        return NULL;
    }
    head->next = NULL;  // 头节点的next初始化为NULL
    // 头节点数据域可以不用初始化(因为不使用)
    return head;
}

// 在第i个位置插入元素e(i从1开始,从第一个数据节点计数)
void InsertNode(Node** head, int i, int e) {
    // 检查参数合法性
    if (head == NULL || *head == NULL || i < 1) {
        printf("插入失败:参数不合法或链表未初始化\n");
        return;
    }

    Node* s = (Node*)malloc(sizeof(Node));
    if (s == NULL) {
        printf("内存分配失败\n");
        return;
    }
    s->data = e;

    Node* p = *head;  // p指向头节点(前驱节点的起点)
    int count = 0;    // 头节点对应count=0,数据节点从count=1开始

    // 找到第i-1个数据节点的前驱(头节点或某个数据节点)
    while (p != NULL && count < i - 1) {
        p = p->next;
        count++;
    }

    if (p == NULL) {
        printf("插入位置超出链表长度\n");
        free(s);
        return;
    }

    // 插入新节点
    s->next = p->next;
    p->next = s;
}

// 按值删除节点(删除第一个匹配的节点)
void DeleteNode(Node** head, int e) {
    if (head == NULL || *head == NULL || (*head)->next == NULL) {
        printf("删除失败:链表为空或未初始化\n");
        return;
    }

    Node* p = *head;  // p指向头节点(从这里开始查找前驱)
    // 查找待删除节点的前驱(停在待删除节点的前一个节点)
    while (p->next != NULL && p->next->data != e) {
        p = p->next;
    }

    if (p->next == NULL) {
        printf("未找到值为%d的节点\n", e);
        return;
    }

    // 删除节点
    Node* temp = p->next;
    p->next = temp->next;
    free(temp);
    temp = NULL;
}

// 按位置修改节点值(i从1开始,从第一个数据节点计数)
void modifyNode(Node** head, int i, int e) {
    if (head == NULL || *head == NULL || (*head)->next == NULL || i < 1) {
        printf("修改失败:链表为空或位置不合法\n");
        return;
    }

    Node* p = (*head)->next;  // 直接指向第一个数据节点
    int count = 1;            // 数据节点从1开始计数

    // 找到第i个数据节点
    while (p != NULL && count < i) {
        p = p->next;
        count++;
    }

    if (p == NULL) {
        printf("修改位置超出链表长度\n");
        return;
    }

    p->data = e;  
}

// 按值查找,返回位置(从1开始,从第一个数据节点计数)
int FindNode(Node* head, int e) {
    if (head == NULL || head->next == NULL) {
        printf("查找失败:链表为空\n");
        return -1;
    }

    Node* p = head->next;  // 跳过头节点,从第一个数据节点开始
    int count = 1;         // 数据节点从1开始计数

    while (p != NULL) {
        if (p->data == e) {
            printf("Find it at position %d\n", count);
            return count;
        }
        p = p->next;
        count++;
    }

    printf("未找到值为%d的节点\n", e);
    return -1;
}

// 打印所有数据节点(跳过头节点)
void PrintNode(Node* head) {
    if (head == NULL || head->next == NULL) {
        printf("链表为空\n");
        return;
    }

    Node* p = head->next;  // 跳过头节点,从第一个数据节点开始打印
    printf("链表元素: ");
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

int main() {
    Node* head = InitNode();  // 初始化带头节点的链表

    // 测试插入操作
    InsertNode(&head, 1, 10);  // 位置1插入10
    InsertNode(&head, 2, 20);  // 位置2插入20
    InsertNode(&head, 3, 30);  // 位置3插入30
    PrintNode(head);           // 输出:链表元素: 10 20 30

    // 测试删除操作
    DeleteNode(&head, 30);
    PrintNode(head);           // 输出:链表元素: 10 20

    // 测试修改操作
    modifyNode(&head, 2, 25);
    PrintNode(head);           // 输出:链表元素: 10 25

    // 测试查找操作
    FindNode(head, 25);        // 输出:Find it at position 2

    return 0;
}

注:这里再说明一个问题:

c 复制代码
head == NULL || *head == NULL 

这个检查是很有必要的,是对两种错误场景进行检查

  1. head == NULL :表示传递给函数的二级指针本身就是无效的 (比如调用者错误地传入了 NULL 作为参数,如 InsertNode(NULL, 1, 10))。这种情况下,连 head 都不能访问(会直接触发空指针解引用错误),必须优先检查。
  2. head == NULL :表示二级指针 head 本身有效(非空),但它指向的头节点指针是无效的 (比如链表未初始化)。这种情况下,head 是合法指针,但 head 是空指针,需要单独处理。

无头结点链表的增删改查

  • 注:这里使用了二级指针,不同于之前的顺序表,其操作的是结构体,当我们传递SqList* L(一级指针)时,已经可以通过指针访问并修改结构体内部的所有成员(数组和长度)
  • 而链表为什么使用二级指针的原因是:C语言的函数参数传递是值传递---函数接收的是参数的副本,而非参数,所以如果使用一级指针(Node*)作为参数,函数内部修改的只是头指针的副本 ,无法影响外部实际的头指针。只有通过二级指针(Node**),才能真正修改外部头指针的指向
c 复制代码
// 错误示例:使用一级指针
void InsertAtHead(Node* head, int value) {
    Node* newNode = createNode(value);
    newNode->next = head;
    head = newNode;  // 这里修改的只是函数内部的副本,外部头指针不变
}

// 正确示例:使用二级指针
void InsertAtHead(Node** head, int value) {
    Node* newNode = createNode(value);
    newNode->next = *head;  // *head表示外部的头指针
    *head = newNode;        // 直接修改外部头指针的指向
}
  • 如果操作会改变头指针的指向,就必须用二级指针;如果只是修改节点内部的数据或 next 指针,一级指针就足够了

完整代码:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct Node {
    int data; // 数据域
    struct Node* next; // 指针域
}Node;

Node* InitNode() {
    return NULL;
}

// 使用二级指针才能修改头指针
void InsertNode(Node** head, int i, int e)
{
	// 判断位置是否合法
	while (i < 1)
	{
		return;
	}
	Node* s = (Node*)malloc(sizeof(Node));
	s->data = e;
	// 表头插入
	if (i == 1)
	{
		s->next = *head;
		*head = s;
		return;
	}
	
	Node* p = *head;
	int count = 1;
	while (p != NULL && count < i - 1)
	{
		p = p->next;
		count++;
	}
	if (p == NULL) {
		printf("插入位置超出链表长度\n");
		free(s);  // 释放已分配的内存
		return;
	}
	s->next = p->next;
	p->next = s;
	return;
	

}

 //按值删除
void DeleteNode(Node** head, int e)
{
	// 处理空链表
	if(*head == NULL)
	{
		printf("Empty list.");
		return;
	}
	Node* p = *head;
	Node* prev = NULL;

	// 查找要删除的结点及其前驱结点
	while (p != NULL && p->data != e)
	{
		prev = p;
		p = p->next;
	}

	// 未找到
	if(p == NULL)
	{
		printf("Not find.");
		return;
	}
	
	// 处理头结点的删除
	if(prev == NULL)
	{
		*head = p->next;
	}
	else {
		prev->next = p->next;
	}
	free(p);
	p = NULL; // 避免野指针
}

// 按位修改
bool UpdateNodeByIndex(Node** head, int i, int e)
{
	if(*head == NULL || i < 1)
	{
		printf("Empty list or invalid position.");
		return false;
	}
	Node* p = *head;
	int count = 1;
	while(p != NULL && count < i)
	{
		p = p->next;
		count++;
	}
	p->data = e;
	return true;
}

void PrintNode(Node* head) {
	if (head == NULL) {
		printf("链表为空\n");
		return;
	}

	Node* p = head;
	printf("链表元素: ");
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}

int main() {
    Node* head = InitNode(); // 初始化顺序表

    // 测试插入操作
	InsertNode(&head, 1, 10);
	InsertNode(&head, 2, 20);
	InsertNode(&head, 3, 30);
	PrintNode(head);

	DeleteNode(&head, 30);
	PrintNode(head);           // 应该输出: 10 20 

	UpdateNodeByIndex(&head, 2, 25);
	PrintNode(head);           // 应该输出: 10 25

    return 0;
}
相关推荐
高山有多高3 小时前
顺序表:数据结构中的基础线性存储结构
数据结构
_OP_CHEN3 小时前
数据结构(C语言篇):(十一)二叉树概念介绍
c语言·开发语言·数据结构·二叉树·学习笔记··
Neverfadeaway3 小时前
C语言————冒泡排序(例题2)
c语言·数据结构·算法·冒泡排序·升序排列·降序排列
散1123 小时前
01数据结构-B树
数据结构·b树
亦良Cool3 小时前
001-Pandas的数据结构
数据结构·pandas
惊鸿.Jh3 小时前
1733. 需要教语言的最少人数
算法·leetcode
大千AI助手4 小时前
SPEA2多目标进化算法:理论与应用全解析
算法·多目标优化·种群·spea2·mop·moea·帕累托最优
nsjqj4 小时前
数据结构中的 二叉树
数据结构
初学小白...4 小时前
红黑树-数据结构
数据结构