2.4 创建新节点
SLTNode* BuySLTNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("malloc"); return NULL; } newnode->data = x; newnode->next = NULL; return newnode; }
2.5 头插
头插显然是要改变头指针存放的地址,因此形参也需要传递二级指针。头插无需单独考虑空链表的情况
代码实现:
void SLTPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySLTNode(x); newnode->next = *pphead; *pphead = newnode; }
2.6 尾删
尾删先要遍历一遍链表找到最后一个节点将其释放掉,还要找到倒数第二个节点将它的指针域中存的地址改为NULL。所以定义两个指针让他们同时去遍历链表,一个找尾,另一个找倒数第二个节点。需要注意的是空链表和只有一个节点的链表的情况,空链表无法进行尾删,而只有一个节点的链表在尾删后会变成一个空链表,这意味着要改变头指针里面存放的地址,所以尾删形参也要传递二级指针。
代码实现:
void SLTPopBack(SLTNode** pphead) { assert(pphead); assert(*pphead);//暴力检查是否为空链表,空链表不能删数据 //检查链表是否为空 /*if (*pphead == NULL)//温柔的进行检查 { return; }*/ //只有一个节点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } //有多个节点 else { //找尾 SLTNode* prev = *pphead; SLTNode* tail = *pphead; while (tail->next != NULL) { prev = tail; tail = tail->next; } free(tail); tail = NULL; prev->next = NULL;//假如只有一个节点这里就会非法访问 } }
2.7 头删
头删很明显需要改变头指针中存放的地址,所以形参仍然需要传递二级指针。头删只需要注意链表是否为空,空链表无法进行删除。此外在进行头删的时候记得将原来的头节点释放掉,因此在改变头节点之前需要先保留原来头结点的地址,否则在更换完新的头节点后就找不到原来的头节点了。
代码展示:
void SLTPopFront(SLTNode** pphead) { if (*pphead == NULL)//这里也可以直接用assert来断言 { return; } SLTNode* tail = *pphead; *pphead = (*pphead)->next;//假如链表为空,这里就会发生越界,因此要判断链表是否为空 free(tail); tail = NULL; }
2.8 单链表查找
其实就是遍历一遍链表,但是只能返回第一次出现的地址。查找可以当修改来使用,我们查找到节点的地址后就可以通过地址去修改数据域中存储的数据。
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) { SLTNode* ptr = phead; while (ptr != NULL) { if (ptr->data == x) { return ptr; } else { ptr = ptr->next; } } return NULL; }
2.9 在pos位置之前插入
和尾插类似,但此时只需要遍历链表找到pos位置的前一个节点即可,同样需要注意pos是头结点的情况,此时就成头插了,需要改变头指针中存的地址,因此函数的形参需要传二级指针。
//在pos之前插入 void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pphead); assert(pos); if (pos == *pphead) { SLTPushFront(pphead, x); } else { SLTNode* newnode = BuySLTNode(x); SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos; } }
上面这种在pos位置前面插入的方法,需要知道头节点的地址
2.10 删除pos位置数据
pos可能是头结点的地址,因此形参要用二级指针。
//pos位置删除 void SLTErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(*pphead);//空链表不能删 assert(pos); if (pos == *pphead) { SLTPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL;//其实没用,形参的改变不改变实参 } }
2.11 在pos位置的后面插入
这里需要特别注意地址的赋值顺序。有以下两种正确的赋值顺序供大家参考:
先让newnode的指针域存储pos后一个节点的地址,再让pos的指针域存newnode的地址
借助中间变量先把pos后面节点的地址保存起来,再让pos的指针域存newnode的地址,最后再让newnode的指针域存第一步中间变量中保存的地址。
void SLTInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = BuySLTNode(x); SLTNode* tmp = pos->next; pos->next = newnode; newnode->next = tmp; }
2.12 删除pos位置后面的数据
注意不能写成后面这样:pos->next = pos->next->next。这样写虽然把pos位置后面的节点从链表中剔除出去了,但并没有真正意义上的实现删除,因为每一个节点都是通过malloc在堆上申请的,不使用的时候要主动的去释放掉,也就是free掉,把这块空间归还给操作系统,否则会导致内存泄漏。而上面那样写,就会导致pos后面的节点丢失,无法进行释放,正确的做法是在执行这条语句之前把pos后边节点的地址先保存起来。
void SLTEraseAfter(SLTNode* pos) //只能删除pos位置后面的节点,不能删除pos节点 //因为pos节点的前一个节点无从得知 { assert(pos); assert(pos->next); SLTNode* tmp = pos->next->next;//这里先保存了pos后面的后面的节点的地址,也是可以的 free(pos->next); pos->next = tmp; }
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!