【数据结构】链表应用-链表重新排序

news2025/2/6 7:03:57

重新排序

  • 反转链表
    • 预期实现
    • 思路
    • 解题过程
    • code
      • 力扣代码
      • 核心代码
      • 完整代码
    • 总结
  • 删除链表中间节点
    • 代码
    • 解惑
  • 链表重新排序
    • 题目描述
    • 解题思路
    • 解题过程
    • 复杂度
    • 代码
      • 力扣代码
      • 完整代码

反转链表

预期实现

在这里插入图片描述

在这里插入图片描述

思路

你选用何种方法解题?
我选用了迭代法来反转链表。这是一种经典且高效的方法,通过遍历链表并逐个反转节点的指针方向来实现。

用三个指针,分别指向当前节点,前一个节点,后一个节点,然后进行反转

解题过程

这些方法具体怎么运用?

  1. 初始化指针:
    prevNode:指向已反转部分的头节点,初始为 NULL。
    currentNode:指向当前待反转的节点,初始为 head。
    nextNode:临时保存当前节点的下一个节点。

  2. 遍历链表:
    在每次循环中:
    保存当前节点的下一个节点到 nextNode。
    将当前节点的 next 指针指向 prevNode,实现反转。
    将 prevNode 移动到当前节点。
    将 currentNode 移动到 nextNode。

  3. 结束条件:
    当 currentNode 为 NULL 时,表示链表已遍历完毕,此时 prevNode 指向反转后的新头节点。

  4. 返回结果:
    将 head 指向 prevNode,并返回 head。

作者:北国无红豆
链接:https://leetcode.cn/problems/UHnkqh/solutions/3062635/fan-zhuan-lian-biao-die-dai-fa-by-chun-s-yg81/
来源:力扣(LeetCode)

在这里插入图片描述

然后改变second指向

在这里插入图片描述

移动三个指针
在这里插入图片描述

再次改变second指向
在这里插入图片描述
在继续同步挪动三个指针
在这里插入图片描述
……

直到second指向NULL

在这里插入图片描述

最后加个head
在这里插入图片描述

code

力扣代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* reverseList(struct ListNode* head){
    if (head == NULL)
        return NULL;
    struct ListNode* prevNode = NULL;
    struct ListNode* currentNode = head;
    struct ListNode* nextNode;

    while(currentNode != NULL)
    {
        nextNode = currentNode->next;
        currentNode->next = prevNode;
        prevNode = currentNode;
        currentNode = nextNode;
    }
    head = prevNode;
    return head;
}

作者:北国无红豆
链接:https://leetcode.cn/problems/UHnkqh/solutions/3062635/fan-zhuan-lian-biao-die-dai-fa-by-chun-s-yg81/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

核心代码

/**
 * @description: 反转链表
 * @param {Node} *head  头节点
 * @return {*}          返回反转后的头节点
 * note:
 * 空指针检查:检查head是否为NULL,避免非法访问。
 * 直接操作原头节点:反转完成后,将原头节点的next指向反转后的首节点(prev),无需新建头节点。
 * 处理所有边界条件:链表为空(head->next为NULL)时,循环不会执行,直接返回head。
 *
 * 创建的三个节点是first,second,third 局部指针变量,不需要free释放内存
 * first->next 或 first->data 是通过指针访问节点的成员。
 * 直接写 first 表示操作指针本身(例如赋值或比较)。
 */
Node *ReverseList(Node *head)
{
    if (head == NULL)
    {
        return NULL; // 处理空头节点情况
    }
    Node *first = NULL;        // 定义一个指针first,指向空NULL,代表反转之后的尾
    Node *second = head->next; // 定义一个指针second,指向头节点的下一个节点,代表当前节点
    Node *third = NULL;        // 定义一个指针third

    while (second != NULL)
    {
        third = second->next; // 将third指向second的下一个节点,保存下一个节点的地址
        second->next = first; // 将当前节点的next指针指向first,实现反转
        first = second;       // 将first指向second,移动到下一个节点,指针的赋值操作
        second = third;       // 将second指向third,移动到下一个节点
    }

    head->next = first; // 头节点的next指针指向first,实现反转

    return head; // 返回新的头节点
}

int main(int argc, char const *argv[])
{
    // 初始化链表
    Node *list = InitList();

    // 获取尾节点
    Node *tail = GetTail(list);

    tail = InsertTail(tail, 1);
    tail = InsertTail(tail, 2);
    tail = InsertTail(tail, 3);
    tail = InsertTail(tail, 4);
    tail = InsertTail(tail, 5);
    tail = InsertTail(tail, 6);

    TraverseList(list); // 遍历链表

    // 反转链表
    Node *ReverseListHead = ReverseList(list);
    TraverseList(ReverseListHead); // 遍历链表
    return 0;
}

完整代码

/**
 * @description: 反转链表
 *
 * 思路:用三个指针,分别指向当前节点,前一个节点,后一个节点,然后进行反转
 */

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

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}

// 初始化节点(带节点数据域参数)
Node *InitListWithElem(ElemType e)
{
    Node *node = (Node *)malloc(sizeof(node)); // 为节点分配内存
    node->data = e;                            // 节点的数据域为e
    node->next = NULL;                         // 节点的指针域为空
    return node;                               // 返回节点
}

/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域,这里是%d,因为ElemType是int类型
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

/* 单链表 - 尾插法 */
// 获取尾节点地址
Node *GetTail(Node *List)
{
    Node *p = List;         // 从头节点开始遍历
    while (p->next != NULL) // 遍历到链表末尾
    {
        p = p->next; // 移动到下一个节点
    }
    return p; // 返回尾节点
}

/**
 * @Description:单链表 - 尾插法插入数据
 * @param {Node} *tail   尾节点
 * @param {ElemType} e   插入的数据
 * @return {*}           返回新的尾节点
 */
Node *InsertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    tail->next = p;                         // 尾节点的指针域指向新节点
    p->next = NULL;                         // 新节点的指针域为空
    return p;                               // 返回新的尾节点
}

/**
 * @Description:单链表 - 在链表尾部插入节点
 * @param {Node} *tail   链表尾部节点
 * @param {Node} *node   要插入的节点
 * @return {Node *}      插入节点后的链表尾部节点
 */
Node *InsertTailWithNode(Node *tail, Node *node)
{
    tail->next = node; // 尾节点的指针域指向要插入的节点
    node->next = NULL; // 要插入的节点的指针域为空
    return node;       // 返回新的尾节点
}

/**
 * @Description:单链表 - 在指定位置插入数据
 * @param {Node} *L     单链表的头节点
 * @param {int} pos     位置
 * @param {ElemType} e  插入的数据
 * @return {*}
 */
int InsertPosNode(Node *L, int pos, ElemType e)
{
    // 用来保存插入位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到插入位置的前驱节点
    while (i < pos - 1) // 遍历到插入位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("插入位置不合法\n");
            return 0;
        }
    }

    Node *newnode = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    newnode->data = e;                            // 在新节点的数据域存入数据e
    newnode->next = p->next;                      // 新节点的指针域指向插入位置的前驱节点的下一个节点
    p->next = newnode;                            // 插入位置的前驱节点的指针域指向新节点
    return 1;
}

/**
 * @Description:单链表 - 删除指定位置的节点
 * @param {Node} *L 单链表的头节点
 * @param {int} pos 位置
 * @return {*}       返回1表示成功
 */
int DeletePosNode(Node *L, int pos)
{
    // 用来保存删除位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到删除节点的前驱节点
    while (i < pos - 1) // 遍历到删除位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("删除位置不合法\n");
            return 0;
        }
    }
    if (p->next == NULL) // 判断删除位置是否合法
    {
        printf("删除位置不合法\n");
        return 0;
    }
    Node *q = p->next; // 保存要删除的节点的地址
    p->next = q->next; // 删除节点的前驱节点的指针域 指向 删除节点的下一个节点
    free(q);           // 释放删除节点的内存

    return 1; // 返回1表示成功
}

int GetListLength(Node *L)
{
    int length = 0;
    Node *p = L; // 从头节点开始遍历,头节点算在内

    while (p != NULL)
    {
        p = p->next;
        length++;
    }
    return length;
}

void FreeList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历,头节点不需要释放
    Node *q = NULL;    // 用来保存下一个节点的地址,q能掌握下一个节点的地址,这是灵魂所在
    while (p != NULL)
    {
        q = p->next; // 保存下一个节点的地址
        free(p);     // 释放当前节点的内存
        p = q;       // 移动到下一个节点
    }
    L->next = NULL; // 头节点的指针域为空
}

// 查找倒数第k个节点
int findNodeFS(Node *L, int k)
{
    Node *fast = L->next;
    Node *slow = L->next;

    for (int i = 0; i < k; i++)
    {
        fast = fast->next;
    }

    while (fast != NULL)
    {
        fast = fast->next;
        slow = slow->next;
    }

    printf("倒数第%d个节点值为:%d\n", k, slow->data);
    return 1;
}

// 查找两个节点共同后缀的起始位置
Node *findIntersectionNode(Node *headA, Node *headB)
{
    if (headA == NULL || headB == NULL)
    {
        return NULL;
    }

    Node *p = headA;
    int lenA = 0;
    int lenB = 0;

    // 遍历链表A,获取链表A的长度
    while (p != NULL)
    {
        p = p->next;
        lenA++;
    }
    // 遍历链表B,获取链表B的长度
    p = headB;
    while (p != NULL)
    {
        p = p->next;
        lenB++;
    }

    Node *fast; // 快指针
    Node *slow; // 慢指针
    int step;   // 两个单词之间数量的差值,可以用于快指针先走的步数
    if (lenA > lenB)
    {
        step = lenA - lenB;
        fast = headA;
        slow = headB;
    }
    else
    {
        step = lenB - lenA;
        fast = headB;
        slow = headA;
    }
    // 让快指针先走step步
    for (int i = 0; i < step; i++)
    {
        fast = fast->next;
    }
    // 快慢指针同步走,直到指向同一个节点退出循环
    while (fast != slow)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}

// 函数:RemoveEqualNodes
// 功能:删除链表中与给定值相等的节点
// 参数:Node *L:链表头指针,int n:链表的长度
// 返回值:无
void RemoveEqualNodes(Node *L, int n)
{
    // TODO: 实现删除链表中与给定值相等的节点的功能
    Node *p = L;                                   // 定义一个指针p,指向链表的头节点
    int index;                                     // 定义一个变量index,作为数组下标使用
    int *q = (int *)malloc(sizeof(int) * (n + 1)); // 在堆内存中分配一个数组,用来存储已经出现过的绝对值

    /* 遍历数组,初始化为0 */
    for (int i = 0; i < n + 1; i++)
    {
        *(q + i) = 0; // 初始化为0,表示没有出现过这个绝对值
    }

    while (p->next != NULL)
    {
        // 获取绝对值
        index = abs(p->next->data); // 计算当前节点的绝对值,作为数组下标使用

        if (*(q + index) == 0) // 如果这个绝对值没有出现过
        {
            *(q + index) = 1; // 标记为已经出现过
            p = p->next;      // 移动到下一个节点
        }
        else // 如果这个绝对值已经出现过,删除当前节点
        {
            Node *tempNode = p->next; // 保存要删除的节点的地址
            p->next = tempNode->next; // 删除当前节点
            free(tempNode);           // 释放当前节点的内存
        }
    }
    free(q); // 释放数组的内存
}

/**
 * @description: 反转链表
 * @param {Node} *head  头节点
 * @return {*}          返回反转后的头节点
 * note:
 * 空指针检查:检查head是否为NULL,避免非法访问。
 * 直接操作原头节点:反转完成后,将原头节点的next指向反转后的首节点(prev),无需新建头节点。
 * 处理所有边界条件:链表为空(head->next为NULL)时,循环不会执行,直接返回head。
 *
 * 创建的三个节点是first,second,third 局部指针变量,不需要free释放内存
 * first->next 或 first->data 是通过指针访问节点的成员。
 * 直接写 first 表示操作指针本身(例如赋值或比较)。
 */
Node *ReverseList(Node *head)
{
    if (head == NULL)
    {
        return NULL; // 处理空头节点情况
    }
    Node *first = NULL;        // 定义一个指针first,指向空NULL,代表反转之后的尾
    Node *second = head->next; // 定义一个指针second,指向头节点的下一个节点,代表当前节点
    Node *third = NULL;        // 定义一个指针third

    while (second != NULL)
    {
        third = second->next; // 将third指向second的下一个节点,保存下一个节点的地址
        second->next = first; // 将当前节点的next指针指向first,实现反转
        first = second;       // 将first指向second,移动到下一个节点,指针的赋值操作
        second = third;       // 将second指向third,移动到下一个节点
    }

    head->next = first; // 头节点的next指针指向first,实现反转

    return head; // 返回新的头节点
}

int main(int argc, char const *argv[])
{
    // 初始化链表
    Node *list = InitList();

    // 获取尾节点
    Node *tail = GetTail(list);

    tail = InsertTail(tail, 1);
    tail = InsertTail(tail, 2);
    tail = InsertTail(tail, 3);
    tail = InsertTail(tail, 4);
    tail = InsertTail(tail, 5);
    tail = InsertTail(tail, 6);

    TraverseList(list); // 遍历链表

    // 反转链表
    Node *ReverseListHead = ReverseList(list);
    TraverseList(ReverseListHead); // 遍历链表
    return 0;
}

总结

  • 方法:迭代法,通过遍历链表逐个反转节点指针。
  • 时间复杂度:O(n),只需遍历链表一次。
  • 空间复杂度:O(1),仅使用常数个额外指针。
  • 优点:高效、直观,适合所有单链表反转场景。

删除链表中间节点

在这里插入图片描述

  • 删除节点4
  • 使用快慢指针,快指针每次走两步,慢指针每次走一步,当快指针走到链表末尾时,慢指针指向的就是链表中间节点。
  • 如果链表长度为偶数,中间节点有两个,通常删除第二个中间节点。

在这里插入图片描述

代码

int DeleteMidNode(Node *head)
{
    Node *fastNode = head->next; // 快指针,先走一步,后面每次走两步
    Node *slowNode = head;       // 慢指针,每次走一步
    /* 当快指针的下一个或者下一个的下一个是NULL,说明快指针已经走到了链表的末尾 */
    while (fastNode != NULL && fastNode->next != NULL) // 快指针走到链表末尾时,慢指针指向的就是链表中间节点
    {
        fastNode = fastNode->next->next; // 快指针每次走两步
        slowNode = slowNode->next;       // 慢指针每次走一步
    }
    // 删除中间节点
    Node *tempNode = slowNode->next; // 保存要删除的节点的地址
    slowNode->next = tempNode->next; // 删除当前节点
    free(tempNode);                  // free(tempNode) 释放的是 tempNode 所指向的内存,也就是被删除节点的堆内存
    return 1;                        // 删除成功返回1
}

解惑

  • notes:问题:我自己创建了一个tempNode,然后free了tempNode,那么被删除的那个节点,没有被free,那么在内存里还存在被删除的节点吗?下面是deepseek的回答:
  • tempNode 的作用:tempNode 是一个指针,它指向的是 slowNode->next,也就是链表中要被删除的节点。tempNode 本身只是一个指针变量,它存储的是被删除节点的内存地址。
  • free(tempNode) 的作用:free(tempNode) 释放的是 tempNode 所指向的内存,也就是被删除节点的内存。free 并不会释放 tempNode 这个指针变量本身(指针变量是存储在栈上的),而是释放指针所指向的堆内存。
  • 被删除节点的内存状态:当你调用 free(tempNode) 后,被删除节点的内存会被操作系统标记为“可回收”,这意味着这块内存不再属于你的程序,操作系统可以将其重新分配给其他部分使用。因此,被删除的节点在内存中不再有效。
  • 总结:tempNode 会随着函数结束自动销毁;被删除的节点是通过 free 函数释放的内存。

链表重新排序

题目描述

在这里插入图片描述

在这里插入图片描述

解题思路

预期效果
在这里插入图片描述

  1. 从中间位置断开(找到链表的中间节点:使用快慢指针法,快指针每次走两步,慢指针每次走一步,当快指针到达链表末尾时,慢指针正好在链表的中间位置。)
    在这里插入图片描述
  2. 反转后面的链表,456变成654(反转后半部分链表:从中间节点开始,反转链表的后半部分)
    在这里插入图片描述
  3. 见缝插针,缝合链表(合并两个链表:将前半部分链表和反转后的后半部分链表交替合并)
    在这里插入图片描述

解题过程

这些方法具体怎么运用?

  1. 快慢指针找中间节点:
    初始化快指针 fastNode 和慢指针 slowNode 都指向链表的头节点 head。
    快指针每次移动两步,慢指针每次移动一步,直到快指针到达链表末尾。
    当快指针到达末尾时,慢指针正好在链表的中间位置。
  2. 反转链表:
    从慢指针 slowNode 的下一个节点开始,反转链表的后半部分。
    使用三个指针 prevNode、currentNode 和 nextNode 来反转链表。
    反转完成后,将前半部分链表和后半部分链表断开。
  3. 合并链表:
    使用两个指针 p1 和 q1 分别指向前半部分链表和反转后的后半部分链表的头节点。
    交替合并两个链表,直到其中一个链表遍历完毕。

作者:北国无红豆
链接:https://leetcode.cn/problems/LGjMqU/solutions/3063709/zhong-pai-lian-biao-kuai-man-zhi-zhen-fa-aghl/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度

时间复杂度: O(n)
空间复杂度: O(1)

代码

力扣代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

void reorderList(struct ListNode* head){
    /* 1.快慢指针找中间节点 */
    struct ListNode *fastNode = head;
    struct ListNode *slowNode = head;

    while((fastNode != NULL) && (fastNode->next != NULL))
    {
        fastNode = fastNode->next->next;
        slowNode = slowNode->next;
    }

    /* 2.反转链表 - 三指针 */
    struct ListNode *prevNode = NULL;
    struct ListNode *currentNode = slowNode->next;
    struct ListNode *nextNode = NULL;
    slowNode->next = NULL;  // 断开前后链表

    while( currentNode != NULL )
    {
        nextNode = currentNode->next;
        currentNode->next = prevNode;
        prevNode = currentNode;
        currentNode = nextNode;
    }

    /* 3.合并链表 */
    struct ListNode *p1 = head;
    struct ListNode *q1 = prevNode;
    struct ListNode *p2, *q2;

    while((p1!=NULL) && (q1!=NULL))
    {
        // save next node
        p2 = p1->next;
        q2 = q1->next;

        // 合并节点
        p1->next = q1;
        q1->next = p2;

        // move node
        p1 = p2;
        q1 = q2;
    }
}

完整代码

/**
 * @description: 删除链表中间节点
 * 思路:快慢指针,快指针每次走两步,慢指针每次走一步,当快指针走到链表末尾时,慢指针指向的就是链表中间节点。
 */

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

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}

// 初始化节点(带节点数据域参数)
Node *InitListWithElem(ElemType e)
{
    Node *node = (Node *)malloc(sizeof(node)); // 为节点分配内存
    node->data = e;                            // 节点的数据域为e
    node->next = NULL;                         // 节点的指针域为空
    return node;                               // 返回节点
}

/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域,这里是%d,因为ElemType是int类型
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

/* 单链表 - 尾插法 */
// 获取尾节点地址
Node *GetTail(Node *List)
{
    Node *p = List;         // 从头节点开始遍历
    while (p->next != NULL) // 遍历到链表末尾
    {
        p = p->next; // 移动到下一个节点
    }
    return p; // 返回尾节点
}

/**
 * @Description:单链表 - 尾插法插入数据
 * @param {Node} *tail   尾节点
 * @param {ElemType} e   插入的数据
 * @return {*}           返回新的尾节点
 */
Node *InsertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    tail->next = p;                         // 尾节点的指针域指向新节点
    p->next = NULL;                         // 新节点的指针域为空
    return p;                               // 返回新的尾节点
}

/**
 * @Description:单链表 - 在链表尾部插入节点
 * @param {Node} *tail   链表尾部节点
 * @param {Node} *node   要插入的节点
 * @return {Node *}      插入节点后的链表尾部节点
 */
Node *InsertTailWithNode(Node *tail, Node *node)
{
    tail->next = node; // 尾节点的指针域指向要插入的节点
    node->next = NULL; // 要插入的节点的指针域为空
    return node;       // 返回新的尾节点
}

/**
 * @Description:单链表 - 在指定位置插入数据
 * @param {Node} *L     单链表的头节点
 * @param {int} pos     位置
 * @param {ElemType} e  插入的数据
 * @return {*}
 */
int InsertPosNode(Node *L, int pos, ElemType e)
{
    // 用来保存插入位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到插入位置的前驱节点
    while (i < pos - 1) // 遍历到插入位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("插入位置不合法\n");
            return 0;
        }
    }

    Node *newnode = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    newnode->data = e;                            // 在新节点的数据域存入数据e
    newnode->next = p->next;                      // 新节点的指针域指向插入位置的前驱节点的下一个节点
    p->next = newnode;                            // 插入位置的前驱节点的指针域指向新节点
    return 1;
}

/**
 * @Description:单链表 - 删除指定位置的节点
 * @param {Node} *L 单链表的头节点
 * @param {int} pos 位置
 * @return {*}       返回1表示成功
 */
int DeletePosNode(Node *L, int pos)
{
    // 用来保存删除位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到删除节点的前驱节点
    while (i < pos - 1) // 遍历到删除位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("删除位置不合法\n");
            return 0;
        }
    }
    if (p->next == NULL) // 判断删除位置是否合法
    {
        printf("删除位置不合法\n");
        return 0;
    }
    Node *q = p->next; // 保存要删除的节点的地址
    p->next = q->next; // 删除节点的前驱节点的指针域 指向 删除节点的下一个节点
    free(q);           // 释放删除节点的内存

    return 1; // 返回1表示成功
}

int GetListLength(Node *L)
{
    int length = 0;
    Node *p = L; // 从头节点开始遍历,头节点算在内

    while (p != NULL)
    {
        p = p->next;
        length++;
    }
    return length;
}

void FreeList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历,头节点不需要释放
    Node *q = NULL;    // 用来保存下一个节点的地址,q能掌握下一个节点的地址,这是灵魂所在
    while (p != NULL)
    {
        q = p->next; // 保存下一个节点的地址
        free(p);     // 释放当前节点的内存
        p = q;       // 移动到下一个节点
    }
    L->next = NULL; // 头节点的指针域为空
}

// 查找倒数第k个节点
int findNodeFS(Node *L, int k)
{
    Node *fast = L->next;
    Node *slow = L->next;

    for (int i = 0; i < k; i++)
    {
        fast = fast->next;
    }

    while (fast != NULL)
    {
        fast = fast->next;
        slow = slow->next;
    }

    printf("倒数第%d个节点值为:%d\n", k, slow->data);
    return 1;
}

// 查找两个节点共同后缀的起始位置
Node *findIntersectionNode(Node *headA, Node *headB)
{
    if (headA == NULL || headB == NULL)
    {
        return NULL;
    }

    Node *p = headA;
    int lenA = 0;
    int lenB = 0;

    // 遍历链表A,获取链表A的长度
    while (p != NULL)
    {
        p = p->next;
        lenA++;
    }
    // 遍历链表B,获取链表B的长度
    p = headB;
    while (p != NULL)
    {
        p = p->next;
        lenB++;
    }

    Node *fast; // 快指针
    Node *slow; // 慢指针
    int step;   // 两个单词之间数量的差值,可以用于快指针先走的步数
    if (lenA > lenB)
    {
        step = lenA - lenB;
        fast = headA;
        slow = headB;
    }
    else
    {
        step = lenB - lenA;
        fast = headB;
        slow = headA;
    }
    // 让快指针先走step步
    for (int i = 0; i < step; i++)
    {
        fast = fast->next;
    }
    // 快慢指针同步走,直到指向同一个节点退出循环
    while (fast != slow)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}

// 函数:RemoveEqualNodes
// 功能:删除链表中与给定值相等的节点
// 参数:Node *L:链表头指针,int n:链表的长度
// 返回值:无
void RemoveEqualNodes(Node *L, int n)
{
    // TODO: 实现删除链表中与给定值相等的节点的功能
    Node *p = L;                                   // 定义一个指针p,指向链表的头节点
    int index;                                     // 定义一个变量index,作为数组下标使用
    int *q = (int *)malloc(sizeof(int) * (n + 1)); // 在堆内存中分配一个数组,用来存储已经出现过的绝对值

    /* 遍历数组,初始化为0 */
    for (int i = 0; i < n + 1; i++)
    {
        *(q + i) = 0; // 初始化为0,表示没有出现过这个绝对值
    }

    while (p->next != NULL)
    {
        // 获取绝对值
        index = abs(p->next->data); // 计算当前节点的绝对值,作为数组下标使用

        if (*(q + index) == 0) // 如果这个绝对值没有出现过
        {
            *(q + index) = 1; // 标记为已经出现过
            p = p->next;      // 移动到下一个节点
        }
        else // 如果这个绝对值已经出现过,删除当前节点
        {
            Node *tempNode = p->next; // 保存要删除的节点的地址
            p->next = tempNode->next; // 删除当前节点
            free(tempNode);           // 释放当前节点的内存
        }
    }
    free(q); // 释放数组的内存
}

/**
 * @description: 反转链表
 * @param {Node} *head  头节点
 * @return {*}          返回反转后的头节点
 * note:
 * 空指针检查:检查head是否为NULL,避免非法访问。
 * 直接操作原头节点:反转完成后,将原头节点的next指向反转后的首节点(prev),无需新建头节点。
 * 处理所有边界条件:链表为空(head->next为NULL)时,循环不会执行,直接返回head。
 *
 * 创建的三个节点是first,second,third 局部指针变量,不需要free释放内存
 * first->next 或 first->data 是通过指针访问节点的成员。
 * 直接写 first 表示操作指针本身(例如赋值或比较)。
 */
Node *ReverseList(Node *head)
{
    if (head == NULL)
    {
        return NULL; // 处理空头节点情况
    }
    Node *first = NULL;        // 定义一个指针first,指向空NULL,代表反转之后的尾
    Node *second = head->next; // 定义一个指针second,指向头节点的下一个节点,代表当前节点
    Node *third = NULL;        // 定义一个指针third

    while (second != NULL)
    {
        third = second->next; // 将third指向second的下一个节点,保存下一个节点的地址
        second->next = first; // 将当前节点的next指针指向first,实现反转
        first = second;       // 将first指向second,移动到下一个节点,指针的赋值操作
        second = third;       // 将second指向third,移动到下一个节点
    }

    head->next = first; // 头节点的next指针指向first,实现反转

    return head; // 返回新的头节点
}

int DeleteMidNode(Node *head)
{
    Node *fastNode = head->next; // 快指针,先走一步,后面每次走两步
    Node *slowNode = head;       // 慢指针,每次走一步
    /* 当快指针的下一个或者下一个的下一个是NULL,说明快指针已经走到了链表的末尾 */
    while (fastNode != NULL && fastNode->next != NULL) // 快指针走到链表末尾时,慢指针指向的就是链表中间节点
    {
        fastNode = fastNode->next->next; // 快指针每次走两步
        slowNode = slowNode->next;       // 慢指针每次走一步
    }
    // 删除中间节点
    Node *tempNode = slowNode->next; // 保存要删除的节点的地址
    slowNode->next = tempNode->next; // 删除当前节点
    free(tempNode);                  // free(tempNode) 释放的是 tempNode 所指向的内存,也就是被删除节点的堆内存
    return 1;                        // 删除成功返回1
}

/**
 * notes:问题:我自己创建了一个tempNode,然后free了tempNode,那么被删除的那个节点,没有被free,那么在内存里还存在被删除的节点吗?下面是deepseek的回答:
 * tempNode 的作用:tempNode 是一个指针,它指向的是 slowNode->next,也就是链表中要被删除的节点。tempNode 本身只是一个指针变量,它存储的是被删除节点的内存地址。
 * free(tempNode) 的作用:free(tempNode) 释放的是 tempNode 所指向的内存,也就是被删除节点的内存。free 并不会释放 tempNode 这个指针变量本身(指针变量是存储在栈上的),而是释放指针所指向的堆内存。
 * 被删除节点的内存状态:当你调用 free(tempNode) 后,被删除节点的内存会被操作系统标记为“可回收”,这意味着这块内存不再属于你的程序,操作系统可以将其重新分配给其他部分使用。因此,被删除的节点在内存中不再有效。
 *
 * 总结:tempNode 会随着函数结束自动销毁;被删除的节点是通过 free 函数释放的内存。
 */

// 重新排列链表
void reOrderList(Node *head)
{
    // TODO: 实现重新排列链表的功能
    Node *fast = head; // 快指针,不需要从head->next开始,因为要找到中间节点(偶数个节点时,中间节点是中间两个节点的前一个节点,奇数个节点时,中间节点是中间那个节点)
    Node *slow = head;
    while (fast != NULL && fast->next != NULL) // 快指针走到链表末尾时,慢指针指向的就是链表中间节点
    {
        fast = fast->next->next;
        slow = slow->next;
    }

    Node *first = NULL;        // 用来保存反转后的链表的头节点
    Node *second = slow->next; // 从中间节点开始反转
    Node *third = NULL;        // 用来保存下一个节点的地址
    slow->next = NULL;         // 中间节点的next指向NULL,从中间断开链表,分成两个链表,再合并两个链表

    while (second != NULL)
    {
        third = second->next; // 保存下一个节点的地址
        second->next = first; // 反转
        first = second;       // 移动到下一个节点
        second = third;       // 移动到下一个节点
    }

    // 合并两个链表
    Node *p1 = head->next; // 从头节点的下一个节点开始遍历
    Node *q1 = first;      // 从反转后的链表的头节点开始遍历
    Node *p2, *q2;
    while ((p1 != NULL) && (q1 != NULL)) // 当两个链表都没有遍历完时,交替合并两个链表
    {
        p2 = p1->next; // 保存p1的下一个节点的地址
        q2 = q1->next; // 保存q1的下一个节点的地址

        p1->next = q1; // 交替合并两个链表,p1和q1交替连接,p2和q2交替连接,直到有一个链表遍历完为止
        q1->next = p2; // 交替合并两个链表,p1和q1交替连接,p2和q2交替连接,直到有一个链表遍历完为止

        p1 = p2; // 移动到下一个节点
        q1 = q2; // 移动到下一个节点
    }
}

int main(int argc, char const *argv[])
{
    // 初始化链表
    Node *list = InitList();

    // 获取尾节点
    Node *tail = GetTail(list);

    tail = InsertTail(tail, 1);
    tail = InsertTail(tail, 2);
    tail = InsertTail(tail, 3);
    tail = InsertTail(tail, 4);
    tail = InsertTail(tail, 5);
    tail = InsertTail(tail, 6);
    tail = InsertTail(tail, 7);

    printf("打印链表:\n");
    TraverseList(list); // 遍历链表

    printf("重新排列链表:\n");
    reOrderList(list);  // 重新排列链表
    TraverseList(list); // 遍历链表
    return 0;
}

/* 链表重新排序 */

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2293655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习threejs,pvr格式图片文件贴图

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️PVR贴图1.2 ☘️THREE.Mesh…

2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题2)-网络部分解析-附详细代码

目录 附录1:拓扑图​编辑 附录2:地址规划表 1.SW1 2.SW2 3.SW3 4.SW4 5.SW5 6.SW6 7.SW7 8.R1 9.R2 10.R3 11.AC1 12.AC2 13.EG1 14.EG2 15.AP2 16.AP3 附录1:拓扑图 附录2:地址规划表

C++,STL,【目录篇】

文章目录 一、简介二、内容提纲第一部分&#xff1a;STL 概述第二部分&#xff1a;STL 容器第三部分&#xff1a;STL 迭代器第四部分&#xff1a;STL 算法第五部分&#xff1a;STL 函数对象第六部分&#xff1a;STL 高级主题第七部分&#xff1a;STL 实战应用 三、写作风格四、…

【AI论文】直接对齐算法之间的差异模糊不清

摘要&#xff1a;直接对齐算法&#xff08;DAAs&#xff09;通过在对齐人类反馈的强化学习&#xff08;RLHF&#xff09;中用直接策略优化替代强化学习&#xff08;RL&#xff09;和奖励建模&#xff08;RM&#xff09;&#xff0c;简化了语言模型对齐过程。DAAs可以根据其排序…

(9)gdb 笔记(2):查看断点 info b,删除断点 delete 3,回溯 bt,

&#xff08;11&#xff09; 查看断点 info b&#xff1a; # info b举例&#xff1a; &#xff08;12&#xff09;删除断点 delete 2 或者删除所有断点&#xff1a; # 1. 删除指定的断点 delete 3 # 2. 删除所有断点 delete 回车&#xff0c;之后输入 y 确认删除所有断点 举…

中间件的概念及基本使用

什么是中间件 中间件是ASP.NET Core的核心组件&#xff0c;MVC框架、响应缓存、身份验证、CORS、Swagger等都是内置中间件。 广义上来讲&#xff1a;Tomcat、WebLogic、Redis、IIS&#xff1b;狭义上来讲&#xff0c;ASP.NET Core中的中间件指ASP.NET Core中的一个组件。中间件…

S4 HANA手工记账Tax Payable – FB41

本文主要介绍在S4 HANA OP中手工记账Tax Payable – FB41。具体请参照如下内容&#xff1a; 手工记账Tax Payable – FB41 该事务代码用于手工处理税码统驭科目的记账&#xff0c;一般税码科目需要设置为只能自动记账&#xff0c;因此无法手工对税码统驭科目记账&#xff0c;但…

Java 大视界 -- Java 大数据在智慧文旅中的应用与体验优化(74)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

[leetcode]两数之和等于target

源代码 #include <iostream> #include <list> #include <iterator> // for std::prev using namespace std; int main() { int target 9; list<int> l{ 2, 3, 4, 6, 8 }; l.sort(); // 确保列表是排序的&#xff0c;因为双指针法要求输入是…

老游戏回顾:G2

一个老的RPG游戏。 剧情有独到之处。 ------- 遥远的过去&#xff0c;古拉纳斯将希望之光给予人们&#xff0c;人类令希望之光不断扩大&#xff0c;将繁荣握在手中。 但是&#xff0c;暗之恶魔巴鲁玛将光从人类身上夺走。古拉纳斯为了守护人类与其展开了一场激战&#xff0c…

行为驱动开发(BDD)如何提高自动化测试效率

在软件开发的过程中&#xff0c;自动化测试一直扮演着至关重要的角色。随着需求变化日益复杂、开发周期不断压缩&#xff0c;如何提升自动化测试的效率和准确性成为了现代软件开发团队的核心挑战之一。行为驱动开发&#xff08;BDD&#xff0c;Behavior Driven Development&…

Redis常见数据类型与编码方式

⭐️前言⭐️ 本小节围绕Redis中常见的数据类型与编码方式展开。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题可以在评论区留言 &#x1f349;博客中涉及源码及博主日常练习代码均已上传GitHu…

大型三甲医院算力网络架构的深度剖析与关键技术探索

一、引言 1.1 研究背景与意义 1.1.1 医疗信息化发展趋势 随着信息技术的迅猛发展&#xff0c;全球医疗行业正经历着深刻的数智化转型。数字化转型已成为医疗行业提升服务质量、优化运营效率、推动医学科研创新的关键驱动力。从电子病历系统的普及到远程医疗的广泛应用&#…

CSV数据分析智能工具(基于OpenAI API和streamlit)

utils.py&#xff1a; from langchain_openai import ChatOpenAI from langchain_experimental.agents.agent_toolkits import create_csv_agent import jsonPROMPT_TEMPLATE """你是一位数据分析助手&#xff0c;你的回应内容取决于用户的请求内容。1. 对于文…

产品经理的人工智能课 02 - 自然语言处理

产品经理的人工智能课 02 - 自然语言处理 1 自然语言处理是什么2 一个 NLP 算法的例子——n-gram 模型3 预处理与重要概念3.1 分词 Token3.2 词向量化表示与 Word2Vec 4 与大语言模型的交互过程参考链接 大语言模型&#xff08;Large Language Models, LLMs&#xff09;是自然语…

华为手机nova9,鸿蒙系统版本4.2.0.159,智慧助手.今天版本是14.x,如何卸载智慧助手.今天?

手欠&#xff0c;将手机鸿蒙系统升级到4.2.0.159后&#xff0c;出现了负一屏&#xff0c;负一屏就是主页向左滑&#xff0c;出现了&#xff0c;如图的界面&#xff1a; 华为鸿蒙系统负一屏的界面 通过在手机中我的华为-搜索“开启或关闭智慧助手.今天&#xff08;负一屏&#…

win32汇编环境,窗口程序中自定义工具栏的使用示例

;运行效果 ;win32汇编环境,窗口程序中自定义工具栏的使用示例 ;工具栏一般放在菜单下面&#xff0c;相当于一个个小的对话框&#xff0c;当然你放在其它地方也可以。 ;原理是&#xff0c;创建一张BMP位图&#xff0c;比如下例用一张168*24的图&#xff0c;平均分成7部分&#x…

【PyQt】pyqt小案例实现简易文本编辑器

pyqt小案例实现简易文本编辑器 分析 实现了一个简单的文本编辑器&#xff0c;使用PyQt5框架构建。以下是代码的主要功能和特点&#xff1a; 主窗口类 (MyWindow): 继承自 QWidget 类。使用 .ui 文件加载用户界面布局。设置窗口标题、状态栏消息等。创建菜单栏及其子菜单项&…

2024最新版Node.js详细安装教程(含npm配置淘宝最新镜像地址)

一&#xff1a;Node.js安装 浏览器中搜索Nodejs&#xff0c;或直接用网址:Node.js — 在任何地方运行 JavaScript 建议此处下载长期支持版本&#xff08;红框内&#xff09;: 开始下载&#xff0c;完成后打开文件: 进入安装界面&#xff0c;在此处勾选&#xff0c;再点击n…

【HTML入门】Sublime Text 4与 Phpstorm

文章目录 前言一、环境基础1.Sublime Text 42.Phpstorm(1)安装(2)启动Phpstorm(3)“启动”码 二、HTML1.HTML简介(1)什么是HTML(2)HTML版本及历史(3)HTML基本结构 2.HTML简单语法(1)HTML标签语法(2)HTML常用标签(3)表格(4)特殊字符 总结 前言 在当今的软件开发领域&#xff0c…