在C中使用指针对冒泡排序单链接列表

Bec*_*Bec 0 c sorting linked-list list bubble-sort

我正在尝试使用C中的指针操作对单个链接列表进行冒泡排序。我已经在网站上查看了冒泡排序的其他一些实现,但是我觉得这里代码的逻辑应该是有道理的。即使这样,仍然进入无限循环。任何帮助将不胜感激!

int counter;
struct node* current = head;
struct node* previous = (struct node*) malloc(sizeof(struct node));
struct node* next = (struct node*) malloc(sizeof(struct node));

for (counter = 0; counter < num_nodes; counter++){
    current = head;
    next = current->m_next;

    while(next != NULL){
        int compare = strcmp(current->m_last_name, next->m_last_name);
        if (compare > 0){
            if (current == head){
                head = next;
            }
            previous->m_next = next;
            current->m_next = next->m_next;
            next->m_next = current; 

            previous = next;
            next = current->m_next;
        }
        else {
            previous = current;
            current = current->m_next;
            next = current->m_next;
        }
    }
}
printf("Loop completely done\n");
Run Code Online (Sandbox Code Playgroud)

}

M O*_*ehm 5

这里有几个问题。首先,为什么要为nextprevious节点指针分配内存?您正在排序,不涉及任何新节点,您只是在整理现有节点之间的链接。更重要的是,您重新分配了您要malloc编辑的指针,例如:

previous = current;
Run Code Online (Sandbox Code Playgroud)

这意味着previous丢失了分配的内存的值,从而丢失了所分配的内存。

接下来,排序后,您的头部指针很可能会不同。调用函数需要了解此更改。因此,您应该将指针传递给排序函数的节点指针。(我相信您将ndes添加到列表的函数也可以做到这一点。)

您的previous指针也是如此。如果这是一个节点指针,则仅是:另一个指针指向同一节点。更新时,您将更新列表结构外部的指针。因此,previous还应该是指向节点指针的指针。该指针首先与指向头节点的指针相同,然后next在遍历列表时指向先前节点的指针。如果更新指针,则prev更新列表结构中的指针,这就是您想要的。

此外,num_nodes在代码中的显式使用非常类似于列表。您应该使用列表结构本身来编写算法。

无限循环是由current交换后未正确更新引起的。鉴于malloced指针的误解,我对此并未进行过多研究。

这是一个可行的实现:

void list_bubble_sort(struct node **head) 
{
    int done = 0;         // True if no swaps were made in a pass

    // Don't try to sort empty or single-node lists
    if (*head == NULL || (*head)->m_next == NULL) return;

    while (!done) {
        struct node **pv = head;            // "source" of the pointer to the
                                            // current node in the list struct
        struct node *nd = *head;            // local iterator pointer
        struct node *nx = (*head)->m_next;  // local next pointer

        done = 1;

        while (nx) {
            int cmp = strcmp(nd->m_last_name, nx->m_last_name);

            if (cmp > 0) {
                nd->m_next = nx->m_next;
                nx->m_next = nd;
                *pv = nx;

                done = 0;
            }
            pv = &nd->m_next;
            nd = nx;
            nx = nx->m_next;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如何pv始终包含指向内部指针的源,nd以及如何通过指向头节点*head并不是什么特殊情况。

该算法反复从头到尾传递列表,直到不需要进行任何更改为止。有点粗糙,但毕竟是泡沫排序。

编辑在另一个答案中,aehrwyn解释了为什么会出现无限循环。他的修复程序有效,但是具有不必要地分配内存且从不真正使用内存的缺点,更不用说释放内存了。应用aehrwyn的发现,您的代码应如下所示:

struct node *bubble (struct node *head)
{
    int num_nodes = count(head);
    int counter;

    for (counter = 0; counter < num_nodes; counter++) {
        struct node* current = head;
        struct node* next = current->m_next;
        struct node* previous = NULL;

        while(next != NULL) {
            int compare = strcmp(current->m_last_name, next->m_last_name);
            if (compare > 0) {
                if (current == head){
                    head = next;
                } else {
                    previous->m_next = next;
                }
                current->m_next = next->m_next;
                next->m_next = current; 

                previous = next;
                next = current->m_next;
            }
            else {
                previous = current;
                current = current->m_next;
                next = current->m_next;
            }
        }
    }
    printf("Loop completely done\n");
    return head;
}
Run Code Online (Sandbox Code Playgroud)

注意事项:

  • 无需分配任何东西。指针nextprevious只是没有适当数据的“工作指针”。它们指向现有节点。

  • 我已经在外循环中移动了工作指针的定义。我认为,这反映了它们的范围,并使错误(例如忘记重新刻画它们)更容易发现。

  • 您分配previous->m_next。我认为您担心取消引用NULL指针,因此分配了一个虚拟节点。相反,您应该NULL在取消引用之前进行检查。在您的代码中,条件previous != NULL等于current == head,因此分配给previous->m_next可以在else子句中进行。实际上,if / else反映了两个基本情况:头节点和后续节点。

  • 在上面的(略显夸张的)解释中,我说过应该使用指向节点的指针,但这是因为我认为您的代码来自单独的函数。但这可能是的一部分main,因此可以使用简单的struct node *方法。请注意,这在返回新的头节点时也适用于单独的功能。您必须使用以下命令调用函数:

    head = bubble(head);
    
    Run Code Online (Sandbox Code Playgroud)

    我认为这容易出错,因为忽略返回值是合法的。我更喜欢上面解释的双指针方法。(旁注:我在SO上看到了很多代码,这是一个长main函数。学习将代码拆分为可重复使用的小型独立函数。乍一看似乎很乏味,但从长远来看这种编码风格带来了回报。)