我该如何按字母顺序排列此链表?

时间:2018-04-12 19:29:11

标签: c linked-list

我正在编写一个包含姓名和年龄的程序。然后可以调用这些名称,并打印出该人的年龄。如果该人不在列表中,则将其年龄打印为-1。如果使用列表中已有的新年龄输入名称,则不会添加新条目。目前看来名称按我输入的顺序排序。如何通过仅更改函数add的代码按字母顺序对它们进行排序?此代码是可编译的,除了非按字母顺序排列的列表外,可以按预期工作。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct person {
    char *name;
    int age;
    struct person *next;
} Person;

void print(Person *); // prints the entire list
Person *add(Person *, char *, int); // adds a new node to the list
int getAge(Person *, char *); // returns the age of the person or -1 if not found

int main(void) {
    char input1[100];
    int input2;
    Person *myList = NULL;
    printf("Enter a person's name (one word) and age : ");
    scanf("%s %d", input1, &input2);
    while (input2 != 0) {
        myList = add (myList, input1, input2);
        printf("\n\nThe list is now : ");   print(myList);
        printf("Enter a name (one word) and age, enter 'xxx' and 0 to exit : ");
        scanf("%s %d", input1, &input2);
    }
    printf("\n\nThe final list is ");   print(myList);
    printf("\n\nEnter the name of a person to look up their age : ");
    scanf("%s", input1);
    while ( strcmp(input1, "xxx") != 0 ) {
        printf("\t%s is %d years old\n", input1, getAge(myList, input1) );
        printf("Enter a name to look up their age or 'xxx' to exit : ");
        scanf("%s", input1);
    }

    return 0;
}

void print(Person *ptr) {
    while (ptr) { printf("[%s-%d] ", ptr->name, ptr->age); ptr = ptr->next; }
    printf("\n");

    return;
}

//adds person to list if the person does not exist already
Person *add(Person *ptr, char *n, int a) {
    Person *newNode = malloc( sizeof(Person) );
    int duplicate = 1;
    Person *dummy = ptr;
    while (dummy) {
        if(strcmp(dummy->name, n) == 0) {
            printf("Name Already Exists in List! Please retry with other name..\n");
            duplicate=-1;
            break;
        }
        else
            dummy = dummy->next;
    }
    if (duplicate!=-1) {
        newNode->name = malloc( strlen(n) + 1 );
        strcpy(newNode->name, n);
        newNode->age = a;
        newNode->next = ptr;
        return newNode;
    }
    duplicate = 1;
    return ptr;
}

//function to find age of the passed person
int getAge(Person *ptr, char *name) {
    while (ptr) {//while loop to traverse entire linked list elements (All persons one by one)
        if(strcmp(ptr->name, name) == 0) //comparing person name in the list with the search key name
            return ptr->age; //if found, returning the age of that person
        else
            ptr = ptr->next; //if not found, check in next node of linked list
    }

    return -1; // if not found, even after visting all nodes, return -1
}

3 个答案:

答案 0 :(得分:1)

您可以进行插入排序。每次添加新记录时,都会扫描列表以查看它所属的位置并将其插入其中。这可以与扫描重复项结合使用。

Person *add(Person *head, char *n, int a) {
  char empty[1] = "";
  Person sentinel = {0};
  sentinel.name = empty;
  sentinel.next = head;
  Person *p = &sentinel;
  while (p) {
    int cmp = p->next ? strcmp(n, p->next->name) : -1;
    if (cmp == 0) {
      printf("Name Already Exists in List! Please retry with another name..\n");
      break;
    }
    if (cmp < 0) {
      Person *newNode = malloc( sizeof(Person) );
      newNode->name = malloc( strlen(n) + 1 );
      strcpy(newNode->name, n);
      newNode->age = a;
      newNode->next = p->next;
      p->next = newNode;
      break;
    }
    p = p->next;
  }
  return sentinel.next;  // a possibly-updated copy of head
}

插入排序总是将新元素与 next 元素(而不是当前元素)进行比较。这使得处理第一个元素变得笨拙,特别是在列表中。我们用一个临时的&#34;哨兵&#34;来解决这个问题。我们假装的就在名单的前面。

还有其他方法。您可以在列表的开头创建新节点,然后向下滑动直到它处于适当位置。如果遇到重复项,则删除新项并修补列表。其他数据结构中的插入排序通常从尾部朝向头部进行,但是不能使用单链表。

答案 1 :(得分:1)

我写了类似的地方,我对学生ID进行了分类。你应该尝试进行交换。声明一个临时变量并用它来交换。代码就像是。

int temp,
    first_name,
    last_name;

temp = first_name;
first_name = last_name;
last_name = temp;

希望能给你一个想法!

编辑:对方建议的是一个好主意,插入排序。

答案 2 :(得分:0)

您的问题已经得到解答,但我想指出另一种添加节点的方法。该列表由其头节点定义。当使用元素时,头节点可以改变。 (它将在您当前的代码中更改,这会在前面添加元素。)为了反映更改,您将返回新头:

myList = add(myList, input1, input2);

这是多余的,因为您必须指定myList两次。丢弃函数返回的结果也是合法的,因此可能会出错。如果传入指向头指针的指针,则将消除冗余。

添加(&amp; myList,input1,input2);

'&amp; will indicate that myList`可能会改变。现在,您可以将返回值用于其他内容,例如指向新插入节点的指针,如果名称已存在,则返回null。

在前面插入一个人(无条件)看起来像这样:

Person *add_front(Person **ptr, const char *name, int age)
{
    Person *p = person_new(name, age);

    p->next = *ptr;
    *ptr = p;

    return p;
}

最后插入需要首先遍历列表:

Person *add_back(Person **ptr, const char *name, int age)
{
    Person *p = person_new(name, age);

    while (*ptr) {
        ptr = &(*ptr)->next;
    }

    p->next = *ptr;
    *ptr = p;

    return p;
}

请注意,您不需要将空列表视为特殊情况。 Adrian的解决方案消除了具有哨兵元素的特殊情况。在这里,它被指针本身消除:ptr首先指向调用函数中的列表头指针。当您浏览列表时,它指向前一个节点的next指针。更新*ptr更新了将我们带到当前节点的指针。

函数person_new创建一个新节点。我发现将它作为一个单独的函数更加温和,您可以从其他函数调用它:

Person *person_new( const char *name, int age)
{
    Person *p = malloc(sizeof(*p));

    p->name = malloc(strlen(name) + 1);
    strcpy(p->name, name);
    p->age = age;
    p->next = NULL;

    return p;
}

现在,您想要的函数按字母顺序插入节点,如下所示:

Person *add(Person **ptr, const char *name, int age)
{
    while (*ptr && strcmp((*ptr)->name, name) < 0) {
        ptr = &(*ptr)->next;
    }

    if (*ptr == NULL || strcmp((*ptr)->name, name) != 0) {
        Person *p = person_new(name, age);

        p->next = *ptr;
        *ptr = p;

        return p;
    }

    return NULL;
}

当您按名称查找节点时,如果当前节点按字母顺序大于您要查找的名称,则可以停止搜索:

const Person *find(const Person *ptr, const char *name)
{
    while (ptr) {
        int cmp = strcmp(ptr->name, name);

        if (cmp > 0) break;
        if (cmp == 0) return ptr;

        ptr = ptr->next; 
    }

    return NULL;
}

您可以在行动here中看到代码。