头插数据
1.申请新节点
newnode
2.新节点指向原来的头节点
newnode->next = *pphead
3.改变头节点
*pphead = newnode
// 头插数据 void SListPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySLTNode(x); newnode->next = *pphead; *pphead = newnode; }
尾插数据
1.申请新节点
newnode
2.当链表为空时,此时的尾插数据为头插数据。需要改变头节点
*pphead = newnode
3.当链表不为空时,需要找到尾结点tail,原来的尾结点指向新节点
tail->next = newnode
,这样节点newnode
就成为新的尾结点了
// 尾插数据 void SListPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySLTNode(x); // 链表为空 if (*pphead == NULL) { *pphead = newnode; } else { // 找尾节点 SLTNode* tail = *pphead; while (tail->next) { tail = tail->next; } tail->next = newnode; } }
尾删数据
1.对
*pphead
进行断言,判断链表是否为空链表2.当
(*pphead)->next == NULL
时,链表只有一个节点。此时尾删数据为头删数据。需要先释放节点free(*pphead)
,再改变头节点*pphead = NULL
3.当
(*pphead)->next != NULL
时,链表有多个节点。此时需要找到尾结点tail
和尾结点的上一个节点prev
,先释放尾结点free(tail)
,再让尾结点的上一个节点成为新的尾结点prev->next = NULL
// 尾删数据 void SListPopBack(SLTNode** pphead) { assert(pphead); assert(*pphead);// 判断是否为空链表 // 只有一个节点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { SLTNode* tail = *pphead; SLTNode* prev = NULL; while (tail->next) { prev = tail; tail = tail->next; } free(tail); tail = NULL; prev->next = NULL; //SLTNode* tail = *pphead; //while (tail->next->next) //{ // tail = tail->next; //} //free(tail->next); //tail->next = NULL; } }
头删数据
1.判断链表是否为空链表
2.保存新的头节点
newHead = (*pphead)->next
3.是否旧的头节点free(*pphead)
4.改变头节点*pphead = newHead
// 头删数据 void SListPopFront(SLTNode** pphead) { assert(pphead); assert(*pphead); // 判断是否为空链表 SLTNode* newHead = (*pphead)->next; // 新的头节点 free(*pphead); // 释放旧的头节点 *pphead = newHead; }
查找数据
利用
while
循环遍历链表,如果有节点的数据等于要查找的数据x
,就返回节点的地址cur
;如果在链表中找不到x
,就返回空指针NULL
。
// 查找数据 SLTNode* SListFind(SLTNode* phead, SLTDataType x) { SLTNode* cur = phead; // 循环遍历链表 while (cur) { if (cur->data == x) { return cur; // 找到了 } cur = cur->next; // 继续往后找 } return NULL; // 没找到 }
在pos
位置之前插入数据
1.对
pos
位置进行断言2.当
*pphead == pos
时,此时的插入数据为头插数据3.当*pphead != pos时,利用while循环pos位置的前一个位置prev,申请新节点newnode,插入数据prev->next = newnode, newnode->next = pos
4.注意:需要在while循环中对prev进行断言assert(prev)。如果prev为空,那么就说明pos不在链表中,参数pos传错了
// 在pos位置之前插入 void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pphead); assert(pos); // 头插 if (*pphead == pos) { SListPushFront(pphead, x); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; assert(prev); // 暴力检查,如果prev为空,那么就说明pos不在链表中,参数pos传错了 } SLTNode* newnode = BuySLTNode(x); prev->next = newnode; newnode->next = pos; } }
在pos
位置之后插入数据
申请新节点
newnode
,插入数据newnode->next = pos->next, pos->next = newnode
(特别要注意修改的顺序)。
// 在pos位置之后插入 void SListInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = BuySLTNode(x); newnode->next = pos->next; pos->next = newnode; }
删除pos
位置的数据
1.当
*pphead == pos
时,此时的删除数据为头删数据,可以调用头删函数SListPopFront
2.当*pphead != pos时,利用while循环找到pos位置的前一个位置prev。删除数据prev->next = pos->next, free(pos)
3.注意:需要在while循环中对prev进行断言assert(prev)。如果prev为空,那么就说明pos不在链表中,参数pos传错了
// 删除pos位置的数据 void SListErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(pos); // 头删数据 if (*pphead == pos) { SListPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; assert(prev); // 暴力检查,如果prev为空,那么就说明pos不在链表中,参数pos传错了 } prev->next = pos->next; free(pos); } }
删除pos
位置之后的数据
1.当
pos->next == NULL
时,直接返回return
2.保存
pos
位置的下一个位置next
,删除数据pos->next = next->next, free(next)
// 删除pos位置之后的数据 void SListEraseAfter(SLTNode* pos) { assert(pos); // pos位置执行NULL if (pos->next == NULL) { return; } else { SLTNode* next = pos->next; pos->next = next->next; free(next); } }
Test.c
Test.c
源文件主要负责测试函数的功能实现是否正确,有没有BUG
。需要提醒大家的一件事,学习数据结构不太需要写菜单,没什么必要。重点的是掌握该结构如何实现增删查改的功能。
#include "SList.h" #include "SList.h" // 测试头插、头删 void TestSList1() { SLTNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 4); SListPrint(plist); // 4->3->2->1->NULL SListPopFront(&plist); SListPrint(plist); // 3->2->1->NULL SListPopFront(&plist); SListPrint(plist); // 2->1->NULL SListPopFront(&plist); SListPrint(plist); // 1->NULL SListPopFront(&plist); SListPrint(plist); // NULL SListDestory(&plist); } // 测试尾插、尾删 void TestSList2() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); // 1->2->3->4->NULL SListPopBack(&plist); SListPrint(plist); // 1->2->3->NULL SListPopBack(&plist); SListPrint(plist); // 1->2->NULL SListPopBack(&plist); SListPrint(plist); // 1->NULL SListPopBack(&plist); SListPrint(plist); //NULL SListDestory(&plist); } // 测试查找、插入 void TestSList3() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPushBack(&plist, 2); SListPushBack(&plist, 2); SListPrint(plist); // 1->2->3->4->2->2->NULL SLTNode* pos = SListFind(plist, 2); int i = 1; while (pos) { printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data); pos = SListFind(pos->next, 2); } pos = SListFind(plist, 3); if (pos) { pos->data *= 10; // 修改 printf("该数据已修改为原来的10倍\n"); } else { printf("链表中没有该数据\n"); } SListPrint(plist); // 1->2->30->4->2->2->NULL pos = SListFind(plist, 2); if (pos) { SListInsert(&plist, pos, 20); } SListPrint(plist); // 1->20->2->30->4->2->2->NULL pos = SListFind(plist, 1); if (pos) { SListInsert(&plist, pos, 10); } SListPrint(plist); // 10->1->20->2->30->4->2->2->NULL SListDestory(&plist); } // 测试删除 void TestSList4() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); // 1->2->3->4->NULL SLTNode* pos = SListFind(plist, 4); SListInsertAfter(pos, 40); SListPrint(plist); //1->2->3->4->40->NULL pos = SListFind(plist, 3); SListErase(&plist, pos); SListPrint(plist); // 1->2->4->40->NULL pos = SListFind(plist, 4); SListEraseAfter(pos); SListPrint(plist); // 1->2->4->NULL SListDestory(&plist); } int main() { //TestSList1(); //TestSList2(); //TestSList3(); TestSList4(); return 0; }
👉单向链表的问题和思考👈
为了解决动态顺序表的问题,我们采取了单向链表的结构。但是,单向链表这种结构只适合头插和头删(时间复杂度为
O(1)
)。能够真正实现任意位置的高效插入删除,还需要学习双向链表。这种结构将会在下一篇博客中讲解,敬请期待。
要求时间复杂度为O(1)
,删除pos
位置的数据,能实现吗?
在上面的讲解中,删除pos
位置的数据,需要找到它的前一个位置prev
才能删除,那么这样时间复杂度就是O(N)
了。那有没有一种方式能做到时间复杂度为O(1)
呢?其实是有的,请看下图:
要求时间复杂度为O(1)
,在pos
位置之前插入数据,能实现吗?
在上面的讲解中,在pos位置之前插入数据,需要找到pos位置的前一个位置prev才能将新节点newnode插入到链表中。这样的解法时间复杂度也是O(N)。那有没有一种方式能做到时间复杂度为O(1)呢?其实也是有的,请看下图:
👉总结👈
为了解决顺序表插入删除时间复杂度高、扩容的问题,我们引入了单向链表的结构并实现其函数接口。不过单向链表只有头插头删时间复杂度为O(1),其他位置的插入删除都是O(N)。想要实现高效地插入删除,就要学习双向链表的结构了,这个会在下一篇博客讲解。以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!💖💝❣️