完整代码:链接:https://pan.baidu.com/s/1-FDdb47ehW8Zw1ds0yPjMw?pwd=jyhh
提取码:jyhh
1.单链表实现思路
单链表实现和顺序表差不多,创建一个结构体,利用结构体实现头插、尾插、头删、尾删、查找、任意位置插入或删除等的接口函数
2.单链表实现过程
2.1尾插
尾插实现基本思路是找到尾节点,将尾节点的next指针指向要插入的节点,如下图2-1所示,要注意的是要考虑链表为空的情况,所以第一步是先创建一个新节点,具体实现如下所示:
图2-1 头删示意图
void SListPushBack(SLTNode** pphead, SLDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while(tail->next != NULL)
{
tail = tail->next;
}tail->next = newnode;
}
}
2.2头插
头插就是先创建一个新节点,将新节点的next指针指向链表的头节点即可,具体实现如下所示:
图2-2 头插示意图
void SListPushFront(SLTNode** pphead, SLDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->next = *pphead;
*pphead = newnode;
}
2.3尾删
尾删实现同样要先找到尾节点,但这时我们还需要另外一个指针prev来记录倒数第二个节点,如下图2-3所示,以避免在删除尾节点的时候,出现野指针的情况;还要考虑的一点是如果链表只有一个节点,那么我们无法将该节点置为空,所以我们需要单独处理它,具体实现如下所示:
图2-3 尾删示意图
void SListPopBack(SLTNode** pphead)
{
assert(*pphead != NULL);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
SLTNode* prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next= NULL;
}
}
2.4头删
头删直接创建一个节点指向头结点的下一个节点,释放头节点再将新头节点的值赋给头节点,如下图2-4所示,注意此时链表不能为空,具体实现如下:
图2-4 头删示意图
void SListPopFront(SLTNode** pphead)
{
assert(*pphead != NULL);
SLTNode* head = (*pphead)->next;
free(*pphead);
(*pphead)= head;
}
2.5查找
查找就是遍历整个链表,如果找到了要找的值就返回该值对应的地址,如果没有找到,就返回空;另外查找还可以实现修改的功能,就是在查找到该值的时候利用返回的地址修改值,具体实现如下所示:
SLTNode* SListFind(SLTNode* phead, SLDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
修改值:
SLTNode* pos = SListFind(plist, 2);
if (pos)
{
SListInsert(&plist, pos, 20);
}
2.6任意位置的插入
2.6.1在任意位置之前插入
pos位置前插入时需要设置两个变量,一个变量prev存pos前一个位置的地址,另一个变量cur存pos的地址,让prev的next指向要插入的新变量newnode的地址,再将newnode的next指向cur的地址就完成插入,思路实现如下图2-5所示;但要注意的是当pos节点为头结点时,prev的值无法设置,所以这种情况要单拎出来解决,详细实现如下所示:
图2-5 任意位置之前插入思路图
void SListInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
assert(pphead);
SLTNode* newnode = BuyListNode(x);
if (pos == *pphead)
{
newnode->next = pos;
*pphead = newnode;
}
else
{
//找到pos前一个位置
SLTNode* PosPrev = *pphead;
while (PosPrev->next != pos)
{
PosPrev = PosPrev->next;
}
PosPrev->next = newnode;
newnode->next = pos;
}
}
2.6.2在任意位置之后插入
在pos节点后插入与在之前插入相比就少了考虑头结点的情况,直接插入就行,具体实现如下:
void SListInsertAfter(SLTNode* pos, SLDataType x)
{
SLTNode* newnode = BuyListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.7任意位置的删除
2.7.1任意位置的删除
任意位置的删除要创建一个变量prev存pos前一个位置的地址,将prev的next指向pos的next释放pos当前节点即可,思路实现如下图2-6所示;这里还要注意删除头结点的情况,就直接将头节点删除,赋予新的头节点即可,具体实现如下所示:
图2-6 任意位置删除思路图
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
if (*pphead == pos)
{
*pphead = pos->next;
free(pos);
}
else
{
SLTNode* prev = *pphead;
while(prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
2.7.2删除任意位置之后的节点
删除pos位置之后的节点,就不用考虑头结点的问题,直接将pos的next指向变量next的next,最后将next释放即可,思路图如下图2-7所示,代码具体实现如下所示:
图2-7 删除pos后一个结点的思路图
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
2.8销毁单链表
销毁单链表要先将下一个节点的地址给保留,释放当前节点即可,具体实现如下所示:
void SListDestroy(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
SLTNode* next = cur->next;
while (cur)
{
next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
3.总结
本次单链表的代码实现过程中,我深刻体会到了assert的精髓,因为我传参的时候传错了,找问题一直找不出来,调试了好久最后发现本来要传的是&plist,结果传成了plist,这个如果要是加一个断言,直接能报错,assert真的很有用!
代码实现方面就是一定要考虑全面,头尾结点时容易忽略的情况要注意,最重要的是要谨记单链表每一个节点里边都有下一个结点的地址,所以处理的时候一定要先把下一个结点的地址给保存好再进行操作~~~~~