尾插法要求我们先找到链表的最后一个结点,所以重点在于如何遍历到最后一个结点。
不带头结点 /** * 尾插法:新插入的结点始终在链表尾 * p_head: 指向头指针的指针 * elem: 新结点的数据 */ void insert_at_tail(Node **p_head, int elem) { Node *new = create_node(elem); Node *p = *p_head; while (p->next != NULL) //从头遍历至链表尾 p = p->next; p->next = new; } 带头结点 /** * 尾插法:新插入的结点始终在链表尾 * p_head: 指向头指针的指针 * elem: 新结点的数据 */ void insert_at_tail(Node **p_head, int elem) { Node *new = create_node(elem); Node *p = (*p_head)->next; while (p->next != NULL) p = p->next; p->next = new; } 删除操作 基本思想删除操作是将要删除的结点从链表中剔除,和插入操作类似。
previous 和 current 为指向结点的指针,现在我们要删除结点 current,过程如下:
核心代码为:
previous->next = current->next; free(current);free() 操作将要删除的结点给释放掉。
current 指针不是必需的,没有它也可以,代码写成这样:
previous->next = previous->next->next;但此时我们已经不能释放要删除的那个结点了,因为我们没有一个指向它的指针,它已经消失在茫茫内存中了。
指定位置删除知道了核心代码,剩下的工作就在于我们如何能够正确地遍历到要删除的那个结点。
如你所见,previous 指针是必需的,且一定是要删除的那个结点的直接前驱,所以要将 previous 指针遍历至其直接前驱结点。
不带头结点 /** * 删除指定位置的结点 * p_head: 指向头指针的指针 * position: 指定位置 (1 <= position <= length + 1) * elem: 使用该指针指向的变量接收删除的值 */ void delete(Node **p_head, int position, int *elem) { Node *previous = NULL; Node *current = *p_head; int length = get_length(*p_head); if (length == 0) { printf("空链表\n"); return; } if (position < 1 || position > length) { printf("删除位置不合法\n"); return; } for (int i = 0; current->next != NULL && i < position - 1; i++) { previous = current; current = current->next; } *elem = current->data; if (previous == NULL) *p_head = (*p_head)->next; else previous->next = current->next; free(current); } 带头结点 /** * 删除指定位置的结点 * p_head: 指向头指针的指针 * position: 指定位置 (1 <= position <= length + 1) * elem: 使用该指针指向的变量接收删除的值 */ void delete(Node **p_head, int position, int *elem) { Node *previous = *p_head; Node *current = previous->next; int length = get_length(*p_head); if (length == 0) { printf("空链表\n"); return; } if (position < 1 || position > length) { printf("删除位置不合法\n"); return; } for (int i = 0; current->next != NULL && i < position - 1; i++) { previous = current; current = current->next; } *elem = current->data; previous->next = current->next; free(current); }通过 insert 和 delete函数,我们就能体会到不带头结点和带头结点的差别了,对于插入和删除操作,不带头结点需要额外考虑在第一个元素前插入和删除第一个元素的特殊情况,而带头结点的链表则将对所有元素的操作统一了。
还有特殊的删头法和删尾法,这里不再给出代码了
查找和修改操作查找本质就是遍历链表,使用一个辅助指针,将该指针正确的遍历到指定位置,就可以获取该结点了。
修改则是在查找到目标结点的基础上修改其值。
代码很简单,这里不再列出。详细代码文末获取。
单链表的优缺点通过以上代码,可以体会到:
优点:
插入和删除某个元素时,不必像顺序表那样移动大量元素。
链表的长度不像顺序表那样是固定的,需要的时候就创建,不需要了就删除,极其方便。
缺点:
单链表的查找和修改需要遍历链表,如果要查找的元素刚好是链表的最后一个,则需要遍历整个单链表,不像顺序表那样可以直接存取。
如果插入和删除操作频繁,就选择单链表;如果查找和修改操作频繁,就选择顺序表;如果元素个数变化大、难以估计,则可以使用单链表;如果元素个数变化不大、可以预估,则可以使用顺序表。