带头节点的单链表的插入操作优化

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

带头节点的单链表的插⼊操作优化
1.偶然看到了⼗字链表的⼀些东西,想到之前在《数据结构与算法分析》的链表⼀章中,需要⽤多重表实现⼀个简单的查询功能。

功能需求如下:
 “已知学⽣和学校课程总数分别为 40000 和 2500,现在需要得到两份报告,⼀份显⽰每门课成注册的所有学⽣信息,
 ⼀份显⽰每个学⽣注册了哪些课程。


 显然可以⽤⼀个 40000 * 2500 个元素的⼆维数组来解决,但是每个学⽣选课数⽬很少,因此会浪费很多空间。

因此选择⼗字链表来实现。

 既然是链表,那么肯定要有插⼊操作,于是便有了本⽂。

算是对功能实现前的铺垫。

2.本⽂会介绍两种带头节点的插⼊操作,说是两种,其实后⼀种只是前⼀种的优化,觉得不错,分享给⼤家。

 根据代码分析:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int Elem;
struct LNode
{
Elem elem;
struct LNode *next;
};
static void*
MALLOC(int num, size_t size)
{
void *new = calloc(num, size);
if (new == NULL)
{
fprintf(stderr, "malloc failed: [%d]\n", (int)size);
exit(1);
}
return new;
}
void
print_link(struct LNode *head)
{
struct LNode *cur = head;
while(cur)
{
printf("%d -> ", cur->elem);
cur = cur->next;
}
printf("end\n");
}
void
insert_link_m1 (struct LNode **head, Elem e) //普通插⼊
{
struct LNode *cur, *prev, *new;
cur = *head;
prev = NULL;
while(cur != NULL && cur->elem < e)
{
prev = cur;
cur = cur->next;
}
new = (struct LNode *)MALLOC(1, sizeof(*new));
new->elem = e;
//insert
if (prev == NULL)
{
*head = new;
}
else
{
prev->next = new;
}
new->next = cur;
}
void
insert_link_m2 (struct LNode **head, Elem e) //优化后插⼊
{
struct LNode *cur, *new;
while((cur = *head) != NULL && cur->elem < e)
{
head = &cur->next;
}
new = (struct LNode *)MALLOC(1, sizeof(*new));
new->elem = e;
new->next = cur;
*head = new;
}
void
delete_link(struct LNode *head)
{
struct LNode *cur, *prev;
cur = head;
prev = NULL;
while(cur != NULL)
{
prev = cur;
cur = cur->next;
free(prev);
memset(prev, 0, sizeof(*prev));
}
}
void
create_link(struct LNode **head)
{
struct LNode *cur, *new;
Elem e;
cur = *head;
while(scanf("%d", &e) == 1)
{
new = (struct LNode *)MALLOC(1, sizeof(*new));
new->elem = e;
if(cur == NULL)
{
*head = new;
cur = new;
}
else
{
cur->next = new;
cur = new;
}
}
}
void
create_ordered_link(struct LNode **head, void (*insert_link) (struct LNode **head, Elem e)) {
Elem e;
while(scanf("%d", &e) == 1)
{
insert_link(head, e);
}
}
int
main(int argc, char *argv[])
{
struct LNode *head = NULL;
printf("enter link nums:\n");
create_link(&head);
print_link(head);
delete_link(head);
printf("\n-----------------------------\n");
printf("enter link nums:\n");
head = NULL;
create_ordered_link(&head, insert_link_m1);
print_link(head);
delete_link(head);
printf("\n-----------------------------\n");
printf("enter link nums:\n");
head = NULL;
create_ordered_link(&head, insert_link_m2);
print_link(head);
delete_link(head);
return0;
}
输⼊⽤例格式:数字+空格+数字+空格...输⼊完以后按下回车到下⼀⾏,ctrl+d结束输⼊。

 1)先来区分⼀下头节点和⾸节点:
⾸节点:链表中第⼀个存放 Elem元素的节点。

头节点:指向这个⾸节点的⼀个指针,给节点不存放Elem元素
 2)带头节点的普通插⼊:
看 void insert_link_m1 (struct LNode **head, Elem e ) 参数 **head,为指向头节点的指针,
因为刚开始head指向NULL(main⾥边的初始化),我们要⽤这个head为头节点创建单链表,⾃然要改变head指向的值,
因此要传进来 &head .接下来:
struct LNode *cur, *prev, *new;
cur = *head;
prev = NULL;
*head是head指针⾥的地址值,⽽head⾥的地址值就是⾸节点的位置,因此,cur = *head便是让cur指向⾸节点,
再将 prev置为NULL,这个操作很关键,因为prev是否为NULL,⽤来当作要插⼊的位置是否位于⾸节点之前,即待插⼊的 节点是否是新的⾸节点。

好了接下来就是遍历链表找到新节点的插⼊位置:
while(cur != NULL && cur->elem < e)
{ prev = cur; cur = cur->next; }
new = (struct LNode *)MALLOC(1, sizeof(*new));
new->elem = e;
接下来是插⼊操作:
if (prev == NULL)
{ *head = new; }
else
{ prev->next = new; }
new->next = cur;
prev指向当前节点的前⼀个节点,如果插⼊的不是链表的⾸节点的位置,⾃然便有 prev->next = new; new->next = cur;
但是若链表为空,或者待插⼊的位置为⾸节点的位置,那么此时 prev = NULL。

不能按上边那样操作,需要单独处理。

因此⽤prev是否等于NULL,便成了判断的标识。

*head = new; new->next = cur;
 3)带头节点的优化插⼊:
优化插⼊其实是思路上的优化,优化针对的地⽅便是是否能在真正执⾏插⼊的地⽅,不⽤区分是否插⼊的地⽅是⾸节点的位置。

即直接执⾏插⼊操作 new->next = cur; *head = new; 这⾥*head是存放new的地址的地⽅,也即指向new的箭头尾部的地⽅。

我们可以看到,要想成功执⾏⼀次链表的插⼊操作只需要⼀个指向新节点new的指针x(*head或->next) 和⼀个指向当前节点的cur指针(->next)。

 
在普通的插⼊操作中 x可以是头节点指针(当新节点需要插⼊到头节点后边时),x也可以是 prev->next 当新节点不需要插⼊到头节点后边时。

于是我们可以⽤⼀个LNode ** 类型的指针N,它既可以存放头节点的地址,⼜可以存放其它节点中->next成员的地址。

即可以存放 &x
当需要执⾏真正的插⼊操作的时候可以有 *N = new; new = cur;
因为N中存放着头节点的地址,或->next成员的地址,当对其进⾏解引⽤*N时便是相当于 *head = new 或 prev-next = new;
根据这个思路便有了优化插⼊的代码:
void
insert_link_m2 (struct LNode **head, Elem e) //优化后插⼊
{
struct LNode *cur, *new;
while((cur = *head) != NULL && cur->elem < e)
{
head = &cur->next;
}
new = (struct LNode *)MALLOC(1, sizeof(*new));
new->elem = e;
new->next = cur;
*head = new;
}
(1)while第⼀次操作时cur是指向⾸节点,头节点指向cur,head中存放头节点的地址。

倘若条件不满⾜直接跳出循环,便有new->next = cur; *head = new;
head存放了头节点的地址, *head作为左值的时候,*head = new;便使得头节点指向了new。

(2)若条件不满⾜时,及插⼊位置不是头节点后边,便执⾏ head = &cur->next;然后 cur = *head; 此时 head存放cur->next的地址,⽽cur = *head,便将cur->next的值,即下⼀个节点的地址赋值给cur,
倘若此时跳出循环, head中存放了此时cur的前⼀个节点中(next)成员的地址,cur指向当前位置,于是执⾏ new->next = cur; *head = new后,便⾃动完成了链接.
这样⼀个技巧便省去了判断prev == NULL的步骤。

参考⽂献:《c与指针》第⼗⼆章。

思路就是这样,难免表述不清,结合代码,画画图便知,欢迎指正与指点。

相关文档
最新文档