【数据结构】链表应用-链表重新排序
重新排序
- 反转链表
- 预期实现
- 思路
- 解题过程
- code
- 力扣代码
- 核心代码
- 完整代码
- 总结
- 删除链表中间节点
- 代码
- 解惑
- 链表重新排序
- 题目描述
- 解题思路
- 解题过程
- 复杂度
- 代码
- 力扣代码
- 完整代码
反转链表
预期实现
思路
你选用何种方法解题?
我选用了迭代法来反转链表。这是一种经典且高效的方法,通过遍历链表并逐个反转节点的指针方向来实现。
用三个指针,分别指向当前节点,前一个节点,后一个节点,然后进行反转
解题过程
这些方法具体怎么运用?
-
初始化指针:
prevNode:指向已反转部分的头节点,初始为 NULL。
currentNode:指向当前待反转的节点,初始为 head。
nextNode:临时保存当前节点的下一个节点。 -
遍历链表:
在每次循环中:
保存当前节点的下一个节点到 nextNode。
将当前节点的 next 指针指向 prevNode,实现反转。
将 prevNode 移动到当前节点。
将 currentNode 移动到 nextNode。 -
结束条件:
当 currentNode 为 NULL 时,表示链表已遍历完毕,此时 prevNode 指向反转后的新头节点。 -
返回结果:
将 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; // 定义一个指针thirdwhile (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; // 头节点的数据域为0head->next = NULL; // 头节点的指针域为空return head; // 返回头节点
}// 初始化节点(带节点数据域参数)
Node *InitListWithElem(ElemType e)
{Node *node = (Node *)malloc(sizeof(node)); // 为节点分配内存node->data = e; // 节点的数据域为enode->next = NULL; // 节点的指针域为空return node; // 返回节点
}/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点p->data = e; // 在新节点的数据域存入数据ep->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; // 在新节点的数据域存入数据etail->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; // 在新节点的数据域存入数据enewnode->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; // 定义一个指针thirdwhile (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 函数释放的内存。
链表重新排序
题目描述
解题思路
预期效果
- 从中间位置断开(找到链表的中间节点:使用快慢指针法,快指针每次走两步,慢指针每次走一步,当快指针到达链表末尾时,慢指针正好在链表的中间位置。)
- 反转后面的链表,456变成654(反转后半部分链表:从中间节点开始,反转链表的后半部分)
- 见缝插针,缝合链表(合并两个链表:将前半部分链表和反转后的后半部分链表交替合并)
解题过程
这些方法具体怎么运用?
- 快慢指针找中间节点:
初始化快指针 fastNode 和慢指针 slowNode 都指向链表的头节点 head。
快指针每次移动两步,慢指针每次移动一步,直到快指针到达链表末尾。
当快指针到达末尾时,慢指针正好在链表的中间位置。 - 反转链表:
从慢指针 slowNode 的下一个节点开始,反转链表的后半部分。
使用三个指针 prevNode、currentNode 和 nextNode 来反转链表。
反转完成后,将前半部分链表和后半部分链表断开。 - 合并链表:
使用两个指针 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 nodep2 = p1->next;q2 = q1->next;// 合并节点p1->next = q1;q1->next = p2;// move nodep1 = 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; // 头节点的数据域为0head->next = NULL; // 头节点的指针域为空return head; // 返回头节点
}// 初始化节点(带节点数据域参数)
Node *InitListWithElem(ElemType e)
{Node *node = (Node *)malloc(sizeof(node)); // 为节点分配内存node->data = e; // 节点的数据域为enode->next = NULL; // 节点的指针域为空return node; // 返回节点
}/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点p->data = e; // 在新节点的数据域存入数据ep->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; // 在新节点的数据域存入数据etail->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; // 在新节点的数据域存入数据enewnode->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; // 定义一个指针thirdwhile (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;
}/* 链表重新排序 */
相关文章:
【数据结构】链表应用-链表重新排序
重新排序 反转链表预期实现思路解题过程code力扣代码核心代码完整代码 总结 删除链表中间节点代码解惑 链表重新排序题目描述解题思路解题过程复杂度代码力扣代码完整代码 反转链表 预期实现 思路 你选用何种方法解题? 我选用了迭代法来反转链表。这是一种经典且高…...
e2studio开发RA2E1(9)----定时器GPT配置输入捕获
e2studio开发RA2E1.9--定时器GPT配置输入捕获 概述视频教学样品申请硬件准备参考程序源码下载选择计时器时钟源UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_uart_callback ()printf输出重定向到串口定时器输入捕获配…...
qt使用MQTT协议连接阿里云demo
qt使用Mqtt协议连接阿里云。 在配置好qt关于MQTT的环境之后,主要就是根据MQTT的连接参数进行连接即可。 环境配置推荐链接QT编译并部署QtMqtt相关环境跑测demo【超详细教程】_mqtt qt开发教程-CSDN博客 连接核心代码,主要就是根据阿里云的MQTT相关参数进行配置实现连…...
Python分享20个Excel自动化脚本
在数据处理和分析的过程中,Excel文件是我们日常工作中常见的格式。通过Python,我们可以实现对Excel文件的各种自动化操作,提高工作效率。 本文将分享20个实用的Excel自动化脚本,以帮助新手小白更轻松地掌握这些技能。 1. Excel单…...
DNN(深度神经网络)近似 Lyapunov 函数
import torch import torch.nn as nn import torch.optim as optim import matplotlib.pyplot as plt # from torchviz import make_dot import torchviz# 1. Lyapunov 函数近似器(MLP 结构) class LyapunovNet(nn.Module):def __init__(self, input_dim…...
什么是数据库代理
数据库代理(DB Proxy)是一种位于应用程序和数据库服务器之间的中间件,充当两者之间的“中间人”。它的核心目标是优化数据库访问、提升性能、增强安全性,并简化数据库架构的复杂度,尤其在高并发、分布式或云环境中应用…...
深入浅出 DeepSeek V2 高效的MoE语言模型
今天,我们来聊聊 DeepSeek V2 高效的 MoE 语言模型,带大家一起深入理解这篇论文的精髓,同时,告诉大家如何将这些概念应用到实际中。 🌟 什么是 MoE?——Mixture of Experts(专家混合模型&#x…...
【创建模式-单例模式(Singleton Pattern)】
赐萧瑀 实现方案饿汉模式懒汉式(非线程安全)懒汉模式(线程安全)双重检查锁定静态内部类 攻击方式序列化攻击反射攻击 枚举(最佳实践)枚举是一种类 唐 李世民 疾风知劲草,板荡识诚臣。 勇夫安识义,智者必怀仁…...
计算机毕业设计Python+Vue.js游戏推荐系统 Steam游戏推荐系统 Django Flask 游 戏可视化 游戏数据分析 游戏大数据 爬虫
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
6. 【Vue实战--孢子记账--Web 版开发】-- 主币种设置
从这篇文章开始我们将一起实现孢子记账的功能,这篇文章实现主币种设置。这个功能比较简单,因此我们从这个功能开始做。 一、功能 根据项目前期的需求调研,用户需要在设置主币种的时候查看汇率信息(别问为什么有这么个需求&#…...
RabbitMQ深度探索:前置知识
消息中间件: 消息中间件基于队列模式实现异步 / 同步传输数据作用:可以实现支撑高并发、异步解耦、流量削峰、降低耦合 传统的 HTTP 请求存在的缺点: HTTP 请求基于响应的模型,在高并发的情况下,客户端发送大量的请求…...
【文件上传、秒传、分片上传、断点续传、重传】
文章目录 获取文件对象文件上传(秒传、分片上传、断点续传、重传)优化 获取文件对象 input标签的onchange方法接收到的参数就是用户上传的所有文件 <html lang"en"><head><title>文件上传</title><style>#inp…...
设计模式Python版 组合模式
文章目录 前言一、组合模式二、组合模式实现方式三、组合模式示例四、组合模式在Django中的应用 前言 GOF设计模式分三大类: 创建型模式:关注对象的创建过程,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式…...
python开发:爬虫示例——GET和POST请求处理
一、Get请求 import json import requests#输入示例:urlhttps://www.baidu.com #RequestHeader:F12标头-请求标头-原始-复制到这(忽略第一句) def GetRequest(url,RequestHeader""):try:dic{}RequestHeaderList RequestHeader.s…...
【3分钟极速部署】在本地快速部署deepseek
第一步,找到网站,下载: 首先找到Ollama , 根据自己的电脑下载对应的版本 。 我个人用的是Windows 我就先尝试用Windows版本了 ,文件不是很大,下载也比较的快 第二部就是安装了 : 安装完成后提示…...
【归属地】批量号码归属地查询按城市高速的分流,基于WPF的解决方案
在现代商业活动中,企业为了提高营销效果和资源利用效率,需要针对不同地区的市场特点开展精准营销。通过批量号码归属地查询并按城市分流,可以为企业的营销决策提供有力支持。 短信营销:一家连锁餐饮企业计划开展促销活动…...
大数据sql查询速度慢有哪些原因
1.索引问题 可能缺少索引,也有可能是索引不生效 2.连接数配置:连接数过少/连接池比较小 连接数过 3.sql本身有问题,响应比较慢,比如多表 4.数据量比较大 -这种最好采用分表设计 或分批查询 5.缓存池大小 可能是缓存问题ÿ…...
安卓路由与aop 以及 Router-api
安卓路由(Android Router)和AOP(面向切面编程)是两个在Android开发中常用的概念。下面我将详细讲解这两个概念及其在Android开发中的应用。 一、安卓路由 安卓路由主要用于在应用程序中管理不同组件之间的导航和通信。它可以简化…...
游戏引擎学习第89天
回顾 由于一直没有渲染器,终于决定开始动手做一个渲染器,虽然开始时并不确定该如何进行,但一旦开始做,发现这其实是正确的决定。因此,接下来可能会花一到两周的时间来编写渲染器,甚至可能更长时间…...
备战蓝桥杯-洛谷
今天打算写一些洛谷上面的题目 P10904 [蓝桥杯 2024 省 C] 挖矿 https://www.luogu.com.cn/problem/P10904 看了大佬写的题解才写出来这道题的:题解:P10904 [蓝桥杯 2024 省 C] 挖矿 - 洛谷专栏 思路: 这是一道贪心的题目,用…...
动手学图神经网络(9):利用图神经网络进行节点分类 WeightsBiases
利用图神经网络进行节点分类Weights&Biases 引言 在本篇博客中,将深入探讨如何使用图神经网络(GNNs)来完成节点分类任务。以 Cora 数据集为例,该数据集是一个引用网络,节点代表文档,推断每个文档的类别。同时,使用 Weights & Biases(W&B)来跟踪实验过程和…...
如何在 FastAPI 中使用本地资源自定义 Swagger UI
要自定义 FastAPI 中的 Swagger UI,且使用本地资源来代替 CDN。只是需要稍微修改一下。 修改后的代码: 步骤: 挂载本地静态文件目录:我们将本地的 Swagger UI 资源文件(如 .js, .css, favicon.png 等)放…...
Swift 进阶:Observation 框架中可观察(@Observable)对象的高级操作(上)
概述 在 WWDC 24 中苹果推出了全新的 Observation 框架,借助于它我们可以更加细粒度的监听可观察(@Observable)对象 。同时,SwiftUI 自身也与时偕行开始全面支持 @Observable 对象的“嵌入”。 然而在这里,我们却另辟蹊径来介绍 @Observable 对象另外一些“鲜为人知”的故…...
aws(学习笔记第二十七课) 使用aws API Gateway+lambda体验REST API
aws(学习笔记第二十七课) 使用aws API Gatewaylambda体验REST API 学习内容: 使用aws API Gatewaylambda 1. 使用aws API Gatewaylambda 作成概要 使用api gateway定义REST API,之后再接收到了http request之后,redirect到lambda进行执行。…...
UE学习日志#23 C++笔记#9 编码风格
注:此文章为学习笔记,只记录个人不熟悉或备忘的内容 1 为代码编写文档 1.1 使用注释的原因 1.说明用途的注释 应该注释的信息:输入,输出含义,参数的类型含义,错误条件和处理,预期用途&#x…...
vue2-vue自定义指令
文章目录 vue2-vue自定义指令1. 什么是指令2. 自定义指令2.1 全局注册2.2 局部注册 3. 自定义指令的钩子函数4. 钩子函数的参数4. 用例 vue2-vue自定义指令 1. 什么是指令 在vue中提供了一套为数据驱动视图更为方便的操作,这些操作被称为指令系统我们平时使用的v-…...
[250202] DocumentDB 开源发布:基于 PostgreSQL 的文档数据库新选择 | Jekyll 4.4.0 发布
目录 DocumentDB 开源发布:基于 PostgreSQL 的文档数据库新选择DocumentDB 的使命DocumentDB 的架构 Jekyll 4.4.0 版本发布🆕 新特性与改进 DocumentDB 开源发布:基于 PostgreSQL 的文档数据库新选择 微软近日宣布开源 DocumentDBÿ…...
matplotlib绘制三维曲面图时遇到的问题及解决方法
在科学计算和数据可视化中,三维曲面图是非常有用的工具,可以直观地展示数据的三维分布和关系。Matplotlib是Python中广泛使用的数据可视化库之一,提供了强大的三维绘图功能。然而,在实际使用过程中,用户可能会遇到各种…...
【数据结构】(4) 线性表 List
一、什么是线性表 线性表就是 n 个相同类型元素的有限序列,每一个元素只有一个前驱和后继(除了第一个和最后一个元素)。 数据结构中,常见的线性表有:顺序表、链表、栈、队列。 二、什么是 List List 是 Java 中的线性…...
简单React项目从0到1
文章目录 项目搭建基于CRA创建项目调整项目目录结构 使用scss预处理器组件库antd使用配置基础路由配置别名路径路径编译配置VsCode提示配置 基本结构搭建表单校验实现获取登录表单数据封装request工具模块使用Redux管理token安装Redux相关工具包配置Redux 实现登录逻辑token持久…...
IM 即时通讯系统-46-OpenIM 提供了专为开发者设计的开源即时通讯解决方案
IM 开源系列 IM 即时通讯系统-41-开源 野火IM 专注于即时通讯实时音视频技术,提供优质可控的IMRTC能力 IM 即时通讯系统-42-基于netty实现的IM服务端,提供客户端jar包,可集成自己的登录系统 IM 即时通讯系统-43-简单的仿QQ聊天安卓APP IM 即时通讯系统-44-仿QQ即…...
MFC 学习笔记目录
序章 MFC学习笔记专栏开篇语-CSDN博客 下载与安装 VS2010 下载与安装 VS2019...
一文讲解Java中的ArrayList和LinkedList
ArrayList和LinkedList有什么区别? ArrayList 是基于数组实现的,LinkedList 是基于链表实现的。 二者用途有什么不同? 多数情况下,ArrayList更利于查找,LinkedList更利于增删 由于 ArrayList 是基于数组实现的&#…...
【Linux系统】线程:线程的优点 / 缺点 / 超线程技术 / 异常 / 用途
1、线程的优点 创建和删除线程代价较小 创建一个新线程的代价要比创建一个新进程小得多,删除代价也小。这种说法主要基于以下几个方面: (1)资源共享 内存空间:每个进程都有自己独立的内存空间,包括代码段…...
HTML 复习
文章目录 路径问题标题标签段落标签换行标签列表标签<ol> 有序列表<ul> 无序标签标签嵌套 超链接标签多媒体标签<img> 图片标签<audio> 音频标签<video> 视频标签 表格标签<colspan> 跨行<rowspan> 跨列组合使用 表单标签基本表单标…...
网络爬虫学习:借助DeepSeek完善爬虫软件,增加停止任务功能
一、引言 我从24年11月份开始学习网络爬虫应用开发,经过2个来月的努力,终于完成了开发一款网络爬虫软件的学习目标。这几天对本次学习及应用开发进行一下回顾总结。前面已经发布了两篇日志: 网络爬虫学习:应用selenium从搜*狐搜…...
【数据结构】单向链表(真正的零基础)
放弃眼高手低,你真正投入学习,会因为找到一个新方法产生成就感,学习不仅是片面的记单词、学高数......只要是提升自己的过程,探索到了未知,就是学习。 目录 一.链表的理解 二.链表的分类(重点理解…...
8. k8s二进制集群之Kubectl部署
创建kubectl证书请求文件生成admin证书文件复制admin证书到指定目录生成kubeconfig配置文件接下来完成kubectl配置文件的角色绑定【扩展】kubectl命令补全操作继续上一篇文章《k8s二进制集群之Kube ApiServer部署》下面介绍一下k8s中的命令行管理工具kubectl。 通过kubectl可以…...
115,【7】 攻防世界 web fileinclude
进入靶场 试着访问了几个文件,都没得到信息,f12看看源码 还真有 <?php // 检查是否开启了错误显示功能 // ini_get 函数用于获取 PHP 配置选项的值,这里检查 display_errors 选项是否开启 if( !ini_get(display_errors) ) {// 如果错误…...
RabbitMQ 从入门到精通:从工作模式到集群部署实战(二)
接上篇:《RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)》 链接 文章目录 4.安装RabbitMQ Messaging Topology Operator 裸金属环境部署RabbitMQ部署单实例部署集群 4.安装RabbitMQ Messaging Topology Operator 使用 cer…...
【MySQL】MySQL经典面试题深度解析
文章目录 一、MySQL与C的深度结合1.1 为什么C项目需要MySQL?1.2 典型应用场景 二、基础概念面试题精讲2.1 存储引擎对比2.2 索引原理 三、C专项面试题解析3.1 连接池实现3.2 预处理语句3.3 批量操作优化 四、高级应用面试题剖析4.1 事务隔离级别4.2 锁机制详解4.3 查…...
小程序-基础加强
前言 这一节把基础加强讲完 1. 导入需要用到的小程序项目 2. 初步安装和使用vant组件库 这里还可以扫描二维码 其中步骤四没什么用 右键选择最后一个 在开始之前,我们的项目根目录得有package.json 没有的话,我们就初始化一个 但是我们没有npm这个…...
vscode+CMake+Debug实现 及权限不足等诸多问题汇总
环境说明 有空再补充 直接贴两个json tasks.json {"version": "2.0.0","tasks": [{"label": "cmake","type": "shell","command": "cmake","args": ["../"…...
零基础Vue入门6——Vue router
本节重点: 路由定义路由跳转 前面几节学习的都是单页面的功能(都在专栏里面https://blog.csdn.net/zhanggongzichu/category_12883540.html),涉及到项目研发都是有很多页面的,这里就需要用到路由(vue route…...
【疑海破局】一个注解引发的线上事故
【疑海破局】一个注解引发的线上事故 1、问题背景 在不久前一个阳光明媚的上午,我的思绪正在代码中游走、双手正在键盘上飞舞。突然,公司内部通讯工具上,我被拉进了一个临时工作群,只见群中产品、运营、运维、测试等关键人员全部严阵以待,我就知道大的可能要来了。果不其…...
C语言:函数栈帧的创建和销毁
目录 1.什么是函数栈帧2.理解函数栈帧能解决什么问题3.函数栈帧的创建和销毁的过程解析3.1 什么是栈3.2 认识相关寄存器和汇编指令3.3 解析函数栈帧的创建和销毁过程3.3.1 准备环境3.3.2 函数的调用堆栈3.3.3 转到反汇编3.3.4 函数栈帧的创建和销毁 1.什么是函数栈帧 在写C语言…...
IDEA启动项目慢问题处理
IDEA启动项目慢问题处理 一、问题现象二、问题排查排查点1:idea内存排查点2:应用内存排查点3:shorten command lineclasspath filejar manifest 排查点4:jstack排查 三、问题定位 一、问题现象 多模块工程,启动模块为…...
Denavit-Hartenberg DH MDH坐标系
Denavit-Hartenberg坐标系及其规则详解 6轴协作机器人的MDH模型详细图_6轴mdh-CSDN博客 N轴机械臂的MDH正向建模,及python算法_mdh建模-CSDN博客 运动学3-----正向运动学 | 鱼香ROS 机器人学:MDH建模 - 哆啦美 - 博客园 机械臂学习——标准DH法和改进MDH…...
Unity 快速入门 1 - 界面操作
本项目将快速介绍 Unity 6的基本操作和功能,下载附件的项目,解压到硬盘,例如 D:\Unity Projects\, 注意整个文件路径中只有英文、空格或数字,不要有中文或其他特殊符合。 1. 打开Unity Hub,点击右上角的 O…...
美国网络司令部军事网络指挥框架战略转型与挑战分析
文章目录 前言一、框架核心内容:从分散到集中,构建标准化作战体系二、指挥体系重构:权责明晰与集中化管控三、风险管理创新:从被动防御到主动备战四、对美军网络作战的影响总结 前言 2024年9月,美国网络司令部发布《国…...