【数据结构】单链表-c语言

单链表基本概念

头结点(Dummy Node)​

定义:头结点是位于链表第一个有效数据结点(开始结点)之前的一个辅助结点,其数据域通常无意义​(或存储链表长度等元信息),​指针域指向第一个实际存储数据的结点。

作用:

  • 简化边界操作:插入/删除第一个实际结点时无需特殊处理头指针。
  • 统一操作逻辑:无论链表是否为空,头指针始终指向头结点,避免空指针异常。

头指针(Head Pointer)​

定义:头指针是一个指针变量,存储链表中第一个结点的地址。无论链表是否有头结点,头指针必须存在。

特性

永远指向链表入口

  • 若链表有头结点,头指针指向头结点;
  • 若链表无头结点,头指针直接指向第一个数据结点(开始结点)。

判断链表为空的条件

  • 带头结点:head->next == NULL
  • 不带头结点:head == NULL

​开始结点(首元结点)​

  • 定义
    链表中第一个存储实际数据的结点,位于头结点(如果有)之后。

  • 注意
    若链表不带头结点,头指针直接指向开始结点。


对比:带头结点 vs 不带头结点

特性 带头结点的链表 不带头结点的链表
头指针指向 头结点(非数据结点) 开始结点(第一个数据结点)
空链表判断条件 head->next == NULL head == NULL
插入/删除首结点 无需修改头指针,逻辑统一 需修改头指针,需特殊处理
代码复杂度 较低(边界情况少) 较高(需处理头指针变更)

开始敲代码! 

定义单链表结构体

// 定义链表结点结构
typedef struct Node {
    int data;
    struct Node* next;
}LNode;

初始化单链表操作

不带头结点

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 初始化空链表(不带头结点)
Node* initNoDummyList() {
    return NULL; // 头指针初始化为 NULL
}
int main() {
    Node* head = initNoDummyList(); // 空链表
    if (head == NULL) {
        printf("链表初始化成功!\n");
    }
    return 0;
}

带头结点

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

// 初始化空链表(带头结点)
Node *initDummyList() {
    Node *dummyHead = (Node *) malloc(sizeof(Node)); // 创建头结点
    if (dummyHead == NULL) {
        exit(1);
    };
    dummyHead->next = NULL; // 初始为空链表
    return dummyHead;
}

int main() {
    Node *head = initDummyList(); // 空链表
    if (head != NULL) {
        printf("链表初始化成功!\n");
    }
    return 0;
}

尾插/头插

不带头结点

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 初始化空链表(不带头结点)
Node* initNoDummyList() {
    return NULL; // 头指针初始化为 NULL
}

// 头插法:插入到链表头部
void insertAtHead(Node** head, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = *head; // 新节点指向原头节点
    *head = newNode;       // 更新头指针
}

// 尾插法:插入到链表尾部
void insertAtTail(Node** head, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;

    if (*head == NULL) {
        // 链表为空,新节点成为头节点
        *head = newNode;
    } else {
        // 找到最后一个节点并链接新节点
        Node* current = *head;
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = newNode;
    }
}

// 打印链表
void printList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

int main() {
    // 测试头插法
    Node* headInsert = initNoDummyList();
    insertAtHead(&headInsert, 1); // 头部插入 1
    insertAtHead(&headInsert, 2); // 头部插入 2
    insertAtHead(&headInsert, 3); // 头部插入 3
    printf("头插法结果:");
    printList(headInsert); // 输出:3 -> 2 -> 1 -> NULL

    // 测试尾插法
    Node* tailInsert = initNoDummyList();
    insertAtTail(&tailInsert, 1); // 尾部插入 1
    insertAtTail(&tailInsert, 2); // 尾部插入 2
    insertAtTail(&tailInsert, 3); // 尾部插入 3
    printf("尾插法结果:");
    printList(tailInsert); // 输出:1 -> 2 -> 3 -> NULL

    // 释放内存
    free(headInsert);
    free(tailInsert);

    return 0;
}

带头结点

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构体
typedef struct Node {
    int data;           // 节点存储的数据
    struct Node* next;  // 指向下一个节点的指针
} Node;

/**
 * 初始化一个带头结点的空链表
 * @return 返回指向头结点的指针
 */
Node* initDummyList() {
    Node* dummyHead = (Node*)malloc(sizeof(Node)); // 动态分配头结点的内存
    if (dummyHead == NULL) {
        printf("内存分配失败\n"); // 如果分配失败,打印错误信息
        exit(1);                 // 退出程序
    }
    dummyHead->next = NULL; // 头结点的 next 初始化为 NULL
    return dummyHead;       // 返回头结点指针
}

/**
 * 头插法:在头结点后插入新节点
 * @param dummyHead 指向头结点的指针
 * @param data 要插入的数据
 */
void insertAtDummyHead(Node* dummyHead, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点的内存
    if (newNode == NULL) {
        printf("内存分配失败\n"); // 如果分配失败,打印错误信息
        exit(1);                 // 退出程序
    }
    newNode->data = data;          // 设置新节点的数据
    newNode->next = dummyHead->next; // 新节点指向原第一个节点
    dummyHead->next = newNode;     // 头结点指向新节点
}

/**
 * 尾插法:在链表尾部插入新节点
 * @param dummyHead 指向头结点的指针
 * @param data 要插入的数据
 */
void insertAtDummyTail(Node* dummyHead, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点的内存
    if (newNode == NULL) {
        printf("内存分配失败\n"); // 如果分配失败,打印错误信息
        exit(1);                 // 退出程序
    }
    newNode->data = data; // 设置新节点的数据
    newNode->next = NULL; // 新节点指向 NULL,表示链表的末尾

    // 从头结点出发找到最后一个节点
    Node* current = dummyHead;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = newNode; // 将新节点链接到尾部
}

/**
 * 打印链表中的所有节点数据(跳过头结点)
 * @param dummyHead 指向头结点的指针
 */
void printDummyList(Node* dummyHead) {
    Node* current = dummyHead->next; // 从第一个节点开始遍历
    while (current != NULL) {
        printf("%d -> ", current->data); // 打印当前节点的数据
        current = current->next;         // 移动到下一个节点
    }
    printf("NULL\n"); // 打印链表结束标志
}

/**
 * 释放链表占用的内存(包含头结点)
 * @param dummyHead 指向头结点的指针
 */
void freeDummyList(Node* dummyHead) {
    Node* current = dummyHead; // 从头结点开始遍历
    while (current != NULL) {
        Node* temp = current; // 临时保存当前节点
        current = current->next; // 移动到下一个节点
        free(temp); // 释放当前节点的内存
    }
}

int main() {
    // 测试头插法
    Node* headInsertList = initDummyList(); // 初始化带头结点的空链表
    insertAtDummyHead(headInsertList, 1); // 头插 1
    insertAtDummyHead(headInsertList, 2); // 头插 2
    insertAtDummyHead(headInsertList, 3); // 头插 3
    printf("头插法结果:");
    printDummyList(headInsertList); // 输出:3 -> 2 -> 1 -> NULL

    // 测试尾插法
    Node* tailInsertList = initDummyList(); // 初始化带头结点的空链表
    insertAtDummyTail(tailInsertList, 1); // 尾插 1
    insertAtDummyTail(tailInsertList, 2); // 尾插 2
    insertAtDummyTail(tailInsertList, 3); // 尾插 3
    printf("尾插法结果:");
    printDummyList(tailInsertList); // 输出:1 -> 2 -> 3 -> NULL

    // 释放内存
    freeDummyList(headInsertList); // 释放头插法链表的内存
    freeDummyList(tailInsertList); // 释放尾插法链表的内存

    return 0; // 程序正常结束
}

尾删/头删

不带头结点

#include <stdio.h>
#include <stdlib.h> 

// 定义链表节点结构体
typedef struct Node {
    int data;           // 节点存储的数据
    struct Node* next;  // 指向下一个节点的指针
} Node;

/**
 * 初始化一个空链表
 * @return 返回指向链表头节点的指针,初始为NULL
 */
Node* initNoDummyList() { return NULL; } // 返回NULL表示链表为空

/**
 * 在链表尾部插入一个新节点
 * @param head 指向链表头节点指针的指针
 * @param data 要插入的数据
 */
void insertTail(Node** head, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点的内存
    newNode->data = data;                        // 设置新节点的数据
    newNode->next = NULL;                        // 新节点的下一个节点初始化为NULL

    // 如果链表为空,新节点即为头节点
    if (*head == NULL) {
        *head = newNode;
    } else {
        // 遍历链表,找到最后一个节点
        Node* current = *head;
        while (current->next != NULL) {
            current = current->next;
        }
        // 将新节点插入到链表尾部
        current->next = newNode;
    }
}

/**
 * 删除链表的头节点
 * @param head 指向链表头节点指针的指针
 */
void deleteHead(Node** head) {
    if (*head == NULL) { // 如果链表为空,输出提示信息并返回
        printf("链表为空,无法删除\n");
        return;
    }
    Node* temp = *head;       // 保存原头节点
    *head = (*head)->next;    // 头指针指向下一个节点
    free(temp);               // 释放原头节点内存
}

/**
 * 删除链表的尾节点
 * @param head 指向链表头节点指针的指针
 */
void deleteTail(Node** head) {
    if (*head == NULL) { // 如果链表为空,输出提示信息并返回
        printf("链表为空,无法删除\n");
        return;
    }

    // 如果链表只有一个节点,直接删除并置头指针为NULL
    if ((*head)->next == NULL) {
        free(*head);
        *head = NULL;
        return;
    }

    // 遍历链表,找到倒数第二个节点
    Node* current = *head;
    while (current->next->next != NULL) {
        current = current->next;
    }

    // 删除尾节点
    free(current->next);
    current->next = NULL;
}

/**
 * 打印链表中的所有节点数据
 * @param head 指向链表头节点的指针
 */
void printList(Node* head) {
    Node* current = head; // 从头节点开始遍历
    while (current != NULL) {
        printf("%d -> ", current->data); // 打印当前节点的数据
        current = current->next;         // 移动到下一个节点
    }
    printf("NULL\n"); // 打印链表结束标志
}

/**
 * 释放链表中的所有节点内存
 * @param head 指向链表头节点的指针
 */
// 释放链表内存
void freeList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        Node* temp = current;
        current = current->next;
        free(temp);
    }
}

int main() {
    Node* head = initNoDummyList(); // 初始化一个空链表
    insertTail(&head, 1);           // 在链表尾部插入数据1
    insertTail(&head, 2);           // 在链表尾部插入数据2
    insertTail(&head, 3);           // 在链表尾部插入数据3

    printf("删除前:");
    printList(head); // 打印链表:1 -> 2 -> 3 -> NULL

    deleteHead(&head); // 删除链表头节点
    printf("头删后:");
    printList(head); // 打印链表:2 -> 3 -> NULL

    deleteTail(&head); // 删除链表尾节点
    printf("尾删后:");
    printList(head); // 打印链表:2 -> NULL

    freeList(head); // 释放链表内存
    return 0;       // 程序正常结束
}

带头结点

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构体
typedef struct Node {
    int data;           // 节点存储的数据
    struct Node* next;  // 指向下一个节点的指针
} Node;

/**
 * 初始化带头结点的空链表
 * @return 头指针;失败时返回 NULL
 */
Node* initDummyList() {
    Node* dummyHead = (Node*)malloc(sizeof(Node)); // 动态分配头结点内存
    if (dummyHead == NULL) { // 如果内存分配失败,输出提示信息并返回 NULL
        printf("内存分配失败\n");
        return NULL;
    }
    dummyHead->next = NULL; // 头结点的 next 指针初始化为 NULL
    return dummyHead;       // 返回头结点指针
}

/**
 * 头插法:在头结点后插入新节点
 * @param dummyHead 头指针
 * @param data 插入的数据
 */
void insertAtHead(Node* dummyHead, int data) {
    if (dummyHead == NULL) return; // 如果头结点为空,直接返回

    Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点内存
    if (newNode == NULL) { // 如果内存分配失败,输出提示信息并返回
        printf("内存分配失败\n");
        return;
    }
    newNode->data = data;               // 设置新节点的数据
    newNode->next = dummyHead->next;    // 新节点指向原第一个节点
    dummyHead->next = newNode;          // 头结点指向新节点
}

/**
 * 尾插法:在链表尾部插入新节点
 * @param dummyHead 头指针
 * @param data 插入的数据
 */
void insertAtTail(Node* dummyHead, int data) {
    if (dummyHead == NULL) return; // 如果头结点为空,直接返回

    Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点内存
    if (newNode == NULL) { // 如果内存分配失败,输出提示信息并返回
        printf("内存分配失败\n");
        return;
    }
    newNode->data = data; // 设置新节点的数据
    newNode->next = NULL; // 新节点的 next 指针初始化为 NULL

    // 从头结点出发找到最后一个节点
    Node* current = dummyHead;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = newNode; // 将新节点链接到链表尾部
}

/**
 * 头删法:删除第一个数据节点
 * @param dummyHead 头指针
 */
void deleteHead(Node* dummyHead) {
    if (dummyHead == NULL || dummyHead->next == NULL) { // 如果链表为空,输出提示信息并返回
        printf("链表为空,无法删除\n");
        return;
    }

    Node* temp = dummyHead->next;    // 保存待删除节点
    dummyHead->next = temp->next;    // 头结点跳过待删除节点
    free(temp);                      // 释放待删除节点的内存
}

/**
 * 尾删法:删除最后一个数据节点
 * @param dummyHead 头指针
 */
void deleteTail(Node* dummyHead) {
    if (dummyHead == NULL || dummyHead->next == NULL) { // 如果链表为空,输出提示信息并返回
        printf("链表为空,无法删除\n");
        return;
    }

    // 找到倒数第二个节点
    Node* current = dummyHead;
    while (current->next->next != NULL) {
        current = current->next;
    }

    // 删除尾节点
    free(current->next);
    current->next = NULL;
}

/**
 * 打印链表(跳过头结点)
 * @param dummyHead 头指针
 */
void printList(Node* dummyHead) {
    if (dummyHead == NULL) return; // 如果头结点为空,直接返回

    Node* current = dummyHead->next; // 从第一个数据节点开始遍历
    while (current != NULL) {
        printf("%d -> ", current->data); // 打印当前节点的数据
        current = current->next;         // 移动到下一个节点
    }
    printf("NULL\n"); // 打印链表结束标志
}

/**
 * 释放链表内存(含头结点)
 * @param dummyHead 头指针
 */
void freeList(Node* dummyHead) {
    if (dummyHead == NULL) return; // 如果头结点为空,直接返回

    Node* current = dummyHead; // 从头结点开始遍历
    while (current != NULL) {
        Node* temp = current;  // 保存当前节点
        current = current->next; // 移动到下一个节点
        free(temp); // 释放当前节点的内存
    }
}

int main() {
    // 初始化带头结点的空链表
    Node* head = initDummyList();
    if (head == NULL) { // 如果初始化失败,输出提示信息并返回 1
        printf("链表初始化失败\n");
        return 1;
    }

    // 插入测试数据
    insertAtTail(head, 1);  // 尾插 1
    insertAtTail(head, 2);  // 尾插 2
    insertAtHead(head, 3);  // 头插 3
    insertAtHead(head, 4);  // 头插 4

    printf("插入后链表: ");
    printList(head); // 输出: 4 -> 3 -> 1 -> 2 -> NULL

    // 删除操作
    deleteHead(head);       // 删除头节点 (4)
    printf("头删后链表: ");
    printList(head); // 输出: 3 -> 1 -> 2 -> NULL

    deleteTail(head);       // 删除尾节点 (2)
    printf("尾删后链表: ");
    printList(head); // 输出: 3 -> 1 -> NULL

    // 释放内存
    freeList(head);
    return 0; // 程序正常结束
}
操作 不带头结点 带头结点
头删法 直接修改头指针 操作头结点的 next 指针
尾删法 需要处理链表长度为1的情况 统一从头结点开始遍历
时间复杂度 头删 O(1),尾删 O(n) 头删 O(1),尾删 O(n)
空链表判断 head == NULL dummyHead->next == NULL

链表升序/降序排序

不带头结点

升序排序

#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构体
typedef struct Node {
    int data;           // 节点存储的数据
    struct Node* next;  // 指向下一个节点的指针
} Node;
/**
 * @brief 对链表进行升序排序(不使用哑节点)
 * 
 * 该函数通过插入排序算法对链表进行升序排序。排序过程中不使用哑节点,直接操作链表指针。
 * 
 * @param head_ref 指向链表头指针的指针,排序后更新链表头指针
 */
void ascendingSortWithoutDummy(Node** head_ref) {
    if (*head_ref == NULL) return;  // 如果链表为空,直接返回
    Node* sorted = NULL;     // 已排序部分的头指针
    Node* current = *head_ref;  // 当前待插入节点
    while (current != NULL) {
        Node* next = current->next;  // 保存下一个待处理节点
        // 查找插入位置(使用二级指针简化操作)
        Node** pp = &sorted;
        while (*pp != NULL && (*pp)->data < current->data) {
            pp = &((*pp)->next);
        }
        // 插入节点
        current->next = *pp;
        *pp = current;
        current = next;  // 处理下一个节点
    }
    *head_ref = sorted;  // 更新链表头指针
}
/**
 * @brief 打印链表中的所有节点数据
 * 
 * @param node 链表的头节点
 */
void printList(Node* node) {
    while (node != NULL) {
        printf("%d ", node->data);
        node = node->next;
    }
    printf("\n");
}
int main() {
    Node* head = NULL;
    int arr[] = {2, 4, 1, 3};  // 测试数据
    int size = sizeof(arr) / sizeof(arr[0]);
    // 创建链表
    for (int i = size - 1; i >= 0; i--) {
        Node* new_node = (Node*)malloc(sizeof(Node));
        new_node->data = arr[i];
        new_node->next = head;
        head = new_node;
    }
    printf("原链表: ");
    printList(head);  // 输出:2 4 1 3
    ascendingSortWithoutDummy(&head);
    printf("升序排序后: ");
    printList(head);  // 输出:1 2 3 4
    return 0;
}

降序排序

#include <stdio.h>
#include <stdlib.h>

// 链表节点定义
typedef struct Node {
    int data;
    struct Node* next;
}Node;
/**
 * @brief 对带有哑结点的链表进行升序排序
 * 该函数使用插入排序算法对链表进行升序排序。链表包含一个哑结点(dummy node),
 * 哑结点的下一个节点是链表的第一个实际节点。
 * @param dummy 指向链表哑结点的指针,哑结点的下一个节点是链表的第一个实际节点
 */
void ascendingSortWithDummy(Node* dummy) {
    // 如果链表为空(只有哑结点),直接返回
    if (dummy->next == NULL) return;

    // 初始化已排序部分的尾节点和当前待插入节点
    Node* sorted_tail = dummy->next;  // 已排序部分的尾节点
    Node* current = sorted_tail->next; // 当前待插入节点

    // 断开已排序部分和未排序部分,以便逐个插入未排序节点
    sorted_tail->next = NULL;

    // 遍历未排序部分的每个节点,将其插入到已排序部分的适当位置
    while (current != NULL) {
        // 保存下一个待处理节点
        Node* next = current->next;

        // 从头结点开始查找插入位置
        Node* prev = dummy;
        while (prev->next != NULL && prev->next->data < current->data) {
            prev = prev->next;
        }

        // 将当前节点插入到已排序部分的适当位置
        current->next = prev->next;
        prev->next = current;

        // 处理下一个节点
        current = next;
    }
}
// 尾插法添加节点(用于测试)
void append(Node* dummy, int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = NULL;

    Node* tail = dummy;
    while (tail->next != NULL) {
        tail = tail->next;
    }
    tail->next = new_node;
}
// 打印链表
void printList(Node* node) {
    while (node != NULL) {
        printf("%d ", node->data);
        node = node->next;
    }
    printf("\n");
}
int main() {
    Node dummy;  // 头结点
    dummy.next = NULL;

    // 尾插法创建链表:3 -> 1 -> 4 -> 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无限循环者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值