一、链表
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,节点计数为0
ll->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,使其也指向链表的头节点
// 检查链表是否为空,如果为空,直接返回NULL
if (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 != NULL while (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;
}