数据结构----链表头插中插尾插

一、链表的基本概念

链表是一种线性数据结构,它由一系列节点组成。每个节点包含两个主要部分:

  1. 数据域:用于存储数据元素,可以是任何类型的数据,如整数、字符、结构体等。
  2. 指针域:用于存储下一个节点(在单链表中)或前一个节点与下一个节点(在双链表中)的地址。

与数组不同,链表中的节点在内存中不是连续存储的。它们通过指针链接在一起,形成一个链状结构。这种非连续存储方式使得链表在插入和删除操作上具有一定的优势。

二、单链表

  1. 结构
    • 单链表节点的定义(以C语言为例):
c 复制代码
typedef struct ListNode {
    int data;       // 数据域,可以根据需要存储不同类型的数据
    struct ListNode *next; // 指针域,指向下一个节点
} ListNode;
  • 这里,data用于存储数据,next是一个指向同类型结构体的指针,用于指向下一个节点。
  1. 基本操作
    • 创建节点
      • 函数实现:
c 复制代码
ListNode* createNode(int value) {
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    if (newNode == NULL) {
        // 内存分配失败处理
        return NULL;
    }
    newNode->data = value;
    newNode->next = NULL;
    return newNode;
}
 - 解释:
   - 首先使用`malloc`函数分配足够的内存来存储一个`ListNode`结构体。如果内存分配成功,将传入的值赋给新节点的数据域,并将指针域初始化为`NULL`,表示这是一个孤立的节点,目前没有下一个节点。最后返回新创建的节点。
  • 插入节点
    • 头插法
      • 函数实现:
c 复制代码
ListNode* insertAtHead(ListNode* head, int value) {
    ListNode* newNode = createNode(value);
    newNode->next = head;
    return newNode;
}
   - 解释:
     - 首先调用`createNode`函数创建一个新节点。然后将新节点的`next`指针指向当前的头节点(如果`head`为`NULL`,则新节点成为唯一的节点)。最后,将新节点作为新的头节点返回。这意味着每次插入操作都会使新节点成为链表的第一个节点。
 - **尾插法**
   - 函数实现:
c 复制代码
ListNode* insertAtTail(ListNode* head, int value) {
    ListNode* newNode = createNode(value);
    if (head == NULL) {
        return newNode;
    }
    ListNode* current = head;
    while (current->next!= NULL) {
        current = current->next;
    }
    current->next = newNode;
    return head;
}
   - 解释:
     - 同样先创建新节点。如果链表为空(`head`为`NULL`),则直接返回新节点作为头节点。如果链表不为空,通过一个循环找到链表的最后一个节点(即当前节点的`next`指针为`NULL`的节点)。找到后,将最后一个节点的`next`指针指向新节点,从而将新节点插入到链表的尾部。最后返回原头节点,因为尾插法不会改变头节点。
 - **中插法(在指定位置插入)**
   - 函数实现:
c 复制代码
ListNode* insertAtPosition(ListNode* head, int value, int position) {
    if (position == 0) {
        return insertAtHead(head, value);
    }
    ListNode* newNode = createNode(value);
    ListNode* current = head;
    for (int i = 0; i < position - 1 && current!= NULL; i++) {
        current = current->next;
    }
    if (current == NULL) {
        return head;
    }
    newNode->next = current->next;
    current->next = newNode;
    return head;
}
   - 解释:
     - 首先判断要插入的位置是否为0,如果是,则调用头插法函数进行插入。如果不是0,先创建新节点。然后通过一个循环找到要插入位置的前一个节点(循环条件确保不会超出链表长度且能找到正确位置)。如果在遍历过程中发现当前节点为`NULL`,说明已经到达链表末尾但还没找到插入位置,此时直接返回原链表。找到插入位置的前一个节点后,将新节点的`next`指针指向当前节点的下一个节点,再将当前节点的`next`指针指向新节点,完成插入操作。最后返回原头节点。
  • 删除节点
    • 函数实现:
c 复制代码
ListNode* deleteNode(ListNode* head, int target) {
    if (head == NULL) {
        return NULL;
    }
    if (head->data == target) {
        ListNode* temp = head;
        head = head->next;
        free(temp);
        return head;
    }
    ListNode* current = head;
    while (current->next!= NULL && current->next->data!= target) {
        current = current->next;
    }
    if (current->next!= NULL) {
        ListNode* temp = current->next;
        current->next = current->next->next;
        free(temp);
    }
    return head;
}
 - 解释:
   - 首先判断链表是否为空,如果为空则无法删除,直接返回`NULL`。如果头节点的数据就是要删除的数据,那么将头节点的下一个节点作为新的头节点,并释放原来的头节点,然后返回新头节点。如果头节点不是要删除的节点,通过循环找到要删除节点的前一个节点(通过判断当前节点的下一个节点的数据是否为目标数据)。找到后,将前一个节点的`next`指针指向要删除节点的下一个节点,然后释放要删除的节点,最后返回原头节点。
  • 遍历链表
    • 函数实现:
c 复制代码
void traverseList(ListNode* head) {
    ListNode* current = head;
    while (current!= NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}
 - 解释:
   - 从链表的头节点开始,通过循环依次访问每个节点的数据域,并将其打印出来。每次循环将当前节点更新为下一个节点,直到当前节点为`NULL`,表示已经遍历完整个链表。

三、双链表

  1. 结构
    • 双链表节点的定义(以C语言为例):
c 复制代码
typedef struct DoubleListNode {
    int data;
    struct DoubleListNode *prev;
    struct DoubleListNode *next;
} DoubleListNode;
  • 这里,data用于存储数据,prev是指向前一个节点的指针,next是指向后一个节点的指针。
  1. 基本操作
    • 创建节点
      • 函数实现:
c 复制代码
DoubleListNode* createDoubleNode(int value) {
    DoubleListNode* newNode = (DoubleListNode*)malloc(sizeof(DoubleListNode));
    if (newNode == NULL) {
        return NULL;
    }
    newNode->data = value;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}
 - 解释:
   - 与单链表创建节点类似,使用`malloc`分配内存。成功分配后,将数据赋给新节点的数据域,并将`prev`和`next`指针都初始化为`NULL`,因为新创建的节点暂时没有前后连接的节点。最后返回新节点。
  • 插入节点
    • 例如,在双链表头部插入节点:
      • 函数实现:
c 复制代码
DoubleListNode* insertAtHeadDouble(DoubleListNode* head, int value) {
    DoubleListNode* newNode = createDoubleNode(value);
    if (head == NULL) {
        return newNode;
    }
    newNode->next = head;
    head->prev = newNode;
    return newNode;
}
   - 解释:
     - 先创建新节点。如果链表为空,直接返回新节点作为头节点。如果链表不为空,将新节点的`next`指针指向当前头节点,同时将当前头节点的`prev`指针指向新节点,最后将新节点作为新的头节点返回。
  • 删除节点
    • 例如,删除双链表中值为target的节点:
      • 函数实现:
c 复制代码
DoubleListNode* deleteDoubleNode(DoubleListNode* head, int target) {
    if (head == NULL) {
        return NULL;
    }
    if (head->data == target) {
        DoubleListNode* temp = head;
        if (head->next!= NULL) {
            head->next->prev = NULL;
        }
        head = head->next;
        free(temp);
        return head;
    }
    DoubleListNode* current = head;
    while (current->next!= NULL && current->next->data!= target) {
        current = current->next;
    }
    if (current->next!= NULL) {
        DoubleListNode* temp = current->next;
        if (temp->next!= NULL) {
            temp->next->prev = current;
        }
        current->next = temp->next;
        free(temp);
    }
    return head;
}
   - 解释:
     - 首先判断链表是否为空,为空则无法删除,返回`NULL`。如果头节点的数据是要删除的数据,判断头节点的下一个节点是否存在,如果存在则将其`prev`指针置为`NULL`,然后将头节点更新为下一个节点,并释放原来的头节点,最后返回新头节点。如果头节点不是要删除的节点,通过循环找到要删除节点的前一个节点(通过判断当前节点的下一个节点的数据是否为目标数据)。找到后,如果要删除的节点的下一个节点存在,则将其下一个节点的`prev`指针指向当前节点,然后将当前节点的`next`指针指向要删除节点的下一个节点,最后释放要删除的节点,返回原头节点。
  • 遍历链表
    • 例如,从头部开始向前遍历:
      • 函数实现:
c 复制代码
void traverseDoubleListForward(DoubleListNode* head) {
    DoubleListNode* current = head;
    while (current!= NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}
   - 解释:
     - 与单链表的遍历类似,从头节点开始,通过循环依次访问每个节点的数据域并打印,每次将当前节点更新为下一个节点,直到当前节点为`NULL`。双链表还可以从尾部开始向后遍历,原理类似,只是使用`prev`指针。

四、循环链表

  1. 结构

    • 循环链表分为循环单链表和循环双链表。
    • 循环单链表是最后一个节点的next指针指向头节点,形成一个环。
    • 循环双链表是最后一个节点的next指针指向头节点,头节点的prev指针指向最后一个节点,形成一个双向的环。
  2. 基本操作

    • 创建循环单链表
      • 例如,通过尾插法创建循环单链表:
        • 函数实现:
c 复制代码
ListNode* createCircularList(int values[], int size) {
    if (size == 0) {
        return NULL;
    }
    ListNode* head = createNode(values[0]);
    ListNode* current = head;
    for (int i = 1; i < size; i++) {
        ListNode* newNode = createNode(values[i]);
        current->next = newNode;
        current = newNode;
    }
    current->next = head;
    return head;
}
   - 解释:
     - 首先判断数组大小是否为0,如果是则返回`NULL`。如果不为0,先创建头节点,然后通过循环依次创建新节点并将其插入到链表尾部。最后将最后一个节点的`next`指针指向头节点,形成循环链表,并返回头节点。
  • 遍历循环单链表
    • 例如:
      • 函数实现:
c 复制代码
void traverseCircularList(ListNode* head) {
    if (head == NULL) {
        return;
    }
    ListNode* current = head;
    do {
        printf("%d ", current->data);
        current = current->next;
    } while (current!= head);
    printf("\n");
}
   - 解释:
     - 首先判断链表是否为空,如果为空则直接返回。如果不为空,从头节点开始遍历,通过`do - while`循环依次访问每个节点的数据域并打印。循环条件是当前节点不等于头节点,因为是循环链表,当再次回到头节点时表示遍历结束。最后换行。

循环链表在一些特定的应用场景中非常有用,例如操作系统中的资源分配循环队列等。双链表在需要双向访问数据的场景下有优势,而单链表则在空间利用和简单操作场景下较为常用。

相关推荐
hellojackjiang20117 分钟前
开源轻量级IM框架MobileIMSDK的鸿蒙NEXT客户端库已发布
网络·即时通讯·im开发·mobileimsdk-鸿蒙端
WebDeveloper200141 分钟前
如何使用美国域名中心US Domain Center和WordPress创建商业网站
运维·服务器·css·网络·html
axxy20001 小时前
leetcode之hot100---24两两交换链表中的节点(C++)
c++·leetcode·链表
chenziang11 小时前
leetcode hot100 环形链表2
算法·leetcode·链表
车载诊断技术2 小时前
电子电气架构 --- 什么是EPS?
网络·人工智能·安全·架构·汽车·需求分析
KevinRay_2 小时前
Python超能力:高级技巧让你的代码飞起来
网络·人工智能·python·lambda表达式·列表推导式·python高级技巧
2301_819287123 小时前
ce第六次作业
linux·运维·服务器·网络
CIb0la3 小时前
GitLab 停止为中国区用户提供 GitLab.com 账号服务
运维·网络·程序人生
Black_mario3 小时前
链原生 Web3 AI 网络 Chainbase 推出 AVS 主网, 拓展 EigenLayer AVS 应用场景
网络·人工智能·web3
Aileen_0v04 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper