目录
第九章(结构体和枚举)第十章(指针的高级运用)链表思维导图
答案:
作业1:结构体相关
(a) 下面结构体类型的变量的内存布局是怎样的?请使用VS的debug模式展示内存布局并截图
typedef struct stundent_s { int number; char name[25]; char gender; int chinese; int math; int english; } Student; Student s;
(b) 如何通过结构体获取成员,如何通过指向结构体的指针获取成员?
(c)(学生信息处理)有这样一种学生结构体类型,其数据成员有包括学号,姓名和3门课程的成绩。实现下列功能:
-
从键盘输入5个学生的信息,将这些同学的信息采用结构体数组的方式存储起来。
-
输出每门课程最高分的学生信息。
-
输出每门课程的平均分。
-
按照总分从高到低对学生进行排序,并输出排序后的学生信息。(排序话自己写个选择或冒泡排序即可)
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
typedef struct stundent_s {
int number;
char name[25];
char gender;
int chinese;
int math;
int english;
} Student;
int main(void) {
Student s1 = { 1,"liuyifei",'f',30,30,30 };
Student* ps1 = &s1;
printf("%3d%10s%3c%5d%5d%5d\n",
s1.number,
s1.name,
s1.gender,
s1.chinese,
s1.math,
s1.english);
printf("%3d%10s%3c%5d%5d%5d\n",
ps1->number,
ps1->name,
ps1->gender,
ps1->chinese,
ps1->math,
ps1->english);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
typedef struct {
int s_id;
char name[25];
int ch_grade;
int en_grade;
int ma_grade;
}Stu;
Stu* up_to_low(Stu* student[],int len) {
int sum; int sum2; int temp; Stu* stemp;
for (int i = 0; i < len; i++) {
sum = student[i]->ch_grade + student[i]->ma_grade + student[i]->en_grade;
temp = i;
for (int j = i+1; j < len; j++) {
sum2= student[j]->ch_grade + student[j]->ma_grade + student[j]->en_grade;
if (sum < sum2) { //注意是找到最大的值不只是比第一个大的
temp = j;
sum = sum2;//更新最大总分值,除非把SUM放在内循环
}
}
//交换两个元素
stemp = student[i];
student[i] = student[temp];
student[temp] = stemp;
}
}
Stu* print_stu(Stu* student[],int len) {
printf("\n");
for (int i = 0; i < len; i++) {
printf("%5d%10s%5d%5d%5d\n",
student[i]->s_id,
student[i]->name,
student[i]->ch_grade,
student[i]->en_grade,
student[i]->ma_grade);
}
printf("\n");
}
void average_max(Stu* studet[], int len, double*ma_ar,double *en_ar, double* ch_ar,
int *ma_max,int* en_max,int*ch_max)
{
for (int i = 0; i < len; i++) {
*ma_ar += studet[i]->ma_grade;
*en_ar += studet[i]->en_grade;
*ch_ar += studet[i]->ch_grade;
if (*ma_max < studet[i]->ma_grade) { *ma_max = studet[i]->ma_grade; }
if (*en_max < studet[i]->en_grade) { *en_max = studet[i]->en_grade; }
if (*ch_max < studet[i]->ch_grade) { *ch_max = studet[i]->ch_grade; }
}
*ma_ar /= 5.0;
*en_ar /= 5.0;
*ch_ar /= 5.0;
}
int main(void) {
Stu s[5]; Stu* sp[5];
printf(" id name chineses_grade english_grade math_grade\n");
for (int i = 0; i < 5; i++) {
scanf("%d%s%d%d%d",
&s[i].s_id,
s[i].name,
& s[i].ch_grade,
&s[i].en_grade,
&s[i].ma_grade);
sp[i] = s+i;
}
up_to_low(sp, 5);
print_stu(sp, 5);
double ma_ar=0.0, en_ar= 0.0, ch_ar= 0.0;
int ma_max=0, en_max=0, ch_max=0;
//神他妈调用栈堆冲突,传了个S
average_max(sp, 5, &ma_ar, &en_ar, &ch_ar, &ma_max, &en_max, &ch_max);
printf("chineses_average_grade is %3.2f ,max is %3d\n"
"english_grade_average_grade is %3.2f ,max is %3d\n"
"math_grade_average_grade is %3.2f ,max is %3d\n",ch_ar,ch_max,en_ar,en_max,ma_ar,ma_max);
return 0;
}
答案:
函数分解------自己写的
void sort_students(Student* arr[], int n) {
// 选择排序
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (cmp(arr[j], arr[minIdx]) < 0) {
minIdx = j;
}
}
swap(arr, i, minIdx);
}
}(c) #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> // 不要给指针类型起别名 typedef struct student_s { int number; char name[25]; char gender; int chinese; int math; int english; } Student; void print_stu_info(const struct student_s* p) { printf("%d %s %c %d %d %d\n", p->number, p->name, p->gender, p->chinese, p->math, p->english); } int total_score(Student* p) { return p->chinese + p->english + p->math; } int cmp(Student* p1, Student* p2) { // p1 < p2 返回负值 // p1 = p2 返回0 // p1 > p2 返回正值 int total1 = total_score(p1); int total2 = total_score(p2); return total2 - total1; } void swap(Student* arr[], int i, int j) { Student* tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } void sort_students(Student* arr[], int n) { // 选择排序 for (int i = 0; i < n - 1; i++) { int minIdx = i; for (int j = i + 1; j < n; j++) { if (cmp(arr[j], arr[minIdx]) < 0) { minIdx = j; } } swap(arr, i, minIdx); } } void print_score(Student students[], int n) { // p[i] = *(p + i) int idx1 = 0; int idx2 = 0; int idx3 = 0; for (int i = 1; i < n; i++) { if (students[i].chinese > students[idx1].chinese) { idx1 = i; } if (students[i].math > students[idx2].math) { idx2 = i; } if (students[i].english > students[idx3].english) { idx3 = i; } } print_stu_info(&students[idx1]); print_stu_info(&students[idx2]); print_stu_info(&students[idx3]); } void print_average_score(Student students[], int n) { double avg1 = 0; double avg2 = 0; double avg3 = 0; for (int i = 0; i < n; i++) { avg1 += students[i].chinese; avg2 += students[i].math; avg3 += students[i].english; } printf("Average score of chinese: %.2lf\n", avg1 / n); printf("Average score of math: %.2lf\n", avg2 / n); printf("Average score of english: %.2lf\n", avg3 / n); } int main(void) { Student students[5]; for (int i = 0; i < 5; i++) { scanf("%d%s %c%d%d%d", &students[i].number, students[i].name, &students[i].gender, &students[i].chinese, &students[i].math, &students[i].english); } // 打印单科最高分学生的信息 print_score(students, 5); // 输出没门课程的平均分 print_average_score(students, 5); // 按照总分从高到低对学生进行排序,并输出排序后的学生信息。 Student* pstus[] = { students, students + 1, students + 2, students + 3, students + 4 }; sort_students(pstus, 5); for (int i = 0; i < 5; i++) { print_stu_info(pstus[i]); } return 0; }
作业2:简答题(动态分配内存相关)
(a) 为什么需要在堆上申请空间?
(b) 动态内存分配函数有哪些?它们的功能是什么?
解答:
(a)
1,栈帧的大小必须在编译期确定,所以不能存储动态大小的数据
2,栈的大小是有限的,栈空间不能存储太大的数据
3,栈空间不适合存放线程之间的共享数据
(B)
1,malloc,分配 size 个字节的内存块,不对内存块进行清零;如果无法分配指定大小的内存块,返回空指针。
2,calloc,为NMEMB个元素的数组分配内存块,每个元素占SIZE个字节,并且对内存块进行清零,如果无法分配指定大小的内存块,返回空指针
3,realloc,调整先前分配内存块的大小,如果重新分配内存大小成功,返回指向新内存块的指针,失败返回空指针,并且旧内存块数据不发生改变
答案:
作业3:实现动态数组
设计一个动态数组,当数组空间不够时,它可以自动扩容。
typedef int E;
typedef struct {
E* elements; // 指向堆空间的数组
int size; // 元素的个数
int capacity; // 数组的容量
} Vector;
// 请实现下面方法
void push_back(Vector* v, E val);
// 在数组最前面添加元素,所有元素依次后移
void push_front(Vector* v, E val);
// 删除最后一个元素, 并把最后一个元素返回
E pop_back(Vector* v);
// 删除第一个元素,并把第一个元素返回
E pop_front(Vector* v);
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define FI_CAP 6
#define MAX_REALLOC 50
typedef int BI;
typedef struct {
BI* elements;
int cap;
int size;
}Vector;
//创建新的动态数组
Vector* vector_creat(void) {
Vector* v = malloc(sizeof(BI) * FI_CAP);
if (!v) {
printf("error in Vector* v \n");
exit(1);
}
v->elements = malloc(sizeof(BI) * FI_CAP);
if (!v->elements) {
printf("error in v->elements \n");
exit(1);
}
v->cap = FI_CAP;
v->size = 0;
return v;
}
//销毁
void destroy_vector(Vector* v) {
free(v->elements);
free(v);
}
//printf
void print_vector(Vector* v) {
//判空
if (!v->size) {
printf("vector size is 0\n");
return;
}
//遍历
printf("cap is %d,size is %d \n", v->cap,v->size);
for (int i = 0; i < v->size; i++) {
printf("%d ", v->elements[i]);
}
printf("\n");
}
//扩容
void grow_capacity(Vector* v) {
//判断容量
int new_cap = v->cap > MAX_REALLOC ? (v->cap + MAX_REALLOC) : (v->cap <<1);
//扩容
BI* new_ele = realloc(v->elements, new_cap*sizeof(BI)); //v->elements数组首地址
if (!new_ele) {
printf("error in v grow_capacity \n");
exit(1);
}
v->cap = new_cap;
v->elements = new_ele;
}
// 后加
void push_back(Vector* v, BI val) {
//判断容量//添加-更新
if (v->size == v->cap) {
grow_capacity(v); //v->element的值可能改变,v不变
}
v->elements[v->size++] = val;
}
// 在数组最前面添加元素,所有元素依次后移
void push_front(Vector* v, BI val) {
//判断容量//添加 //更新数组信息
if (v->size == v->cap) {
grow_capacity(v);
}
//size后移到size+1
for (int i = (v->size) + 1; i >0; i--) {
v->elements[i] = v->elements[i-1];
}
//添加元素
v->elements[0] = val;
v->size++; //更新元素个数,好好好只少了这一步就是崩溃级别的错误
}
// 删除最后一个元素, 并把最后一个元素返回
BI delete_last(Vector* v) {
//判空 //删除 更新数组信息
if (!v->size ) {
printf("vector size is 0\n");
exit(1);
}
return v->elements[v->size--];
}
// 删除第一个元素, 并把第一个元素返回
BI delete_first(Vector* v) {
//判空 //删除 //元素前移 更新数组信息
if (!v->size) {
printf("vector size is 0\n");
exit(1);
}
BI fir= v->elements[0];
for (int i = 0; i < v->size - 1; i++) {
v->elements[i] = v->elements[i+1];
}
v->size--;
return fir;
}
int main(void) {
Vector* vec= vector_creat();
print_vector(vec);
for (int i = 0; i < 5; i++) {
push_back(vec, i);
}
print_vector(vec);
for (int i = 10; i > 5; i--) {
push_front(vec, i);
}
print_vector(vec);
delete_last(vec);
print_vector(vec);
delete_first(vec);
print_vector(vec);
destroy_vector(vec);
return 0;
}
答案:
差不多
小TIPS,不能忘记加<stdlib.h>,编译器不提醒但是疯狂报错
realloc (指针,扩容后/缩容后 的字节数目)
#include "Vector.h" #include <stdio.h> #include <stdlib.h> #define DEAFAULT_CAPACITY 8 #define MAX_PREALLOCATE 1024 // 默认构造函数 Vector* vector_create(void) { Vector* v = malloc(sizeof(Vector)); if (!v) { printf("malloc failed.\n"); exit(1); } v->elements = malloc(DEAFAULT_CAPACITY * sizeof(E)); if (!v->elements) { free(v); printf("malloc failed.\n"); exit(1); } v->size = 0; v->capacity = DEAFAULT_CAPACITY; return v; } void vector_destroy(Vector* v) { free(v->elements); free(v); } void grow_capacity(Vector* v) { // 扩容策略 int new_capacity = (v->capacity <= MAX_PREALLOCATE) ? (v->capacity << 1) : (v->capacity + MAX_PREALLOCATE); E* p = realloc(v->elements, new_capacity * sizeof(E)); if (!p) { printf("Error: realloc failed.\n"); exit(1); } v->elements = p; v->capacity = new_capacity; } void push_back(Vector* v, E val) { // 添加在索引为 size 的位置 if (v->size == v->capacity) { grow_capacity(v); } // 把元素添加到末尾 v->elements[v->size] = val; v->size++; } // 在数组最前面添加元素,所有元素依次后移 void push_front(Vector* v, E val) { if (v->size == v->capacity) { grow_capacity(v); } // 将所有元素后移一位 for (int i = v->size; i > 0; i--) { v->elements[i] = v->elements[i - 1]; } // 将元素添加到第一个位置 v->elements[0] = val; v->size++; } // 删除最后一个元素, 并把最后一个元素返回 E pop_back(Vector* v) { return v->elements[--v->size]; } // 删除第一个元素,并把第一个元素返回 E pop_front(Vector* v) { E ret_value = v->elements[0]; // 将所有元素前移一位 for (int i = 0; i < v->size - 1; i++) { v->elements[i] = v->elements[i + 1]; } v->size--; return ret_value; }
作业4:枚举(返回月份天数)
定义一个枚举类型Month,包含一年中的12个月份,并为每个月份分配一个从1开始的数字值。
例如:January=1,February=2,依此类推....
编写一个函数,接收Month类型的参数,返回该月份有多少天。
(提示:使用switch语句)
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
typedef enum month{
JANUARY, FEBRUARY, MARCH,
APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER,
OCTOBER, NOVEMBER, DECEMBER,
}Month;
int main(void) {
Month mon1;
do{
scanf("%d", &mon1);
mon1 --;
switch (mon1) {
case JANUARY: printf("JANUARY has %3d days\n", 31); break;
case FEBRUARY:printf("FEBRUARY has %3d or %3d days\n", 28, 29); break;
case MARCH: printf("MARCH has %3d days\n", 31); break;
case APRIL: printf("APRIL has %3d days\n", 30); break;
case MAY: printf("MAY has %3d days\n", 31); break;
case JUNE: printf("JUNE has %3d days\n", 30); break;
case JULY: printf("JULY has %3d days\n", 31); break;
case AUGUST: printf("AUGUST has %3d days\n", 31); break;
case SEPTEMBER:printf("SEPTEMBER has %3d days\n", 30); break;
case OCTOBER: printf("OCTOBER has %3d days\n", 31); break;
case NOVEMBER:printf("NOVEMBER has %3d days\n", 30); break;
case DECEMBER:printf("DECEMBER has %3d days\n", 31); break;
}
} while (mon1 != 13);
return 0;
}
答案:
利用好CASE穿透,呜呜写作业的时候要多想一下
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdbool.h> // 定义枚举类型Month typedef enum { January = 1, // 手动设置为1,否则默认从0开始设置值 February, March, April, May, June, July, August, September, October, November, December } Month; bool isLeapYear(int year) { return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0); } // 定义函数,根据Month枚举返回该月的天数 int days_in_month(int year, Month month) { switch (month) { case January: case March: case May: case July: case August: case October: case December: return 31; case April: case June: case September: case November: return 30; case February: return isLeapYear(year) ? 29 : 28; default: // 错误处理 return -1; } } int main(void) { int year = 2024; Month month = February; printf("Days in February: %d\n", days_in_month(year, month)); month = April; printf("Days in April: %d\n", days_in_month(year, month)); return 0; }
21作业1:链表头插法
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
typedef struct node {
int data;
struct node* next;
}NOde;
void add_before_head(NOde** phead, NOde** ptail, int val) {
//新建结点
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) {
printf("error,in add_before_head\n");
exit(1);
}
//将新结点加入链表
new_node->next = *phead;
new_node->data = val;
//头结点改为新节点
*phead = new_node;
//补:第一个结点,要更新尾结点信息
if (*ptail == NULL) {
*ptail = new_node;
}
}
void print01(NOde** phead, NOde** ptail) {
NOde* curr = *phead;
while (curr->next) {
printf("%d ", curr->data);
curr = curr->next;
}
printf("%d \n", (*ptail)->data);
}
int main(void) {
NOde* head = NULL;
NOde* tail = NULL;
for (int i = 0; i < 4; i++) {
add_before_head(&head, &tail, i);
print01(&head, &tail);
}
return 0;
}
21作业2:链表尾插法
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
typedef struct node {
int data;
struct node* next;
}NOde;
void add_behind_tail(NOde** phead, NOde** ptail, int val) {
//新增结点
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) {
printf("error,in add_before_head\n");
exit(1);
}
new_node->next = NULL;
new_node->data = val;
//第一个元素更新head信息
if (*phead == NULL) {
*ptail=*phead = new_node;
} //不为空,更新尾部结点信息,
else {
(*ptail)->next = new_node;
*ptail= new_node;
}
}
void print01(NOde** phead, NOde** ptail) {
NOde* curr = *phead;
while (curr->next) {
printf("%d ", curr->data);
curr = curr->next;
}
printf("%d \n", (*ptail)->data);
}
int main(void) {
NOde* head = NULL;
NOde* tail = NULL;
for (int i = 0; i < 4; i++) {
add_behind_tail(&head, &tail, i);
print01(&head, &tail);
}
return 0;
}
21作业3:顺序链表插入
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef struct node{
int data;
struct node* next;
}NOde;
void add_behind_tail(NOde** phead, NOde** ptail, int val) {
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) {
printf("error,in add_before_head\n");
exit(1);
}
new_node->next = NULL;
new_node->data = val;
if (*phead == NULL) {
*ptail = *phead = new_node;
}
else {
(*ptail)->next = new_node; //尾结点的指针指向NEW
*ptail = new_node;
}
}
void add_in_sort(NOde** phead, NOde** ptail, int val) {
//新建结点
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) {
printf("error,in add_before_head\n");
exit(1);
}
//空链表--插入--尾插
new_node->data = val;
if (*phead == NULL) {
*phead = *ptail=new_node;
new_node->next = NULL;
return;
}
//非空--遍历
//第一个就满足 头插
if ((*phead)->data >= val) {
new_node->next = (*phead)->next; //插入
*phead = new_node; //更新
return;
}
//大于 结点 指针域指向的下一个结点的 data
NOde* curr = *phead;
while (curr->next) {
if (curr->next->data >= val) {
break;
}
curr=curr->next;
}
//中间满足 插在CURR的后面
// 插入到 curr 和 curr->next 之间
new_node->next = curr->next;
curr->next = new_node;
// 如果新节点插入在尾部,需要更新尾指针
if (curr == *ptail) {
*ptail = new_node;
}
//最后满足
}
void print01(NOde** phead, NOde** ptail) {
NOde* curr = *phead;
while (curr->next) {
printf("%d ", curr->data);
curr = curr->next;
}
printf("%d \n", (*ptail)->data);
}
int main(void) {
NOde* head = NULL;
NOde* tail = NULL;
add_in_sort(&head, &tail, -1);
print01(&head, &tail);
for (int i = 0; i < 4; i++) {
add_behind_tail(&head, &tail, i);
}
print01(&head, &tail);
add_in_sort(&head, &tail, -2);
print01(&head, &tail);
add_in_sort(&head, &tail, 2);
print01(&head, &tail);
add_in_sort(&head, &tail, 4);
print01(&head, &tail);
return 0;
}
21作业4:结构体排序(函数指针)
从键盘录入 5 个学生的信息,然后对学生进行排序。排序规则如下:先按总分从高到低进行排序,如果总分一样,依次按语文、数学、英语的分数从高到低进行排序;如果各科成绩都一样,则按名字的字典顺序从小到大排序。
学生结构体定义如下:
typedef struct {
int id;
char name[25];
char gender;
int chinese;
int math;
int english;
} Student;
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
char name[25];
char gender;
int chinese;
int math;
int english;
} Stu;
Stu* print_stu(Stu student[], int len) {
printf("\n");
for (int i = 0; i < len; i++) {
printf("%3d%6s%3c%5d%5d%5d\n",
student[i].id,
student[i].name,
student[i].gender,
student[i].chinese,
student[i].math,
student[i].english);
}
printf("\n");
}
//传入指向数组元素的指针
int comp(const void*p1,const void* p2 ) {
Stu* s1 = p1;
Stu* s2 = p2; //强制类型转换,否则无法解引用
//先按总分从高到低进行排序
int sum1 = s1->chinese + s1->english + s1->math;
int sum2 = s2->chinese + s2->english + s2->math;
if (sum1 != sum2) {
return sum2 - sum1;
}
//依次按语文、数学、英语的分数从高到低进行排序
if (s1->chinese != s2->chinese) { return s2->chinese - s1->chinese; }
if (s1->math != s2->math) { return s2->math - s1->math; }
if (s1->english != s2->english) { return s2->english - s1->english; }
//按名字的字典顺序从小到大排序
return strcmp(s1->name, s2->name);
}
int main(void) {
Stu stu[5];
for (int i = 0; i < 5; i++) {
scanf("%d%s %c%d%d%d",
&stu[i].id,
stu[i].name,
&stu[i].gender,
&stu[i].chinese,
&stu[i].math,
&stu[i].english);
}
print_stu(stu,5);
qsort(stu, 5, sizeof(Stu), comp);
print_stu(stu, 5);
return 0;
}
22作业1:链表的中间结点
求链表中间结点 (876. 链表的中间结点)
给定单链表的第一个结点 head
,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
struct ListNode* middleNode(struct ListNode* head);
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int data;
struct node* next;
}NOde;
void print_NOde(NOde**phead){
if (*phead==NULL) {
printf("linknode is NULL\n");
return; /*exit(1);不需要退出程序,难怪*/
}
NOde* curr= *phead;
while (curr->next) {
printf("%d ", curr->data);
curr = curr->next;
}
//打印最后一个元素
printf("%d \n", curr->data);
}
void push_back(NOde** phead, NOde** ptail, int val) {
//结点 判空 插入 更新链表信息
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) { //可能失败
printf("error in push_back\n");
exit(1);
}
new_node->data = val;
new_node->next = NULL;
if (!*phead) {
*phead = *ptail = new_node;
}
else {
(*ptail)->next = new_node;
*ptail = new_node;
}
}
NOde* middleNode(NOde* head) {
NOde* fast = head;
NOde* slow = head;
while (fast != NULL && fast->next != NULL) { //顺序,解引用空指针
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
int main(void) {
NOde* head = NULL;
NOde* tail = NULL;
print_NOde(&head);
for (int i = 0; i < 10; i++) {
push_back(&head, &tail, i);
}
print_NOde(&head);
printf("linknode's middlenode is %d \n", middleNode(head)->data);
return 0;
}
答案:
小心解引用空指针
/ 方法一 struct ListNode* middleNode(struct ListNode* head) { // 求链表的长度 len int len = 0; struct ListNode* curr = head; while (curr) { len++; curr = curr->next; } // 求索引为 len/2 的结点 curr = head; for (int i = 0; i < len/2; i++) { curr = curr->next; } return curr; } // 方法二 struct ListNode* middleNode(struct ListNode* head) { struct ListNode* fast = head; struct ListNode* slow = head; while (fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; } return slow; }
22作业2:判断单链表是否有环
判断单链表是否有环?(141. 环形链表)
给定一个链表的第一个节点 head
,判断链表中是否有环
bool hasCycle(struct ListNode *head);
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct node {
int data;
struct node* next;
}NOde;
//环不能使用打印函数
void print_NOde(NOde** phead) {
if (*phead == NULL) {
printf("linknode is NULL\n");
return; /*exit(1);不需要退出程序,难怪*/
}
NOde* curr = *phead;
while (curr->next) {
printf("%d ", curr->data);
curr = curr->next;
}
//打印最后一个元素
printf("%d \n", curr->data);
}
void push_back(NOde** phead, NOde** ptail, int val) {
//结点 判空 插入 更新链表信息
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) { //可能失败
printf("error in push_back\n");
exit(1);
}
new_node->data = val;
new_node->next = NULL;
if (!*phead) {
*phead = *ptail = new_node;
}
else {
(*ptail)->next = new_node;
*ptail = new_node;
}
}
bool hasCycle(struct ListNode* head) {
NOde* fast = head;
NOde* slow = head;
while (slow->next||fast!=NULL||fast->next!=NULL) {
slow = slow->next;
fast = fast->next->next;
if (fast == NULL || fast->next == NULL) {
return false;
}else if (slow->next == fast->next) { //可能fast已经是空指针,不能解引用
return true;
}
}
}
int main(void) {
NOde* head = NULL;
NOde* tail = NULL;
print_NOde(&head);
for (int i = 0; i < 10; i++) {
push_back(&head, &tail, i);
}
print_NOde(&head);
hasCycle(head) ? printf("have circle \n") : printf("no no no \n");
tail->next = head->next->next; //环 不能使用打印函数哦
hasCycle(head) ? printf("have circle \n") : printf("no no no \n");
return 0;
}
答案:
能出循环就是false,可以少写一个if
好好好,不要把循环退出条件和循环继续条件整混了
while (fast != NULL && fast->next != NULL(循环退出条件1)) {
if (slow == fast) { // fast 和 slow 再一次相遇,有环
return true;(循环退出条件2)
}
} // fast == NULL || fast->next == NULL, fast 到达末尾,无环bool hasCycle(struct ListNode *head) { struct ListNode* slow = head; struct ListNode* fast = head; while (fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; if (slow == fast) { // fast 和 slow 再一次相遇,有环 return true; } } // fast == NULL || fast->next == NULL, fast 到达末尾,无环 return false; }
22作业3:反转单链表
反转单链表 (206. 反转链表)
给定单链表的头节点 head
,请反转链表,并返回反转后的链表的头节点。
struct ListNode* reverseList(struct ListNode* head);
解答:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct node {
int data;
struct node* next;
}NOde;
void print_NOde(NOde** phead) {
if (*phead == NULL) {
printf("linknode is NULL\n");
return; /*exit(1);不需要退出程序,难怪*/
}
NOde* curr = *phead;
while (curr->next) {
printf("%d ", curr->data);
curr = curr->next;
}
printf("\n");
}
void push_back(NOde** phead, NOde** ptail, int val) {
//结点 判空 插入 更新链表信息
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) { //可能失败
printf("error in push_back\n");
exit(1);
}
new_node->data = val;
new_node->next = NULL;
if (!*phead) {
*phead = *ptail = new_node;
}
else {
(*ptail)->next = new_node;
*ptail = new_node;
}
}
NOde* reverseList(NOde* head) { //头插法 第一个结点,指向NULL,后面的指向前面的,
if (head == NULL || head->next == NULL) {
return head;
}
NOde* pre = head->next;
NOde* curr = head->next->next;//往后两个移位
head->next = NULL;
NOde* new_head = head;
while (curr->next) {
//pre指向的结点头插新链表
//pre=cur;
//cur=pre->next;//顺序
pre->next = new_head;
new_head = pre;
pre = curr;
curr = curr->next;
}
//最后2个结点
pre->next = new_head;
new_head = pre;
curr->next = new_head;
return curr;
}
int main(void) {
NOde* head = NULL;
NOde* tail = NULL;
for (int i = 0; i < 10; i++) {
push_back(&head, &tail, i);
}
print_NOde(&head);
NOde* re_head = reverseList(head);
print_NOde(&re_head);
return 0;
}
答案:
while(curr) // 循环结束: curr == NULL, prev指向反转后的第一个结点
next记录CURR的后继结点,在WHILE里面设置
递归:
边界条件,空链表和一个结点的链表
递归公式:
struct ListNode* result = reverseList(head->next);
- 递归调用
reverseList
,传入当前节点的下一个节点head->next
,并返回已反转子链表的新头节点result
。逆转节点指针:
head->next->next = head;
- 将当前节点的下一个节点的
next
指向当前节点,实现局部反转。- (反转都是next的指向改变了,整个结点的内存空间不会改变!绕了)
head->next = NULL;
- 当前节点的
next
指向NULL
,避免成环。// 方法一:循环方式 (要求掌握) struct ListNode* reverseList(struct ListNode* head) { struct ListNode* prev = NULL; struct ListNode* curr = head; while (curr) { // 保留curr的后继结点 struct ListNode* next = curr->next; // 反转当前结点 curr->next = prev; // 移动 prev 和 curr 指针 prev = curr; curr = next; } // 循环结束: curr == NULL, prev指向反转后的第一个结点 return prev; } // 方法二:递归方式 struct ListNode* reverseList(struct ListNode* head) { // 边界条件 if (head == NULL || head->next == NULL) { return head; } // 递归公式 struct ListNode* result = reverseList(head->next); head->next->next = head; head->next = NULL; return result; }
22作业4:合并两条有序链表
合并两条有序链表 (21. 合并两个有序链表)
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 (要求: 不能额外申请堆内存空间)。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2);
解答:写不出来
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct node {
int data;
struct node* next;
}NOde;
void print_NOde(NOde** phead) {
if (*phead == NULL) {
printf("linknode is NULL\n");
return; /*exit(1);不需要退出程序,难怪*/
}
NOde* curr = *phead;
while (curr->next) {
printf("%d ", curr->data);
curr = curr->next;
}
//打印最后一个元素
printf("%d \n", curr->data);
}
void push_back(NOde** phead, NOde** ptail, int val) {
//结点 判空 插入 更新链表信息
NOde* new_node = malloc(sizeof(NOde));
if (!new_node) { //可能失败
printf("error in push_back\n");
exit(1);
}
new_node->data = val;
new_node->next = NULL;
if (!*phead) {
*phead = *ptail = new_node;
}
else {
(*ptail)->next = new_node;
*ptail = new_node;
}
}
NOde* mergeTwoLists(NOde* head1, NOde* head2) {
//不相交的升序链表 直接接一起
//相交的升序链表 重叠部分接一起
//包含的升序链表 插中间
NOde* max = head1->data > head2->data ? head1 : head2;
NOde* min = head1->data < head2->data ? head1 : head2;
bool same1 = false;
bool same2 = false;
if ((!same1) && (!same2)) { //不相交的
//min的尾结点指向max的头结点
max = head1->data > head2->data ? head1 : head2;
min = head1->data < head2->data ? head1 : head2;
while (min->next) {
min = min->next;
};
min->next = max;
min = head1->data < head2->data ? head1 : head2;
return min;
}
}
int main(void) {
NOde* head = NULL;
NOde* tail = NULL;
for (int i = 0; i < 5; i++) {
push_back(&head, &tail, i);
}
print_NOde(&head);
NOde* head1 = NULL;
NOde* tail1 = NULL;
for (int i = 6; i < 9; i++) {
push_back(&head1, &tail1, i);
}
print_NOde(&head1);
NOde* new1=mergeTwoLists(head, head1);
print_NOde(&new1);
NOde* new2 = mergeTwoLists(new1, head1);
print_NOde(&new2);
return 0;
}
/*
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
(要求: 不能额外申请堆内存空间)。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2);*/
答案:
return dummy.next;
dummy是局部变量,dummy的next记录的地址不是局部变量的地址
尾插法——某个结点后面插入(两种情况:前面没有结点,前面有结点)——技巧,保证前面一定有结点——简化链表操作
NOde* dummy_node(不存数据项,哑结点,sentinal node 哨兵结点)
比较——小的插入链表,小的指向当前结点的指针前移(大的不动)——比较again——知道走完一条链表
NO分类讨论,找共性规律
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { struct ListNode dummy = {0, NULL}; // dummy node: 简化一些corner case的判断 struct ListNode* tail = &dummy; // 如果list1和list2还有结点 while (list1 && list2) { // 如果list1->val < list2->val, 将list1添加到curr结点的后面 if (list1->val < list2->val) { tail->next = list1; tail = tail->next; list1 = list1->next; } else { // 否则将list2添加到curr结点的后面 tail->next = list2; tail = tail->next; list2 = list2->next; } } // list1 == NULL || list == NULL // 循环结束后, 至少有一条链表为空. 将另一条链表添加到curr后面即可 if (list1) tail->next = list1; if (list2) tail->next = list2; // head->next即为合并后链表的第一个节点 return dummy.next; }