[英]adding to sorted linked list
我有一个使用结构的链表,现在用户必须将等级从最低到最高进行排序或从最高到最低进行排序。 我不能写第三个条件,例如,用户输入ACB程序将崩溃,所以我不知道这是什么问题。
我知道我的代码很乱:D并且我很抱歉那也请原谅我的英语不好。
这是结构:
typedef struct student
{
char sname[32];
char sid[9];
struct student *snext;
struct course *current;
struct course *head;
struct course *tail;
} STUDENT;
typedef struct course
{
char cname[15];
char grade;
struct course *next;
} COURSE;
这是函数:
void add_course(STUDENT *current){
int hx,cy,tmpz;
COURSE *tmp = current->current;
if(current->head!=NULL){
hx = current->head->grade;
cy = current->current->grade;}
//here the first element will be added
if(current->head==NULL){
current->head = current->current;
current->tail = current->current;
current->tail->next = NULL;
}
else{
//here it compares the head grade and the element i want to add
//so if the ascii number of head's grade is greater than the current's grade
//then the current will be the head and the previous head will be after the
//current head
if(hx>cy){
current->current->next = current->head;
current->head = current->current;
current->current = current->head;
while(current->current->next!=NULL)
current->current = current->current->next;
current->tail->next = current->current;
current->tail = current->current;
current->tail->next = NULL;
}
else{
//what i am trying to do here is e.g. if i have three elements already and
//their grades are ( first element: A second element: C third element: D)
//and i want to add a new element and its grade is B
//so the problem here it doesnt sort them it only crash in this condition
//i dont know what i am really doing here lol
//third condition
current->current = current->head;
hx = current->current->grade;
tmpz = tmp->grade;
while(current->current->next!= NULL && tmpz>hx){
current->current = current->current->next;
hx = current->current->next->grade;
}
current->current->next = tmp->next;
current->current->next = tmp;
current->current = tmp;
}
}
}
在原始代码中,此片段是可疑的:
if(current->head!=NULL){
hx = current->head->grade;
cy = current->current->grade;}
else{
}
if(current->head==NULL){
缩进表明在其自己的行上的紧密括号是一个闯入者。 您不需要一个空的else
块,并且缩进表明if (current->head == NULL)
代码应该是else
主体的一部分,而不是一组新的条件。 如果删除标识为闯入者的行,则功能末尾可能需要额外的大括号。
如果您清楚地缩进代码,则很容易看到代码块如何排列。 当您弄乱缩进时,很难发现这样的错误。 缩进用于使代码易于阅读和理解,而误导性缩进意味着代码难以阅读且难以理解和正确使用。
在修订后的代码中,去除了原始注释,重新格式化并缩进了可读性,并为讨论点添加了新注释,我们有:
void add_course(STUDENT *current)
{
int hx,cy,tmpz; // P1
COURSE *tmp = current->current; // P2
if (current->head != NULL) // P3
{
hx = current->head->grade;
cy = current->current->grade;
}
if (current->head == NULL)
{
current->head = current->current; // P4
current->tail = current->current;
current->tail->next = NULL; // P5
}
else
{
if (hx > cy) // P6
{
current->current->next = current->head;
current->head = current->current;
current->current = current->head;
while (current->current->next != NULL)
current->current = current->current->next;
current->tail->next = current->current;
current->tail = current->current;
current->tail->next = NULL;
}
else
{ // P7
current->current = current->head;
hx = current->current->grade;
tmpz = tmp->grade;
while (current->current->next != NULL && tmpz > hx)
{
current->current = current->current->next;
hx = current->current->next->grade;
}
current->current->next = tmp->next;
current->current->next = tmp;
current->current = tmp;
}
}
}
块的开口撑杆的位置值得商bat。 包括K&R在内的有些人喜欢与if
, else
, while
, for
, switch
等在同一行上的开括号。我非常喜欢缩进的Allman风格 ,但是在必要时使用局部约定。 当我重新格式化某些内容时,它会变成Allman风格-但是您不应该将其解释为我的不正当行为。 更改不属于对代码的任何评论。
您没有向我们展示没有课程记录的学生记录是如何表示的。 最合理的表示是head
, tail
和current
都初始化为NULL。
尽管名称为add_course()
,但是您的函数记录了一个学生记录,但是(令人惊讶地)没有添加课程。 假设在P4中使用current->current
,我们必须假设您在调用函数之前通过将current->current
设置为新课程来部分添加了课程,并且您需要该函数来固定该课程的位置。列表中的新课程开始head
和整理tail
。 这在功能上并不太紧密-这是一个糟糕的设计。 理想情况下,应将新的课程记录作为单独的参数传递给函数,并且next
字段应为NULL(课程名称应初始化,并且成绩必须初始化):
void add_course(STUDENT *student, COURSE *new_course);
我们可以观察到P1和P2处的变量直到P6或P7才使用,因此P1,P2和P3处的代码应移至P6或以后的位置。 我将坚持使用C89代码,而不是使用C99(以及C11和C ++)支持的“在任何地方定义”规则。
P4至P5的段落应该处理一个空列表。 可以将其编写为使用tmp
(在这种情况下,P2的定义应保留在该位置)。 写起来可能更清晰:
COURSE *new_course = current->current;
if (current->head == NULL)
{
current->head = new_course;
current->tail = new_course;
new_course->next = NULL;
}
如果在调用函数之前正确初始化了课程,则最终的分配是不必要的。
假设已经有一个与该学生相关的课程,并且通过调用代码将current->current
的值更改为新课程并调用此函数来添加新课程。 有几种可能的情况需要考虑:
该问题尚未指定在第一种和最后一种情况下应采取的措施。 当出现第二种情况时,应在现有课程之前插入新课程; 当出现第三条路线时,应在现有路线之后插入新路线。 为了确定性,如果新的分数相同,则应按字母顺序列出课程。 处理重复项包括在整个列表中搜索匹配项; 它可能应该封装到一个函数中:
COURSE *find_course(STUDENT *student, COURSE *course);
该函数采用一个学生,从搜索列表head
到tail
,课程名称,在列表中的课程设置的比较。 它返回列表中匹配的元素(如果有); 此处的代码仅要求函数返回NULL,表明未找到名称。
COURSE *find_course(STUDENT *student, COURSE *course)
{
COURSE *next_student = student->head;
while (next_student != NULL && strcmp(next_student->cname, course->cname) != 0)
next_student = next_student->next;
return next_student;
}
可以将此功能接口和实现更改为:
COURSE *find_course(COURSE *course, const char *cname)
{
while (course != NULL)
{
if (strcmp(course->cname, cname) == 0)
return(course);
course = course->next;
}
return(course);
}
这可用于搜索任何适当构造的课程列表,例如有效课程列表(以便您可以拒绝无效课程)。
我们还应该回顾当存在多个现有课程时应该发生的情况,以便避免重复的代码。 重复的课程检查是相同的,应该仍然排在第一位。 由于我们可以通过归纳法安全地假定空列表是有序的,而单个元素列表是有序的,因此我们可以决定add_course()
将始终确保课程列表是有序的。
但是,我将把它留给您继续进行。
我们将需要一个课程比较功能。 我们可以使用与strcmp()
相同的约定,如果第一个参数应位于第二个参数之前,则返回负数;如果第二个参数应位于第一个参数之前,则返回正数。 如果两个课程相同,则(名义上)为零:
int cmp_course(const COURSE *c1, const COURSE *c2)
{
if (c1->grade < c2->grade)
return -1;
else if (c1->grade > c2->grade)
return +1;
else
return(strcmp(c1->cname, c2->cname));
}
[...长时间停顿... 24小时或更长时间...]
这是您的代码,经过分解,被包装成一个可工作,正在编译,正在运行(C99)的程序。 除了重新格式化和添加断言之外,我根本没有更改add_course()
中的代码。
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
typedef struct student
{
char sname[32];
char sid[9];
struct student *snext;
struct course *current;
struct course *head;
struct course *tail;
} STUDENT;
typedef struct course
{
char cname[15];
char grade;
struct course *next;
} COURSE;
extern void add_course(STUDENT *current);
void add_course(STUDENT *current)
{
int hx,cy,tmpz;
COURSE *tmp = current->current;
assert(tmp != 0);
if (current->head != NULL)
{
hx = current->head->grade;
cy = current->current->grade;
}
if (current->head == NULL)
{
current->head = current->current;
current->tail = current->current;
current->tail->next = NULL;
}
else
{
if (hx > cy)
{
current->current->next = current->head;
current->head = current->current;
current->current = current->head;
while (current->current->next != NULL)
current->current = current->current->next;
current->tail->next = current->current;
current->tail = current->current;
current->tail->next = NULL;
}
else
{
current->current = current->head;
hx = current->current->grade;
tmpz = tmp->grade;
while (current->current->next != NULL && tmpz>hx)
{
current->current = current->current->next;
hx = current->current->next->grade;
}
current->current->next = tmp->next;
current->current->next = tmp;
current->current = tmp;
}
}
}
static void dump_student(FILE *fp, const char *tag, const STUDENT *student)
{
fprintf(fp, "Student: %s\n", tag);
fprintf(fp, "Name: %s; ID: %s\n", student->sname, student->sid);
fprintf(fp, "Next: 0x%" PRIXPTR "\n", (uintptr_t)student->snext);
fprintf(fp, "Current: 0x%" PRIXPTR "; ", (uintptr_t)student->current);
fprintf(fp, "Head: 0x%" PRIXPTR "; ", (uintptr_t)student->head);
fprintf(fp, "Tail: 0x%" PRIXPTR "\n", (uintptr_t)student->tail);
COURSE *cp = student->head;
while (cp != 0)
{
fprintf(fp, "Course: %-14s (%c) (0x%.16" PRIXPTR ")\n",
cp->cname, cp->grade, (uintptr_t)cp->next);
cp = cp->next;
}
}
int main(void)
{
STUDENT s1 = { "Yours Truly", "me", 0, 0, 0, 0 };
COURSE c[] =
{
{ "Math", 'B', 0 },
{ "English", 'A', 0 },
{ "Science", 'D', 0 },
{ "History", 'C', 0 },
{ "French", 'C', 0 },
};
dump_student(stdout, "Before", &s1);
for (int i = 0; i < 5; i++)
{
char buffer[8];
sprintf(buffer, "After%d", i+1);
s1.current = &c[i];
add_course(&s1);
dump_student(stdout, buffer, &s1);
}
return(0);
}
注意dump_student()
函数; 我发现具有此类接口的功能非常有用,并且经常将其保留在代码中以供以后调试。 FILE *
参数意味着可以将输出发送到标准错误(或日志文件),并使用标记来标识正在运行哪个调用。 如果需要,可以在接口中添加文件,行,函数名称; 我通常不这样做。
只有几个地方的代码是C99。 main()
的for
循环和dump_student()
的课程指针定义; 如果您的C编译器不支持C99语法,则可以移动变量定义。
这是Mac OS X 10.7.4上64位编译时的示例输出。
Student: Before
Name: Yours Truly; ID: me
Next: 0x0
Current: 0x0; Head: 0x0; Tail: 0x0
Student: After1
Name: Yours Truly; ID: me
Next: 0x0
Current: 0x7FFF643D84E0; Head: 0x7FFF643D84E0; Tail: 0x7FFF643D84E0
Course: Math (B) (0x0000000000000000)
Student: After2
Name: Yours Truly; ID: me
Next: 0x0
Current: 0x7FFF643D84E0; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English (A) (0x00007FFF643D84E0)
Course: Math (B) (0x0000000000000000)
Student: After3
Name: Yours Truly; ID: me
Next: 0x0
Current: 0x7FFF643D8510; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English (A) (0x00007FFF643D84E0)
Course: Math (B) (0x00007FFF643D8510)
Course: Science (D) (0x0000000000000000)
Student: After4
Name: Yours Truly; ID: me
Next: 0x0
Current: 0x7FFF643D8528; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English (A) (0x00007FFF643D84E0)
Course: Math (B) (0x00007FFF643D8528)
Course: History (C) (0x0000000000000000)
Student: After5
Name: Yours Truly; ID: me
Next: 0x0
Current: 0x7FFF643D8540; Head: 0x7FFF643D84F8; Tail: 0x7FFF643D84E0
Course: English (A) (0x00007FFF643D84E0)
Course: Math (B) (0x00007FFF643D8540)
Course: French (C) (0x0000000000000000)
请注意,插入的前几行很好,但是之后有问题。 我在valgrind
下运行,这为代码提供了明确的运行状况清单(尽管系统库之外没有动态内存分配)。
我建议您跟踪为什么在第三次插入后列表没有正确扩展。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.