嵌入式学习笔记DAY20(链表,gdb调试)
一、链表
1. 练习
- 单链表——尾插
int InsertTailLinkList(LinkList *ll, DATATYPE *data)
{// 创建新节点并分配内存LinkNode* newnode1 = malloc(sizeof(LinkNode));// 内存分配失败检查if (newnode1 == NULL){fprintf(stderr, "InsertTailLinkList malloc failed\n");return 1; // 返回错误码1表示内存分配失败}// 复制数据到新节点memcpy(&newnode1->data, data, sizeof(DATATYPE));newnode1->next = NULL; // 新节点的next指针置为NULL,作为尾节点// 处理链表为空的特殊情况if(IsEmptyLinkList(ll)){ll->head = newnode1; // 空链表时,新节点直接成为头节点}else{// 链表非空时,寻找当前尾节点LinkNode* tail = ll->head;// 循环遍历直到找到尾节点(next为NULL的节点)while(tail->next) {tail = tail->next;}// 将新节点插入到尾节点之后tail->next = newnode1;}ll->clen++; // 更新链表长度return 0; // 返回0表示插入成功
}
- 链表——按位置插入
int InsertAtPosition(LinkList *ll, int pos, DATATYPE *data) {if (ll == NULL || pos < 0 || pos > ll->clen) {return 1; // pos的位置应该在合理范围之内}if (pos == 0) {return InsertHeadLinkList(ll, data); //如果pos为0,那么直接掉迎头差}//创建新节点,为新节点分配内存,使用memcpy将data复制到新节点的data成员中LinkNode *newnode = malloc(sizeof(LinkNode));if (newnode == NULL) {fprintf(stderr, "InsertAtPosition malloc failed\n");return 1;}memcpy(&newnode->data, data, sizeof(DATATYPE));//遍历到第pos-1个节点(即想要插入位置的前面一个节点)LinkNode *prev = ll->head; //for (int i = 0; i < pos - 1; i++) {prev = prev->next;}newnode->next = prev->next; //新节点的 next 指向 prev 的下一个节点prev->next = newnode; // prev 的 next 指向新节点ll->clen++;return 0;
}
- 修改链表
int ModifyLinkList(LinkList *ll, char *name, DATATYPE *data)
{DATATYPE *tmp = FindLinkList(ll, name);if (NULL == tmp) {return 1;}memcpy(tmp, data, sizeof(DATATYPE));return 0;
}
- 链表的销毁
int DestroyLinkList(LinkList *ll)
{// 检查链表是否为空或已被销毁if (ll == NULL || ll->head == NULL) {return 0; // 空链表无需处理,直接返回成功}LinkNode *tmp = ll->head; // 初始化临时指针,指向链表头节点LinkNode *next; // 用于保存下一个节点的指针// 遍历链表,逐个释放节点while (tmp != NULL) {next = tmp->next; // 保存当前节点的下一个节点指针free(tmp); // 释放当前节点的内存tmp = next; // 移动临时指针到下一个节点}// 链表销毁后,重置链表头指针为NULL,节点计数为0ll->head = NULL;ll->clen = 0;return 0; // 返回成功状态码
}
2. gdb调试
一般调试步骤与命令:
1、gcc -g *.c 加上调试选项(eg:gcc -g main.c linklist.c)
2、gdb a.out(调试可执行文件,eg:gdb ./a.out)
3、r 运行(出现页面然后进行输入)
4、b fun.c:36 设置断点,运行到这个位置,程序自动暂停
- b :100 默认停在main.c的100行;
- b fun.c : 36 停在fun.c的36行
- b 函数名 eg: b InserPosLinkList)
5、n 执行下一步 步过(如果是函数,直接调用结束)
s 步入自定义函数(系统函数不入)
6、使用p命令,查看变量或指针等数据
- p 变量: 显示变量值 eg:p len
- p 指针: 看地址 eg:p *data
7、q命令 退出(y)
3. 练习
- 查找链表中间节点
DATATYPE *FindMiddleLinklist(LinkList *ll) {LinkNode *slow = ll->head; //初始化慢指针slow,使其指向链表的头节点LinkNode *fast = ll->head; //初始化快指针fast,使其也指向链表的头节点// 检查链表是否为空,如果为空,直接返回NULLif (NULL == ll) {return NULL;}// 使用快慢指针法寻找中间节点// 当快指针fast不为空,且fast的下一个节点和下下个节点都不为空时,循环继续while (fast != NULL && fast->next->next != NULL) {fast = fast->next->next; // 快指针每次移动两步slow = slow->next; // 慢指针每次移动一步}// 循环结束后,此时慢指针slow指向的节点即为中间节点(或中间偏右节点,取决于链表长度奇偶性)// 返回中间节点的数据指针return &slow->data;
}
如何返回偏前的中间节点?
修改循环条件,让快指针提前一步终止:
LinkNode* FindMiddleLinklist(LinkList* ll) {if (ll == NULL || ll->head == NULL) return NULL;LinkNode* slow = ll->head;LinkNode* fast = ll->head;// 修改循环条件:fast->next->next != NULLwhile (fast != NULL && fast->next != NULL && fast->next->next != NULL) {slow = slow->next;fast = fast->next->next;}return slow; }
- 找倒数第k个节点
LinkNode* FindKthFromEnd(LinkList* ll, int k)
{// 处理空链表以及无效的k值if (ll == NULL || ll->head == NULL || k <= 0) return NULL;// 检查k是否超过链表长度if (k > ll->clen){fprintf(stderr, "Error: k (%d) exceeds list length (%d)\n", k, ll->clen);return NULL;}LinkNode* fast = ll->head;LinkNode* slow = ll->head;// 1.快指针先走k步for (int i = 0; i < k; i++) {fast = fast->next;}// 2.快慢指针同步移动while (fast != NULL) {slow = slow->next;fast = fast->next;}return slow; // 返回倒数第k个节点
}
算法核心思想
假设链表总长度为 n,倒数第 k 个节点的正向位置是 n-k+1(从 1 开始计数)。例如:
链表
10 → 20 → 30 → 40 → 50
(n=5)倒数第 2 个节点是
40
,其正向位置是5-2+1=4
双指针法的巧妙之处在于:
让快指针(
fast
)先走 k 步,此时快指针距离链表尾部还有 n-k 步。然后让慢指针(
slow
)和快指针同步移动。当快指针到达尾部(
NULL
)时,慢指针恰好走了 n-k 步,此时慢指针的位置就是 n-k+1(倒数第 k 个节点)。
- 链表的逆序
int RevertLinkList(LinkList *ll) {LinkNode *prev = NULL;LinkNode *tmp = ll->head;LinkNode *next = tmp->next;int len = GetSizeLinkList(ll);if (len < 2) {return 1;}while (1) {tmp->next = prev;prev = tmp;tmp = next;if (NULL == tmp)break;next = next->next;}ll->head = prev;return 0;
}