【编织时空二:探究顺序表与链表的数据之旅】(上):https://developer.aliyun.com/article/1424871
有问题,我们发现将 tail
置为空后,但是3结点位置的 next
并没有置为空,那么就会出现野指针的问题。解决这个问题的关键就是将3结点位置的 next
置为空.
方法一:创建新结点,让这个结点的位置的 next
等于 tail
。
// 单链表的尾删 void SListPopBack(SLTNode** pplist) { assert(pplist); //1.链表为空 assert(*pplist != NULL); //2.链表只剩下一个元素 if ((*pplist)->next == NULL) { free(*pplist); *pplist = NULL; } //3.链表有多个元素 else { SLTNode* tailPrev = NULL; SLTNode* tail = *pplist; while (tail->next!= NULL) { tailPrev = tail; tail = tail->next; } free(tail); tailPrev->next = NULL; } }
方法二:不创建新结点,使用 tail->next->next
找到3结点位置
// 单链表的尾删 void SListPopBack(SLTNode** pplist) { assert(pplist); //1.链表为空 assert(*pplist != NULL); //2.链表只剩下一个元素 if ((*pplist)->next == NULL) { free(*pplist); *pplist = NULL; } //3.链表有多个元素 else { SLTNode* tail = *pplist; while (tail->next->next != NULL) { tail = tail->next; } free(tail->next); tail->next = NULL; } }
(5)、单链表头删:void SListPopFront(SLTNode** pplist)
单链表如何进行头删呢?头删需要找到头结点的 next ,将其 next 用一个新结点 newhead 保存起来,然后释放原来的头结点,再将 newhead 赋给 pplist 即可。
// 单链表头删 void SListPopFront(SLTNode** pplist) { assert(pplist); //空 assert(*pplist); //非空 SLTNode* newhead = (*pplist)->next; free(*pplist); *pplist = newhead; }
(6)、单链表查找:SLTNode* SListFind(SLTNode* plist, SLTDataType x)
要查找某个数字的在链表中的位置,需要遍历链表,让 tail指向空的位置,这样链表中的每个数据都能被访问到,就能查找的数字的在链表中的位置。
// 单链表查找 SLTNode* SListFind(SLTNode* plist, SLTDataType x) { SLTNode* cur = plist; while (cur != NULL) { if (cur->data == x) return cur; cur = cur->next; } //没找到数据 return NULL; }
(7)、单链表在pos位置之前插入x:void SListInsert(SLTNode** plist, SLTNode* pos, SLTDataType x)
我们这里设置plist为二级指针,因为pos位置可能会头插,需要改变头结点指向的结点。
// 单链表在pos位置之前插入x void SListInsert(SLTNode** plist, SLTNode* pos, SLTDataType x) { assert(plist); assert(pos); SLTNode* newnode = BuySListNode(x); //头插 if (pos == *plist) { newnode->next = *plist; *plist = newnode; } //中间插入,不可能尾插 else { SLTNode* posPrev = *plist; while (posPrev->next != pos) { posPrev = posPrev->next; } newnode->next = posPrev->next; posPrev->next = newnode; /*posPrev->next = newnode; newnode->next = pos;*/ } }
单链表在pos位置之前插入x,有两种情况,一种是头插,一种是在中间插入。头插可以复用之前的函数,但是注意传入的参数,中间插入的话首先要找到pos位置前一个结点posPrev,将posPrev位置的next指向要插入的结点,再将要插入的结点的next给到pos,即可完成连接。
(8)、单链表在pos位置之后插入x:void SListInsertAfter(SLTNode* pos, SLTDataType x)
在pos位置之后插入x不会出现头插的现象,所以这里我们不会改变plist,所以这个参数也就不用掺入了
// 单链表在pos位置之后插入x void SListInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = BuySListNode(x); newnode->next = pos->next; pos->next = newnode; }
(9)、删除pos位置的值:void SListErase(SLTNode* pos, SLTDataType x);
单链表删除pos位置的值,有两种情况,一种是头删,一种是在中间删除。头删可以复用之前的函数,但是注意传入的参数,中间删除的话首先要找到pos位置前一个结点posPrev,将posPrev位置的next指向pos位置的next,即可完成删除。
// 删除pos位置的值 void SListErase(SLTNode** plist, SLTNode* pos) { assert(plist); assert(pos); if (*plist == pos) { SLTNode* Next = pos->next; free(pos); *plist = Next; } else { SLTNode* posPrev = *plist; while (posPrev->next != pos) { posPrev = posPrev->next; } posPrev->next = pos->next; free(pos); } }
(10)、单链表删除pos位置之后的值:void SListEraseAfter(SLTNode* pos)
这个函数有个缺点:不能删头,同时pos为尾结点,那么此时删除pos位置之后的值就无意义
// 单链表删除pos位置之后的值 void SListEraseAfter(SLTNode* pos) { assert(pos); //删除pos位置之后的值就无意义 if (pos->next == NULL) exit(-1); SLTNode* posNext = pos->next; pos->next = posNext->next; free(posNext); }
这里我们为什么不写成pos->next = pos->next->next呢?为什么还要传教一个posNext结点呢?
因为如果我们写成pos->next = pos->next->next,那么删除的那个结点我们就丢失了,无法找到,就会早成内存泄露的问题。
(11)、单链表打印:void SListPrint(SLTNode* plist);
第一步:输出第一个节点的数据域,输出完毕后,让指针保存后一个节点的地址
第二步:输出移动地址对应的节点的数据域,输出完毕后,指针继续后移
第三步:以此类推,直到节点的指针域为NULL
// 单链表打印 void SListPrint(SLTNode* plist) { SLTNode* cur = plist; while (cur != NULL) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); }
(12)、单链表销毁:void SListDestroy(SLTNode** pplist);
单链表的销毁比较简单,只保存cur下一个位置next,然后将当前的cur结点释放,再将cur移动到下一个结点的位置next,依次遍历直至cur为空即可完成销毁。
void SListDestroy(SLTNode** pplist) { assert(pplist); SLTNode* cur = *pplist; SLTNode* next = NULL; while (cur) { next = cur->next; free(cur); cur = next; } *pplist = NULL; }