6. 前插法创建单链表
前插法,就是将新结点逐个插入到链表的头部,即头结点后面(首元结点前面),由此创建链表(如图 3.4.8 所示)。
【算法步骤】
- 初始化空链表
- 创建头结点,并初始化指针域为
NULL
。 - 检查内存分配是否成功(增强鲁棒性)。
- 创建头结点,并初始化指针域为
- 循环插入元素:逆序输入
n
个元素,每次执行:
a. 动态生成新结点*s
,检查分配是否成功。
b. 读取元素值到s->data
(可扩展为从任意数据源输入)。
c. 将新结点插入头结点之后(前插法)。
【算法描述】
Status CreateList_H(LinkList *L, int n) {
// 前插法创建带头结点的单链表,逆序输入 n 个元素
if (n <= 0) return ERROR; // 检查元素个数合法性
*L = (LNode*)malloc(sizeof(LNode)); // 创建头结点
if (!*L) return ERROR; // 内存分配失败检查
(*L)->next = NULL; //创建带头结点的空链表
for (int i = 0; i < n; ++i) {
LNode *s = (LNode*)malloc(sizeof(LNode));
if (!s) {
// 内存分配失败时,清理已创建的链表
while ((*L)->next) {
LNode *temp = (*L)->next;
(*L)->next = temp->next;
free(temp);
}
free(*L);
*L = NULL;
return ERROR;
}
scanf("%d", &s->data); // 输入元素值(假设为整型)
s->next = (*L)->next; // 新结点指向原首结点
(*L)->next = s; // 头结点指向新结点
}
return OK;
}
【算法分析】
- 时间复杂度:每次结点创建与插入操作均为 O(1)O(1)O(1) ,循环
n
次,总复杂度 O(n)O(n)O(n) 。 - 空间复杂度:除头结点外,需额外分配
n
个结点(插入的结点),复杂度 O(n)O(n)O(n) 。
7. 后插法创建单链表
后插法是通过将新结点逐个插入到链表的尾部来创建链表。同前插法一样,每次申请一个新结点,读入相应的数据元素值。不同的是,为了使新结点能够插入到表尾,需要增加一个尾指针 r
指向链表的尾结点(如图 3.4.9 所示)。
【算法步骤】
-
初始化阶段
- 创建头结点并初始化指针域为
NULL
- 检查头结点是否分配成功
- 初始化尾指针
r
指向头结点
- 创建头结点并初始化指针域为
-
循环插入阶段
-
正序输入
n
个元素,每次执行:
a. 生成一个新结点*s
;
b. 输入元素值赋给新结点*s
的数据域;c. 将新结点
*s
插入到尾结点*r
之后;d. 尾指针
r
指向新的尾结点*s
。
-
【算法描述】
Status CreateList_R(LinkList *L, int n) {
// 后插法创建带头结点单链表,正序输入n个元素
if (n <= 0) return ERROR; // 参数合法性检查
*L = (LNode*)malloc(sizeof(LNode));
if (!*L) return ERROR; // 头结点分配失败
(*L)->next = NULL;
LNode *r = *L; // 尾指针初始化
for (int i = 0; i < n; ++i) {
LNode *s = (LNode*)malloc(sizeof(LNode));
if (!s) {
// 分配失败时清理已创建链表
while ((*L)->next) {
LNode *temp = (*L)->next;
(*L)->next = temp->next;
free(temp);
}
free(*L);
*L = NULL;
return ERROR;
}
scanf("%d", &s->data); // 输入元素值(假设为整型)
s->next = NULL;
r->next = s; // 链接新结点
r = s; // 更新尾指针
}
return OK;
}
【算法分析】
- 时间复杂度: O(n)O(n)O(n) 。
- 空间复杂度:O(n)O(n)O(n)
例 3.4.3 线性表L在( )情况下适用于使用链式结构实现。
A. 需经常修改L中的结点值\qquad B. 需不断对L进行删除插入
C. L中含有大量的结点 \qquad D.L中结点结构复杂
【解】
- 选项 A,经常修改结点的值,即需要查找到该结点,因此顺序表更适合。
- 选项 B,链表更适合于不断进行删除和插入操作,因为顺序表对于这些操作要移动元素,时间复杂度是 O(n)O(n)O(n) ;链表不需要,只需要修改指针即可,时间复杂度是 O(1)O(1)O(1) 。
- 选项 C、D,线性表中元素数量的多少和复杂度的高低,不是选择顺序表还是链表的依据。
本题答案:B
例 3.4.4 在单链表中,要将 s
所指结点插入到 p
所指结点之后,其语句应为( )。
A. s->next = p + 1; p->next = s;
B. (*p).next = s; (*s).next = (*p).next;
C. s->next = p->next; p->next = s->next;
D. s->next = p->next; p->next = s;
【解】
将新结点插入到 p
所指结点之后,其插入语句是 s->next = p->next; p->next = s;
。
本题答案:D
例 3.4.5 在一个长度为 n(n>1)n(n>1)n(n>1) 的带头结点的单链表 h
上,另设有尾指针 r
(指向尾结点),以下执行操作与链表的长度有关的是( )。
A. 删除单链表中的首结点
B. 删除单链表中的尾结点
C. 在单链表首结点前插入一个新结点
D. 在单链表尾结点后插入一个新结点
【解】
根据题目叙述可知单链表有头指针和尾指针,如下图所示。
选项 A :删除单链表中的首元结点,过程如下:
LinkList *p = h->next;
h->next = p->next;
free(p);
选项 B:删除单链表的尾结点,过程如下:
LinkList *p = h;
while(p->next != r) //查找尾结点的前驱结点
p = p->next;
p->next = NULL;
free(r);
r = p;
选项 C:在首元结点之前插入一个新结点,过程如下:
//假设插入 *s 结点
s->next = h->next;
h->next = s;
选项 D:在尾节点后插入一个新结点,过程如下:
//假设插入 *s 结点
r->next = s;
s->next = NULL;
r = s;
本题答案:B