【问题标题】:How do I use mergesort to sort the names that have the same surname?如何使用 mergesort 对具有相同姓氏的名称进行排序?
【发布时间】:2020-10-22 14:39:08
【问题描述】:

我们的教练给了我们一个 csv 文件,我们应该在其中根据姓氏按字母顺序对姓名进行排序。 但是也有同姓的名字 我的代码只适用于他们的姓氏。 当他们有相同的姓氏时,我不知道要添加什么来按他们的名字对他们进行排序。

这是我的代码:

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

#define people 11

struct list_people {
    char FirstName[20];
    char LastName[20];
    char Name[20];
    char Age[5];
};

typedef struct list_people Details;

int merge_sort();
int merge(Details *[11], int, int, int);

int main() {
    FILE *info;
    int i, j;
    Details A[people];
    Details *cell[people];
    
    for (i = 0; i < (people + 1); i++) {
       cell[i] = &A[i];
    }
    
    info = fopen("people.csv", "r");
    for (i = 0; i < people; i++) {
       fscanf(info," %[^,], %[^,], %8s", A[i].LastName, A[i].FirstName, A[i].Age);
    }
    fclose(info);
    
    merge_sort(cell, 1, people - 1);
    for (i = 1; i < people; i ++) {
        printf( "\t %-20s %-20s Age:%-20s \n", A[i].FirstName, A[i].LastName, A[i].Age);
    }
}
    
int merge_sort(Details *A[], int low, int high) {
    int mid;
    if (low < high) {
        mid = (low + high) / 2;
        merge_sort(A, low, mid);
        merge_sort(A, mid + 1, high);
        merge(A, low, mid, high);
    }
    return 0;
}
    
int merge(Details *A[], int low, int mid, int high) {
    int leftIndex = low;
    int rightIndex = mid + 1;
    int combinedIndex = low;
    int i, j;
    Details tempA[people];
    
    while (leftIndex <= mid && rightIndex <= high) {
        if (strcasecmp((A[leftIndex]->LastName), (A[rightIndex]->LastName)) <= 0) {
            tempA[combinedIndex] = *(*(A + leftIndex));
            combinedIndex++;
            leftIndex++;
        } else {
            tempA[combinedIndex] = *(*(A + rightIndex));
            combinedIndex++;
            rightIndex++;
        }
    }
    if (leftIndex == mid + 1) {
        while (rightIndex <= high) {
            tempA[combinedIndex] = *(*(A + rightIndex));
            combinedIndex++;
            rightIndex++;
        }
    } else {
        while (leftIndex <= mid) {
            tempA[combinedIndex] = *(*(A + leftIndex));
            combinedIndex++;
            leftIndex++;
        }
    }
    
    for (i = low; i <= high; i++) {
       *(*(A + i)) = tempA[i];
    }
    return 0;
}

【问题讨论】:

  • 您可以通过点击分数下方的灰色复选标记来接受答案。

标签: c mergesort


【解决方案1】:

您可以使用以下方式比较元素:

        int cmp;
        // compare the last names
        cmp = strcasecmp( ( A[leftIndex]->LastName), ( A[rightIndex]->LastName) );
        if (cmp == 0)
        {
            // last names are identical so compare the first names
            cmp = strcasecmp( ( A[leftIndex]->FirstName), ( A[rightIndex]->FirstName) );
        }
        if (cmp <= 0)
        {
            // ...
        }
        else
        {
            // ...
        }

【讨论】:

    【解决方案2】:

    代码中有多个问题:

    • 数组大小为people,因此初始化循环应排除索引值people。你应该写而不是for (i = 0; i &lt; (people + 1); i++):

        for (i = 0; i < people; i++)
      
    • 为避免缓冲区溢出,您应该在fscanf() 中指定每个目标数组要读取的最大字节数:

        fscanf(info," %19[^,], %19[^,], %4s", A[i].LastName, A[i].FirstName, A[i].Age);
      

      您还应该检查fscanf() 的返回值以检测无效输入。

    • 要订购具有相同姓氏的商品,您应该使用比较函数并比较LastName,然后是FirstName,然后是Age,返回第一个不相等的结果。

    这是一个例子:

    int compareDetails(const Details *a, const Details *b) {
        int res, age_a, age_b;
        if ((res = strcasecmp(a->LastName, b->LastName)) != 0)
            return res;
        if ((res = strcasecmp(a->FirstName, b->FirstName)) != 0)
            return res;
        age_a = atoi(a->Age);
        age_b = atoi(a->Age);
        return (age_a > age_b) - (age_a < age_b);
    }
    

    数组在 C 中基于零,因此索引值应从 0 开始。在 C 中,指定一个包含第一个索引并排除最后一个索引的数组切片也是惯用的。这允许空切片并使merge_sort 代码更简单,避免容易出错的+1/-1 调整。

    cell 指针数组是多余的,因为您对 Details 数组进行了适当的排序。你可以这样简化代码:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define PEOPLE 11
    
    struct list_people {
        char FirstName[20];
        char LastName[20];
        char Name[20];
        char Age[5];
    };
    
    typedef struct list_people Details;
    
    int merge_sort(Details A[], int low, int high);
    
    int main() {
        FILE *info;
        int i, j, n;
        Details A[PEOPLE];
        
        info = fopen("people.csv", "r");
        if (info == NULL)
            return 1;
    
        for (n = 0; n < PEOPLE; n++) {
           if (fscanf(info," %19[^,], %19[^,], %4s", A[n].LastName, A[n].FirstName, A[n].Age) != 3)
               break;
        }
        fclose(info);
        
        merge_sort(A, 0, n);
        for (i = 0; i < n; i++) {
            printf( "\t %-20s %-20s Age:%-20s \n", A[i].FirstName, A[i].LastName, A[i].Age);
        }
        return 0;
    }
        
    int compareDetails(const Details *a, const Details *b) {
        int res, age_a, age_b;
    
        if ((res = strcasecmp(a->LastName, b->LastName)) != 0)
            return res;
        if ((res = strcasecmp(a->FirstName, b->FirstName)) != 0)
            return res;
        age_a = atoi(a->Age);
        age_b = atoi(a->Age);
        return (age_a > age_b) - (age_a < age_b);
    }
    
    void merge(Details A[], int low, int mid, int high) {
        int leftIndex = low;
        int rightIndex = mid;
        int combinedIndex = 0;
        int i, j;
        Details tempA[high - low];
        
        while (leftIndex < mid && rightIndex < high) {
            if (compareDetails(&A[leftIndex], &A[rightIndex]) <= 0) {
                tempA[combinedIndex] = A[leftIndex];
                combinedIndex++;
                leftIndex++;
            } else {
                tempA[combinedIndex] = A[rightIndex];
                combinedIndex++;
                rightIndex++;
            }
        }
        while (leftIndex < mid) {
            tempA[combinedIndex] = A[leftIndex];
            combinedIndex++;
            leftIndex++;
        }
        while (rightIndex < high) {
            tempA[combinedIndex] = A[rightIndex];
            combinedIndex++;
            rightIndex++;
        }
        for (i = low; i < high; i++) {
            A[i] = tempA[i - low];
        }
    }
    
    int merge_sort(Details A[], int low, int high) {
        if (high - low >= 2) {
            int mid = low + (high - low) / 2;
            merge_sort(A, low, mid);
            merge_sort(A, mid, high);
            merge(A, low, mid, high);
        }
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      如果您有两个相同的姓氏,那么您需要与另一个属性进行比较并使用 that 来确定排序方式。

      首先,为了让事情更清晰,我们将从条件表达式中取出对strcasecmp 的调用,并将其结果保存到某个变量中:

      while ( leftIndex <= mid  &&  rightIndex <= high )
      {
        int lastNameResult = strcasecmp( A[leftIndex]->LastName, A[rightIndex]->LastName );
      

      然后,作为第一遍,我们将把 &lt;= 0 案例拆分为两个单独的案例:

        if ( lastNameResult < 0 )
        {
          // left < right, process as before
        }
        else if ( lastNameResult == 0 )
        {
          // new case to handle same last names
        }
        else
        {
          // left > right, process as before 
        }
      

      现在在这个新案例中,我们需要与第二个属性进行比较;通常的选择是FirstName:

        ...
        else if ( lastNameResult == 0 )
        {
          int firstNameResult = strcasecmp( A[leftIndex]->FirstName, A[rightIndex]->FirstName );
      
          if ( firstNameResult < 0 )
          {
            // left < right, handle the same way you did for LastName
          }
          else if ( firstNameResult == 0 )
          {
            // left == right, need to compare against another attribute
          }
          else
          {
            // left > right, handle the same way you did for LastName
          }
        }
        ...
      

      如果您遇到两个具有相同名字的条目,那么您需要选择 另一个 属性进行比较(例如 Age),并将其放入 else if ( firstNameResult == 0 ) 分支.或者,您可以合并 &lt; 和 == 案例,让重复的条目落在它们所在的位置。

      显然,这种方法不能很好地扩展多个属性,并且您最终会重复代码。更好的方法是将比较与合并分离。添加一个新变量,我们将其命名为mergeLeft,如果您需要从左侧列表合并,则设置为true(非零),如果您需要从右侧合并,则设置为false(0)。我们可以计算这个 而无需使用整个 if-else 链:

      while ( leftIndex <= mid  &&  rightIndex <= high )
      {
        int lastNameResult = strcasecmp( A[leftIndex]->LastName, A[rightIndex]->LastName );
        int firstNameResult = strcasecmp( A[leftIndex]->FirstName, A[rightIndex]->FirstName );
      
        /**
         * Yes, you can use logical expressions outside of an if condition.  The
         * parentheses aren't necessary in this case, but it should make the
         * expression easier to understand.  The result of this expression
         * will either be 0 or 1.  
         */
        int mergeLeft = lastNameResult < 0 || (lastNameResult == 0 && firstNameResult < 0);
      

      如果左边的LastName 小于右边的LastName,则mergeLeft 变量将设置为true (1),或如果姓氏相等且左边FirstName 小于右 FirstName。噗,那个丑陋的嵌套if 语句神奇地消失了,我们只剩下这个作为while 循环的主体:

        if ( mergeLeft )
        {
          tempA[combinedIndex++] = *A[leftIndex++];  // I've combined the updates
        }                                            // of combinedIndex and leftIndex
        else                                         // within these statements, 
        {                                            // just to save a little space
          tempA[combinedIndex++] = *A[rightIndex++]; // I also replaced pointer
        }                                            // notation with array notation
      }                                              // because it's less eye-stabby
      

      【讨论】:

        猜你喜欢
        • 2020-11-03
        • 2019-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多