单链表 删除结点

删除第一次出现的给定键值

让我们制定问题陈述以了解删除过程。给定一个“键值”,删除该键在链表中的第一个匹配项。

要从链表中删除结点,我们需要执行以下步骤。

  1. 查找待删除的结点的前驱结点
  2. 更改前驱结点的指针域
  3. 释放目标结点所占内存

go语言单向循环链表删除头节点 单链表删掉头节点_结点

void deleteNode(Node** head_ref, int key) /* 可能会改变头指针(目标结点是头结点) */
{
	Node* temp = *head_ref; /* 保存头指针,待会儿遍历用 */
	Node* prev = NULL; /* 保存前驱结点 */

	if (temp != NULL && temp->data == key) /* 如果链表非空且头结点正好是待删除结点, */
	{
		*head_ref = temp->next; // 更新head
		delete temp; // 释放旧head
		return;
	}

	else /* 链表非空且目标结点不是头结点 */
	{
		while (temp != NULL && temp->data != key) /* 查找满足条件的第一个结点,直至查找成功或者temp出界(指向null) */
		{
			prev = temp; /* 当前结点存到前驱结点中 */
			temp = temp->next; /* 当前结点向后顺移,遍历结束时,prev是末尾结点,temp是末尾结点->next,即null */
		}

		if (temp == NULL) // 两种情况下到达这里:链表一开始就是空的。或者经过上面的循环,链表已遍历完毕,此时temp指向链表外部的null,说明直到查找结束仍未找到满足key的结点,任务失败 */
			return;

		// Unlink the node from linked list. unlink 解开链条,很生动
		prev->next = temp->next; /* 让前驱结点不再指向待删除结点,而是指向待删除结点的后继结点,即孤立目标结点 */

		delete temp; /* 释放内存 */
	}
}
/* 函数调用 */
deleteNode(&head, 1);

删除指定位置结点

Example:

Input: position = 1, Linked List = 8->2->3->1->7
Output: Linked List =  8->3->1->7

Input: position = 0, Linked List = 8->2->3->1->7
Output: Linked List = 2->3->1->7

如果待删除的结点是头结点,正常删除就行。

如果待删除的结点不是头结点,则必须维护一个指针,这个指针指向待删除结点的前驱结点。因此如果待删除结点的索引不是0,则我们需要循环position-1次来使指针找到这个前驱结点。

以下是上面思路的具体实现细节。

void deleteNode(Node** head_ref, int position)
{
	// 如果链表为空
	if (*head_ref == NULL)
		return;
	Node* temp = *head_ref; // 保存头结点,稍后遍历用
	// 如果要删除的结点是头结点
	if (position == 0)
	{
		*head_ref = temp->next;
		delete temp;
		return;
	}
	// 如果要删除的结点不是头结点,则将temp遍历至目标位置的前一个位置,此时temp的意义是待删除结点的前驱。
	// 也可能pos远超出链表长度了,则temp停留在尾结点后面的null处。
	for (int i = 0; temp != NULL && i < position - 1; i++)
		temp = temp->next;
	// temp==null说明pos远远超出链表长度,temp->next==null说明pos刚好超过链表长度,两种情况都任务失败。
	if (temp == NULL || temp->next == NULL)
		return;
	// 如果一切正常,则此时temp停留在待删除结点的前驱结点,temp->next正是目标结点。
	// 为了删除目标结点,我们要先存下来目标结点的next指针域所指之处,方便跟前驱衔接。
	Node* next = temp->next->next;
	delete temp->next; // 释放目标结点内存
	temp->next = next; // 桥接(前驱直接指向后继)
}

思考:
一、最后两条语句为什么不能交换位置?两个temp->next有什么区别?
temp保存的是前驱,delete temp->next是目标结点。而第二个temp->next的意义是前驱重新伸出来的的指针域指向倒数第三句保存的后继结点。(前驱 目标 后继)
二、返回值类型为void的函数,究竟返回的是什么东西?删除函数为我们输出了什么?
就像做菜一样,形参Node** head_ref和int position是对食材的描绘,而实参&head和2就是真实的食材,我们不需要在意自己写的函数做出了什么菜,只要知道自己亲手传入的食材确实被处理了(假设函数功能一切正常),并且处理结果保留了下来,即可。其他函数也需要使用他们的时候自然会接手这些处理过的食材继续进行别的加工。从这个角度看待,也可以如此理解:返回值类型为void的函数不生产任何新的东西,只是在后厨改造了我们传进去的参数,并且将其作为遗产又确确实实留给了我们(传引用)。

删除整条链表

void deleteList(Node** head_ref)
{
	Node* current = *head_ref;
	Node* next;

	while (current != NULL)
	{
		/* (current) -> next */
		next = current->next;
		delete current;
		current = next;
	}

	*head_ref = NULL; /* 全部删完后记得将头指针置NULL */
}

参考资料

https://www.geeksforgeeks.org/linked-list-set-3-deleting-node/

https://www.geeksforgeeks.org/delete-a-linked-list-node-at-a-given-position/

https://www.geeksforgeeks.org/write-a-function-to-delete-a-linked-list/