尾删函数
和头删同样,需要传二级指针,以及断言2次
尾删,找到尾节点很简单,但是删除尾节点后还需要把尾节点前一个结点的next指针赋为NULL,但是如何找到这个倒数第二个结点是个问题
这里我们有2种放法:
1.利用tail->next->next找,当tail->next->next==NULL时,就找到了新的尾结点
2.定义一个prev指针,让prev一直在tail指针的前面,当tail到达尾时,prev也自然是倒数第二个结点了
起始时:
逐渐向后移动:
最后:
这里我们使用第一种方法,接着我们还会发现一个问题:当只有一个节点时,cur->next已经为空了。cur->next->next就错了
所以还需分类运算当只有一个节点的情况
void SLTPophBack(SLTNode** pphead) { assert(pphead); assert(*pphead); if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { SLTNode* tail = *pphead; while (tail->next->next != NULL) { tail = tail->next; } free(tail->next); tail->next = NULL; } } 1
查找函数
遍历链表,如果找到则返回这个节点的地址,否则返回NULL
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) { SLTNode* pos = phead; while (pos != NULL) { if (pos->data == x) { return pos; } else { pos = pos->next; } } return NULL; }
pos位置前插入
这个pos是通过SLTFind寻找返回的节点地址,这个地址不会为空,所以可以断言判断一下
如果想在pos位置前插入,就需要直到pos前一个位置,所以这里就需要遍历寻找pos的前一个结点prev,然后将prev,newndoe和pos链接在一起
如果这个pos等于*pphead,就是在头节点前插入,也就是头插
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pphead); assert(pos); if (pos==*pphead) { SLTPushFront(pphead, x); } else { SLTNode* prev = *pphead; while (prev->next != pos)//想要插入pos前,就要知道pos前的节点,就需要遍历,所以单链表不适合在前面插入 { prev = prev->next; } SLTNode* newnode = SLTBuyNode(x); prev->next = newnode; newnode->next = pos; } }
因为在pos前插入,所以这个函数是不会做到尾插功能的
下面考虑一个问题:如果只给pos,不给头指针,怎么在pos前插入?
在pos后面插入,再交换pos和pos后面节点中的data值就做到了在pos前面插入
删除pos位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(pos); assert(*pphead); if (pos == *pphead) { SLTPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } }
只给pos,不给头指针,也可以删除pos位置节点:交换pos和pos->next的data值,保存pos->next->next的值为nex,然后删除pos->next,最后链接pos和nex
但是这种方法不适用尾删
pos位置后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = SLTBuyNode(x); newnode->next = pos->next; pos->next = newnode; }
因为在pos后面插入,所以不需要传头指针,同时还需要assert断言判断pos是否为空
在pos后面插入,所以这个函数实现不了头插
pos位置后面删除
void SLTEraseAfter(SLTNode* pos) { assert(pos); assert(pos->next); SLTNode* del = pos->next; pos->next = pos->next->next; free(del); del = NULL; } 1
在pos后面删除,不仅到断言pos还需要断言pos->next
其余逻辑很简单
销毁函数
void SLTDestroy(SLTNode** pphead) { assert(pphead); assert(*pphead); SLTNode* del = *pphead; SLTNode* next = NULL; while (del != NULL) { next = del->next; free(del); del = next; } *pphead = NULL; }
14
因为销毁会改变头指针的指向,所以需要传二级指针
如果链表为空,就不必销毁了,所以需要断言*pphead
销毁链表,是一个一个结点得释放,在释放当前节点前,需要保存下一个节点的地址,然后再销毁当前节点,再删除下一个节点
最后还需要把*pphead也就是头节点赋值为空*pphead = NULL
单链表的问题
从上面的代码中可以看出,对于单链表想要尾插,就需要找尾,想要尾删就需要找到尾和尾的前一个结点
并且在某个位置插入删除,需要找到这个位置的前一个结点
这些操作都需要遍历链表,效率低
这也正是单链表的问题,而这些问题放到带头循环双向链表就是小菜一碟了