C语言常用数据结构-单链表的详解与实现

单链表(Singly Linked List)是一种基本的线性数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。在C语言中,通常使用动态内存分配来创建和管理单链表,下面详细介绍了单链表的结构定义及其常用操作。

单链表结构定义

单链表的节点结构定义如下:

typedef struct Node {
    int data;         // 节点数据域
    struct Node* next; // 指向下一个节点的指针
} Node;

每个节点包含一个整型数据 data 和一个指向下一个节点的指针 next。最后一个节点的 next 指针通常指向 NULL,表示链表的结束。

单链表结构体

整个单链表的结构体定义如下:

typedef struct {
    Node* head; // 头指针,指向头结点
    int size;   // 链表的节点数量
} LinkedList;
  • head 是指向链表头部的指针,实际上是指向一个空的头结点,而不是第一个数据节点。
  • size 是链表中节点的数量,包括头结点。

常用操作详解

1. 创建链表和销毁链表

创建链表 createLinkedList()

LinkedList* createLinkedList() {
    LinkedList* list = malloc(sizeof(LinkedList));
    if (list == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return NULL;
    }
    
    // 创建头结点
    list->head = malloc(sizeof(Node));
    if (list->head == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        free(list);
        return NULL;
    }
    list->head->next = NULL;  // 头结点初始时不指向任何节点
    list->size = 0;  // 初始时链表没有有效节点
    
    return list;
}

createLinkedList() 函数动态分配内存创建一个空的链表,并初始化头结点和链表大小为0。

销毁链表 destroyLinkedList()

void destroyLinkedList(LinkedList* list) {
    if (list == NULL) return;
    
    Node* curr = list->head;
    while (curr != NULL) {
        Node* next = curr->next;
        free(curr);
        curr = next;
    }
    
    free(list);
}

destroyLinkedList() 函数释放链表的所有节点和头结点的内存空间,最后释放整个链表结构体的内存。

2. 插入操作

头部插入 insertAtHead()

void insertAtHead(LinkedList* list, int data) {
    Node* newNode = malloc(sizeof(Node));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    
    // 新节点指向当前的第一个节点
    newNode->next = list->head->next;
    // 头结点指向新节点,新节点成为新的第一个节点
    list->head->next = newNode;
    
    list->size++; // 链表大小加1
}
  • insertAtHead() 在链表头部插入一个新节点,新节点成为链表的第一个有效节点。

尾部插入 insertAtTail()

void insertAtTail(LinkedList* list, int data) {
    Node* newNode = malloc(sizeof(Node));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    newNode->next = NULL; // 新节点为尾节点,其 next 指针置为 NULL
    
    Node* curr = list->head;
    while (curr->next != NULL) {
        curr = curr->next;
    }
    // curr 指向最后一个节点,将新节点链接到最后
    curr->next = newNode;
    
    list->size++; // 链表大小加1
}
  • insertAtTail() 在链表尾部插入一个新节点,新节点成为链表的最后一个有效节点。

指定位置插入 insertAtIndex()

void insertAtIndex(LinkedList* list, int data, int index) {
    if (index < 0 || index > list->size) {
        fprintf(stderr, "Invalid index\n");
        return;
    }
    
    Node* newNode = malloc(sizeof(Node));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    
    Node* curr = list->head;
    for (int i = 0; i < index; i++) {
        curr = curr->next;
    }
    
    // 新节点指向当前节点的下一个节点
    newNode->next = curr->next;
    // 当前节点指向新节点,新节点插入到当前位置
    curr->next = newNode;
    
    list->size++; // 链表大小加1
}
  • insertAtIndex() 在指定位置插入一个新节点,索引从0开始,0表示插入到头结点之后,1表示插入到第一个节点之后,依此类推。
3. 删除操作

头部删除 deleteAtHead()

void deleteAtHead(LinkedList* list) {
    if (list->head->next == NULL) {
        fprintf(stderr, "Empty list\n");
        return;
    }
    
    Node* temp = list->head->next; // 保存第一个有效节点的指针
    list->head->next = temp->next; // 头结点指向第二个节点
    free(temp); // 释放第一个节点的内存
    
    list->size--; // 链表大小减1
}
  • deleteAtHead() 删除链表的头部节点,即删除第一个有效节点。

尾部删除 deleteAtTail()

void deleteAtTail(LinkedList* list) {
    if (list->head->next == NULL) {
        fprintf(stderr, "Empty list\n");
        return;
    }
    
    Node* curr = list->head;
    while (curr->next->next != NULL) {
        curr = curr->next;
    }
    
    Node* temp = curr->next; // 保存最后一个节点的指针
    curr->next = NULL; // 倒数第二个节点的 next 指针置为 NULL
    free(temp); // 释放最后一个节点的内存
    
    list->size--; // 链表大小减1
}
  • deleteAtTail() 删除链表的尾部节点,即删除最后一个有效节点。

指定位置删除 deleteAtIndex()

void deleteAtIndex(LinkedList* list, int index) {
    if (index < 0 || index >= list->size) {
        fprintf(stderr, "Invalid index\n");
        return;
    }
    
    Node* curr = list->head;
    for (int i = 0; i < index; i++) {
        curr = curr->next;
    }
    
    Node* temp = curr->next; // 保存要删除节点的指针
    curr->next = temp->next; // 当前节点指向删除节点的下一个节点
    free(temp); // 释放删除节点的内存
    
    list->size--; // 链表大小减1
}
  • deleteAtIndex() 删除指定位置的节点,索引从0开始,0表示删除第一个有效节点,1表示删除第二个节点,依此类推。
4. 查找操作

按值查找 findByValue()

Node* findByValue(LinkedList* list, int data) {
    Node* curr = list->head->next; // 从第一个有效节点开始查找
    while (curr != NULL) {
        if (curr->data == data) {
            return curr; // 找到节点并返回指针
        }
        curr = curr->next; // 移动到下一个节点
    }
    return NULL; // 未找到返回 NULL
}
  • findByValue() 根据节点值查找节点,并返回节点的指针。如果找到则返回节点指针,否则返回 NULL

按索引查找 findByIndex()

Node* findByIndex(LinkedList* list, int index) {
    if (index < 0 || index >= list->size) {
        fprintf(stderr, "Invalid index\n");
        return NULL;
    }
    
    Node* curr = list->head->next; // 从第一个有效节点开始查找
    for (int i = 0; i < index; i++) {
        curr = curr->next;
    }
    
    return curr; // 返回找到的节点指针
}
  • findByIndex() 根据节点索引查找节点,并返回节点的指针。索引从0开始,0表示第一个有效节点,1表示第二个节点,依此类推。
5. 修改操作

修改节点数据 updateNode()

void updateNode(Node* node, int newData) {
    if (node != NULL) {
        node->data = newData; // 修改节点数据
    }
}
  • updateNode() 修改指定节点的数据值为 newData
6. 打印操作

打印链表 printLinkedList()

void printLinkedList(LinkedList* list) {
    Node* curr = list->head->next; // 从第一个有效节点开始打印
    while (curr != NULL) {
        printf("%d -> ", curr->data);
        curr = curr->next;
    }
    printf("NULL\n");
}
  • printLinkedList() 打印整个链表的节点数据,依次输出每个节点的数据值,以 NULL 结尾表示链表结束。

示例代码

下面是一个使用上述定义的单链表操作的示例代码:

单链表结构定义和基本操作

LinkedList.h
#ifndef LINKEDLIST_H
#define LINKEDLIST_H

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

typedef struct {
    Node* head; // 头指针,指向头结点
    int size;   // 链表大小(节点数量)
} LinkedList;

LinkedList* createLinkedList();  // 创建链表
void destroyLinkedList(LinkedList* list);  // 销毁链表

void insertAtHead(LinkedList* list, int data);  // 头部插入节点
void insertAtTail(LinkedList* list, int data);  // 尾部插入节点
void insertAtIndex(LinkedList* list, int data, int index);  // 指定位置插入节点

void deleteAtHead(LinkedList* list);  // 删除头部节点
void deleteAtTail(LinkedList* list);  // 删除尾部节点
void deleteAtIndex(LinkedList* list, int index);  // 删除指定位置节点

Node* findByValue(LinkedList* list, int data);  // 查找节点
Node* findByIndex(LinkedList* list, int index);  // 根据索引查找节点

void printLinkedList(LinkedList* list);  // 打印链表

#endif /* LINKEDLIST_H */

LinkedList.c

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

// 创建链表
LinkedList* createLinkedList() {
    LinkedList* list = malloc(sizeof(LinkedList));
    if (list == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return NULL;
    }
    
    // 创建头结点,并初始化链表大小为0
    list->head = malloc(sizeof(Node));
    if (list->head == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        free(list);
        return NULL;
    }
    list->head->next = NULL;
    list->size = 0;
    
    return list;
}

// 销毁链表
void destroyLinkedList(LinkedList* list) {
    if (list == NULL) return;
    
    Node* curr = list->head;
    while (curr != NULL) {
        Node* next = curr->next;
        free(curr);
        curr = next;
    }
    
    free(list);
}

// 头部插入节点
void insertAtHead(LinkedList* list, int data) {
    Node* newNode = malloc(sizeof(Node));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    
    newNode->next = list->head->next; // 新节点指向原头结点的下一个节点
    list->head->next = newNode;       // 头结点指向新节点
    
    list->size++;
}

// 尾部插入节点
void insertAtTail(LinkedList* list, int data) {
    Node* newNode = malloc(sizeof(Node));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    newNode->next = NULL;
    
    Node* curr = list->head;
    while (curr->next != NULL) {
        curr = curr->next;
    }
    curr->next = newNode;
    
    list->size++;
}

// 指定位置插入节点
void insertAtIndex(LinkedList* list, int data, int index) {
    if (index < 0 || index > list->size) {
        fprintf(stderr, "Invalid index\n");
        return;
    }
    
    Node* newNode = malloc(sizeof(Node));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    newNode->data = data;
    
    Node* curr = list->head;
    for (int i = 0; i < index; i++) {
        curr = curr->next;
    }
    
    newNode->next = curr->next;
    curr->next = newNode;
    
    list->size++;
}

// 删除头部节点
void deleteAtHead(LinkedList* list) {
    if (list->head->next == NULL) {
        fprintf(stderr, "Empty list\n");
        return;
    }
    
    Node* temp = list->head->next;
    list->head->next = temp->next;
    free(temp);
    
    list->size--;
}

// 删除尾部节点
void deleteAtTail(LinkedList* list) {
    if (list->head->next == NULL) {
        fprintf(stderr, "Empty list\n");
        return;
    }
    
    Node* curr = list->head;
    while (curr->next->next != NULL) {
        curr = curr->next;
    }
    
    Node* temp = curr->next;
    curr->next = NULL;
    free(temp);
    
    list->size--;
}

// 删除指定位置节点
void deleteAtIndex(LinkedList* list, int index) {
    if (index < 0 || index >= list->size) {
        fprintf(stderr, "Invalid index\n");
        return;
    }
    
    Node* curr = list->head;
    for (int i = 0; i < index; i++) {
        curr = curr->next;
    }
    
    Node* temp = curr->next;
    curr->next = temp->next;
    free(temp);
    
    list->size--;
}

// 根据节点值查找节点
Node* findByValue(LinkedList* list, int data) {
    Node* curr = list->head->next;
    while (curr != NULL) {
        if (curr->data == data) {
            return curr;
        }
        curr = curr->next;
    }
    return NULL;
}

// 根据索引查找节点
Node* findByIndex(LinkedList* list, int index) {
    if (index < 0 || index >= list->size) {
        fprintf(stderr, "Invalid index\n");
        return NULL;
    }
    
    Node* curr = list->head->next;
    for (int i = 0; i < index; i++) {
        curr = curr->next;
    }
    
    return curr;
}

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

示例程序演示链表的使用

main.c
#include <stdio.h>
#include "LinkedList.h"

int main() {
    LinkedList* list = createLinkedList();

    // 插入一些节点
    insertAtTail(list, 1);
    insertAtTail(list, 2);
    insertAtTail(list, 3);
    insertAtHead(list, 0);
    
    printf("Linked List: ");
    printLinkedList(list);  // 打印链表:0 -> 1 -> 2 -> 3 -> NULL
    
    // 删除节点
    deleteAtTail(list);
    deleteAtHead(list);
    
    printf("Linked List after deletion: ");
    printLinkedList(list);  // 打印链表:1 -> 2 -> NULL
    
    // 查找节点
    Node* node = findByValue(list, 2);
    if (node != NULL) {
        printf("Found node with value 2\n");
    } else {
        printf("Node with value 2 not found\n");
    }
    
    // 销毁链表
    destroyLinkedList(list);
    
    return 0;
}

常用操作及其时间复杂度

  1. 头部插入 (insertAtHead)

    • 时间复杂度:O(1)
    • 在链表头部插入节点只需修改几个指针的指向,操作的时间与链表的长度无关,因此是常数时间复杂度。
  2. 尾部插入 (insertAtTail)

    • 时间复杂度:O(n)
    • 需要遍历链表找到尾节点,然后在尾节点后面插入新节点。因为需要遍历整个链表,所以时间复杂度为线性,与链表长度成正比。
  3. 指定位置插入 (insertAtIndex)

    • 时间复杂度:O(n)
    • 需要先遍历找到指定位置的前一个节点,然后进行插入操作。因为需要遍历到指定位置,所以时间复杂度为线性。
  4. 头部删除 (deleteAtHead)

    • 时间复杂度:O(1)
    • 删除头节点只需修改头指针,释放原头节点的内存,操作的时间与链表的长度无关,因此是常数时间复杂度。
  5. 尾部删除 (deleteAtTail)

    • 时间复杂度:O(n)
    • 需要遍历链表找到尾节点的前一个节点,然后进行删除操作。因为需要遍历到尾节点前面的位置,所以时间复杂度为线性。
  6. 指定位置删除 (deleteAtIndex)

    • 时间复杂度:O(n)
    • 需要先遍历找到指定位置的前一个节点,然后进行删除操作。因为需要遍历到指定位置,所以时间复杂度为线性。
  7. 按值查找 (findByValue)

    • 时间复杂度:O(n)
    • 需要遍历整个链表来查找具有给定值的节点,因为需要考虑最坏情况下找到值在链表末尾,所以时间复杂度为线性。
  8. 按索引查找 (findByIndex)

    • 时间复杂度:O(n)
    • 需要遍历整个链表来查找给定索引位置的节点,因为需要遍历到指定位置,所以时间复杂度为线性。
  9. 修改节点数据 (updateNode)

    • 时间复杂度:O(1)
    • 直接修改节点的数据域,操作的时间与链表的长度无关,因此是常数时间复杂度。

总结

  • 单链表的插入和删除操作的时间复杂度取决于操作位置,头部操作是 O(1),尾部和中间操作是 O(n)。
  • 单链表的查找操作(按值和按索引)都是 O(n),因为需要遍历链表来寻找目标节点。
  • 单链表适合于频繁插入、删除、查找的场景,但需要注意其插入和删除操作的时间复杂度与操作位置相关。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值