添加到已排序的链接列表

时间:2012-08-03 19:45:32

标签: c

我有一个使用结构的链表,现在用户可以添加从最低到最高的等级,或从最高到最低。 我无法写出第三个条件 例如用户进入A C B程序将崩溃所以我不知道是什么问题。

我知道我的代码是一团糟: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;

        }
    }

}

1 个答案:

答案 0 :(得分:3)

在原始代码中,这个片段是可疑的:

if(current->head!=NULL){
hx = current->head->grade;
cy = current->current->grade;}
else{
}
    if(current->head==NULL){

压痕表明它自己的紧密支撑是一个闯入者;你不需要一个空的else块,并且缩进表明if (current->head == NULL)代码应该是else的主体的一部分,而不是一组新的条件。如果删除标识为interloper的行,则可能需要在函数末尾添加一个额外的大括号。

如果您干净地缩进代码,那么很容易看出这些块是如何排列的。当你弄乱了缩进时,很难发现这样的错误。缩进用于使代码易于阅读和理解,误导性缩进意味着代码难以阅读,难以理解和正确。


在修订后的代码中,删除了原始评论,重新格式化并缩小了易读性,并注释了对讨论点的新评论,我们有:

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;
        }
    }
}

块的开括号的位置是值得商榷的。有些人,包括K& R,喜欢与ifelsewhileforswitch在同一行的左大括号,我非常喜欢缩进的Allman Style,但在必要时使用当地的约定。当我重新格式化某些东西时,它会变成奥尔曼风格 - 但你不应该把它解释为除了我之外的其他任何东西。此更改不是对您的代码进行任何批评的一部分。

您没有向我们展示如何代表没有课程记录的学生记录。最合理的表示是headtailcurrent都被初始化为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的值更改为新课程,并调用此函数。有几种可能的情况需要考虑:

  1. 新课程是(现有)课程的重复(并且应该被拒绝 - 但是该功能没有返回值,因此无法表明该课程被拒绝)
  2. 新课程成绩低于现有课程成绩。
  3. 新课程成绩高于现有课程成绩。
  4. 新课程成绩与现有课程成绩相同。
  5. 问题没有说明应该对第一个和最后一个情况做什么。当出现第二种情况时,应在现有课程之前插入新课程;当第三个出现时,应在现有课程之后插入新课程。确切地说,如果新等级相同,那么课程应按字母顺序列出。处理重复项涉及搜索整个列表以进行匹配;它应该被封装成一个函数:

    COURSE *find_course(STUDENT *student, COURSE *course);
    

    该功能需要学生,从headtail搜索列表,将课程名称与列表中的课程进行比较。它返回列表中匹配的元素(如果有的话);这里的代码只需要函数返回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; for中的main()循环和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下运行,这为代码提供了一个干净的健康状况(尽管系统库外没有动态内存分配)。

    我建议您在第三次插入后跟踪列表未正确扩展的原因。