实验课总体安排:
1.课后作业题目讲解
2.头插法创建单链表
3.尾插法创建单链表
4.LeetCode算法练习题带刷、讲解
5.实验一报告书优秀作业展示
一、课后作业题目讲解

程序中基本语句“y--”或“x++”,执行的次数是由x和y决定的,而在咱们的题目中,x和y都是一个常数,所以T(n)=O(1)。
部分同学写成了0(1000),这样是不对的,时间复杂度的常量级表达是用O(1),统一表示。
还有部分同学写成了O(y),这样也是不对的,已经找到了执行次数是由x和y决定的,而咱们的算法度量时间公式是T(n) =O(f(n)),是一个关于问题规模n的某个函数f(n).



1、逐步分析
-
初始值:i = 1
-
循环更新:每次 i 变为原来的 3 倍
-
第 1 次循环:i = 3
-
第 2 次循环:i = 9
-
第 3 次循环:i = 27
-
…
-
第 k 次循环:i = 3^k
-
-
循环终止条件:i > n
-
即 3^k > n
-
2、求循环次数
要满足退出条件:
3k>n3k>n
取对数:
k>log3nk>log3n
所以循环大约执行k = O(log_3 n) ] 次。
3、时间复杂度
因为常数底数不影响数量级:
O(log_3 n) = O(log n)
二、两种方法创建单链表代码讲解
#include <stdio.h>
#include <stdlib.h>
typedef char ElemType; // 存储的数据类型
// 单链表结点定义
typedef struct Lnode {
ElemType data;
struct Lnode* next;
} LNode, *LinkList;
// 全局指针,指向链表头结点
LinkList phead = NULL;
// 菜单展示
void show_menu() {
printf("\n==== 单链表基本操作 ====\n");
printf("1 -- 头插法创建链表\n");
printf("2 -- 尾插法创建链表\n");
printf("3 -- 遍历显示链表\n");
printf("0 -- 退出\n");
printf("请输入功能编号: ");
}
// 创建空链表
LinkList create_empty_list() {
LinkList phead = (LinkList)malloc(sizeof(LNode));
if (!phead) {
printf("内存分配失败!\n");
exit(1);
}
phead->next = NULL;
return phead;
}
// 头插法建表
LinkList create_list_head() {
LinkList phead = create_empty_list();
int n;
ElemType temp;
printf("请输入元素个数: ");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("请输入第 %d 个元素: ", i + 1);
scanf(" %c", &temp);
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = temp;
node->next = phead->next;
phead->next = node;
}
return phead;
}
// 尾插法建表
LinkList create_list_tail() {
LinkList phead = create_empty_list();
LNode* r = phead; // r为尾指针
int n;
ElemType temp;
printf("请输入元素个数: ");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("请输入第 %d 个元素: ", i + 1);
scanf(" %c", &temp);
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = temp;
node->next = NULL;
r->next = node;
r = node;
}
return phead;
}
// 遍历链表
int show_list(LinkList phead) {
if (!phead) {
printf("链表不存在!\n");
return 0;
}
LNode* p = phead->next;
if (!p) {
printf("链表为空!\n");
return 0;
}
printf("链表元素:");
while (p) {
printf("%c ", p->data);
p = p->next;
}
printf("\n");
return 1;
}
int main() {
int choice;
do {
show_menu();
scanf("%d", &choice);
switch (choice) {
case 1:
printf("头插法创建链表\n");
phead = create_list_head();
show_list(phead);
break;
case 2:
printf("尾插法创建链表\n");
phead = create_list_tail();
show_list(phead);
break;
case 3:
printf("遍历链表:\n");
show_list(phead);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("无效选择,请重新输入!\n");
}
} while (choice != 0);
return 0;
}
一、单链表结点定义
// 单链表结点定义
typedef struct Lnode {
ElemType data;
struct Lnode* next;
} LNode, *LinkList;
单链表:结点包含两个部分,一个是数据域(data)存放结点本身,另一个是指针域(next域)存放指向下一个结点的地址。
- ElemType data → 这一格专门放数据
- struct Lnode* next → 这一格专门放“下一个结点的地址”
typedef struct Lnode {
ElemType data;
LNode* next; // ❌ 这里会报错
} LNode;
- next 是个 指针,它指向的是 相同类型的结点(struct Lnode)。
- 如果写成 LNode* next;,这时 LNode 还没 typedef 完成,编译器会报错。
所以要写 struct Lnode* next;。 -
结构体体内部还在定义
struct Lnode。 -
LNode这个名字是 typedef 之后才生效的别名,在定义体内还不存在。 -
所以编译器找不到
LNode,会报错。等 typedef 完成之后,才可以直接用 LNode* 或 LinkList
struct Lnode* next; // ✅
struct Lnode是结构体的“原名”,已经存在,可以在内部引用自己。- typedef 是在 } 后才生效,所以在结构体内部不能用别名。
二、创建空链表
// 创建空链表
LinkList create_empty_list() {
LinkList phead = (LinkList)malloc(sizeof(LNode));
if (!phead) {
printf("内存分配失败!\n");
exit(1);
}
phead->next = NULL;
return phead;
}
- create_empty_list() 的作用是创建一个空链表。
- 空链表是指:有一个头结点,但头结点的 next 指针为空,没有实际数据。
1.第一行
LinkList phead = (LinkList)malloc(sizeof(LNode));
- malloc(sizeof(LNode)):向堆申请一块内存,大小正好存放一个结点。
- (LinkList):把 malloc 返回的 void* 转换为 LinkList 类型(即 LNode*)。
- phead:指向新分配的头结点。
2.第二行
if (!phead) {
printf("内存分配失败!\n");
exit(1);
}
- 检查内存分配是否成功。
- 如果 malloc 返回 NULL,说明分配失败,就打印错误信息并退出程序。
3.第三行
phead->next = NULL;
- 给头结点的指针域 next 赋值为空。
- 表示这个链表目前没有任何实际结点。
4.第四行
return phead;
- 返回链表的头指针。
- 别忘了,链表虽然空,但头结点已经存在,后续可以用头插法或尾插法往里面加数据。
小结
- 空链表 ≠ NULL
- 空链表:有头结点,但 head->next = NULL
- NULL:链表根本没有头结点
- 这是链表操作的基础,后面所有插入、遍历操作都从这个头结点开始。
三、头插法创建单链表
// 头插法建表
LinkList create_list_head() {
LinkList phead = create_empty_list();
int n;
ElemType temp;
printf("请输入元素个数: ");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("请输入第 %d 个元素: ", i + 1);
scanf(" %c", &temp);
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = temp;
node->next = phead->next;
phead->next = node;
}
return phead;
}
-
一、创建头结点
LinkList phead = create_empty_list();
- 调用 create_empty_list() 创建一个空链表(有头结点,next = NULL)。
- phead 是链表的起点。
二、读入元素个数
printf("请输入元素个数: ");
scanf("%d", &n);
- 用户输入要插入的结点数量 n。
三、循环读入每个元素
for (int i = 0; i < n; i++) { ... }
- 每次循环完成一个结点的创建和插入。
四、创建新结点
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = temp;
- 申请内存创建新结点。
- 把用户输入的数据存入新结点的 data 域。
五、插入新结点到链表前面
node->next = phead->next;
phead->next = node;
- 第一行:把新结点的 next 指向原来的第一个结点(可能是 NULL)。
- 第二行:把头结点的 next 指向新结点。
✅ 这样每次新结点都会“插到前面”,所以叫头插法。
六、返回头指针
return phead;
- 返回链表的头结点指针,方便后续操作(遍历、插入、删除等)。
小结
- 链表一开始只有头结点。
- 每次插入一个新结点,都把它放在链表最前面。
- 新结点的 next 指向原来的第一个结点,头结点 next 更新为新结点。
- 最终链表顺序 = 输入顺序的逆序(头插法特点)。
四、尾插法创建单链表
// 尾插法建表
LinkList create_list_tail() {
LinkList phead = create_empty_list();
LNode* r = phead; // r为尾指针
int n;
ElemType temp;
printf("请输入元素个数: ");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("请输入第 %d 个元素: ", i + 1);
scanf(" %c", &temp);
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = temp;
node->next = NULL;
r->next = node;
r = node;
}
return phead;
}
-
一、创建头结点
LinkList phead = create_empty_list();
- 创建一个空链表,有头结点(next = NULL)。
- phead 是链表起点。
二、设置尾指针
LNode* r = phead; // r为尾指针
- r 永远指向链表的最后一个结点。
- 开始时链表只有头结点,所以尾指针指向头结点。
三、读入元素个数
printf("请输入元素个数: ");
scanf("%d", &n);
- 用户输入要插入的结点数量。
四、循环创建并插入新结点
for (int i = 0; i < n; i++) { ... }
- 每次循环处理一个新结点。
五、创建新结点并存数据
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = temp;
node->next = NULL;
- 新结点数据域存用户输入的值。
- 指针域 next = NULL,表示它暂时是链表的最后一个结点。
六、将新结点插入链表末尾
r->next = node;
r = node;
- 第一行:把尾结点的 next 指向新结点,把它加到链表末尾。
- 第二行:更新尾指针 r 指向新结点,下次插入继续在末尾插。
✅ 这样每次新结点都加到链表最后,所以叫尾插法。
七、返回头指针
return phead;
- 返回链表头结点,方便后续操作(遍历、插入、删除等)。
小结
- 链表一开始只有头结点。
- 每次插入一个新结点,都加在链表末尾。
- 尾指针 r 永远指向最后一个结点,保证插入操作时间短且顺序不变。
- 最终链表顺序 = 输入顺序一致(尾插法特点)。
单链表操作与算法解析
6245

被折叠的 条评论
为什么被折叠?



