C语言——链表

在C语言中,链表是一种常见的数据结构,用于存储一系列的元素,但不同于数组,链表中的元素在内存中不是连续存储的,而是通过指针将这些元素链接起来。链表具有动态大小的特点,可以方便地进行插入和删除操作。

链表的基本结构通常包括节点(Node)和头指针(Head Pointer)。每个节点包含两部分:一部分是存储数据的域(Data Field),另一部分是存储指向下一个节点的指针(Pointer Field)。

链表具有几个显著的特点:

  1. 动态大小:链表可以根据需要动态地增加或减少节点,这使得链表在处理未知大小的数据集时非常有用。
  2. 高效的插入和删除:在链表中插入或删除节点通常只需要改变相关节点的指针,而不需要移动大量的数据。
  3. 内存使用:由于链表中的节点是单独分配的,因此可能存在内存碎片的问题。同时,每个节点都需要额外的指针存储空间。

链表类型

在C语言中,链表可以分为单向链表(Singly Linked List)和双向链表(Doubly Linked List)等类型。

  • 单向链表:每个节点只包含一个指向下一个节点的指针。
  • 双向链表:每个节点包含两个指针,一个指向下一个节点,另一个指向前一个节点。这使得在双向链表中向任意方向遍历都变得容易。

链表操作

下面是一个简单的单向链表(Singly Linked List)的实现示例:

1. 定义节点结构

cpp 复制代码
#include <stdio.h>  
#include <stdlib.h>  
  
// 定义节点结构  
struct Node {  
    int data;  
    struct Node* next;  
};

2. 创建新节点

使用malloccalloc等内存分配函数为节点分配内存,并初始化节点的数据域和指针域。

cpp 复制代码
// 创建新节点并返回其指针  
struct Node* createNode(int data) {  
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));  
    if (!newNode) {  
        printf("内存分配失败\n");  
        exit(1);  
    }  
    newNode->data = data;  
    newNode->next = NULL;  
    return newNode;  
}

3. 插入节点

在链表头部插入节点

创建一个新节点,将其指针域指向当前的头节点,然后更新头指针指向新节点。

cpp 复制代码
// 在链表头部插入新节点  
void insertAtHead(struct Node** head, int data) {  
    struct Node* newNode = createNode(data);  
    newNode->next = *head;  
    *head = newNode;  
}
在链表尾部插入节点

遍历链表找到最后一个节点,将其指针域指向新节点。如果链表为空,则新节点成为头节点。

cpp 复制代码
// 在链表尾部插入新节点  
void insertAtTail(struct Node** head, int data) {  
    struct Node* newNode = createNode(data);  
    if (*head == NULL) {  
        *head = newNode;  
        return;  
    }  
    struct Node* temp = *head;  
    while (temp->next != NULL) {  
        temp = temp->next;  
    }  
    temp->next = newNode;  
}
在指定节点后面插入节点

在指定节点后面插入节点时,我们使用当前节点的数据域作为判断条件,插入新的节点时,新的节点需要指向指定节点原本指向的那个节点。而新的节点是插在指定节点的后面,指定节点的指向需要更改为新的节点,这样链表就完整连接起来了。

cpp 复制代码
void insertFromBehind(struct Test *head,int data,struct Test *new)
{
        struct Test *p = head;
        while(1){
                if(p != NULL){
                        if(p->data == data){
                                new->next = p->next;
                                p->next = new;
                        }
                        p = p->next;
                }else{
                        break;
                }
        }
}
在指定节点前面插入节点

在指定节点前面插入节点时,我们要拿当前节点的下一个节点的数据作为判断条件,这样才能保证插入新的节点时,新的节点前面的那个节点可以指向新的节点。而新的节点是插在指定节点的前面,新的节点可以直接指向指定节点,这样链表就前后连接起来了。

cpp 复制代码
struct Test* insertFromForward(struct Test *head,int data,struct Test *new)
{
        struct Test *p = head;
        if(data == p->data){
                new->next = p;
                return new;
        }
        while(p->next != NULL){
                if(p->next->data == data){
                        new->next = p->next;
                        p->next = new;
                        return head;
                }else{
                        p = p->next;
                }
        }
        return 0;
}

4. 删除节点

删除头部节点

更新头指针指向当前头节点的下一个节点,并释放头节点的内存。

cpp 复制代码
// 删除链表头部节点  
struct Test* delteNode(struct Test* head,int data)
{
        struct Test *p = head;
        if(p->data == data){
                head = head->next;
        //      free(p);
                return head;
        }

}
删除指定值的节点

遍历链表找到要删除的节点的前一个节点,然后更新其指针域跳过要删除的节点,并释放该节点的内存。

cpp 复制代码
// 删除链表中第一个值为key的节点  
void deleteNode(struct Node** head, int key) {  
    struct Node* temp = *head;  
    struct Node* prev = NULL;  
  
    // 如果头节点就是要删除的节点  
    if (temp != NULL && temp->data == key) {  
        *head = temp->next;  
        free(temp);  
        return;  
    }  
  
    // 查找要删除的节点  
    while (temp != NULL && temp->data != key) {  
        prev = temp;  
        temp = temp->next;  
    }  
  
    // 如果没有找到  
    if (temp == NULL) return;  
  
    // 解除节点并释放内存  
    prev->next = temp->next;  
    free(temp);  
}
cpp 复制代码
struct Test* delteNode(struct Test* head,int data)
{
        struct Test *p = head;
        //删除头节点
        if(p->data == data){
                head = head->next;
        //      free(p);
                return head;
        }
        //查找要删除的节点
        while(p->next != NULL){
                //找到后进行删除
                if(p->next->data == data){
                        //struct Test *temp = p;
                        p->next = p->next->next;
                        //free(temp);释放配p以避免内存泄漏
                        return head;
                }
                //没找到继续往下找
                p = p->next;
        }//遍历完整个链表找不到后返回头节点
        return head;
}

5. 遍历链表

从头节点开始,使用指针域依次访问每个节点,直到达到链表的末尾(即指针域为NULL的节点)。

cpp 复制代码
// 打印链表中的所有元素  
void printLink(struct Test *head)
{
        while(1){
                if(head != NULL){
                        printf("%d  ",head->data);
                        head = head->next;
                }else{
                        putchar('\n');
                        break;
                }
        }
}

6. 释放链表内存

遍历链表,逐个释放每个节点的内存。这是防止内存泄漏的重要步骤。

cpp 复制代码
// 释放链表的所有节点  
void freeList(struct Node* head) {  
    struct Node* temp;  
    while (head != NULL) {  
        temp = head;  
        head = head->next;  
        free(temp);  
    }  
}
相关推荐
土豆湿1 分钟前
拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流
开发语言·javascript·css
界面开发小八哥8 分钟前
更高效的Java 23开发,IntelliJ IDEA助力全面升级
java·开发语言·ide·intellij-idea·开发工具
qystca37 分钟前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
薯条不要番茄酱37 分钟前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
今天吃饺子42 分钟前
2024年SCI一区最新改进优化算法——四参数自适应生长优化器,MATLAB代码免费获取...
开发语言·算法·matlab
努力进修1 小时前
“探索Java List的无限可能:从基础到高级应用“
java·开发语言·list
Ajiang28247353043 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
幽兰的天空3 小时前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10226 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
网易独家音乐人Mike Zhou6 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot