【问题标题】:QuickSort on a doubly linked list not working as it should双向链表上的快速排序无法正常工作
【发布时间】:2020-02-29 15:45:03
【问题描述】:

我使用了过去用于数组的算法。这总是选择第一个元素作为枢轴。代码如下:

void quickSort(int a[],int l,int r,int *count)
{
    if(l<r-1)
    {
        *count=*count+r-l-1;
        int q=partition(a,l,r); //finding the pivot position in sorted array
        quickSort(a,l,q-1,count);     //recursive calling before pivot sub array
        quickSort(a,q+1,r,count);     //recursive calling after pivot sub array
    }
}

//partition function definition
int partition(int a[],int l,int r)
{
    int j,temp,i=l+1;

    for(j=l+1;j<r;j++)
    {
        //swap values if a[j]<=a[r](i.e. pivot)
        if(a[j]<=a[l])
        {
            temp=a[j];
            a[j]=a[i];
            a[i]=temp;
            i++;
        }
    }
    //place pivot at its position by swapping
    temp=a[i-1];
    a[i-1]=a[l];
    a[l]=temp;
    return i;
}

现在是我尝试将其实现为双向链表的时候。 “head”表示链表的头部

void recQuick(void* head, node* s, node* e, int (*comparator)(void*,void*)) {    
    //s = (node*) head;


    if ( e != NULL && s != e && s != e->next ) { //we want to cycle through the linked list

        node* pivot = (node*) realQuickSorter(head,s,e,(comparator)); 


        recQuick(head,s,pivot->prev, (comparator));

        recQuick(head,pivot->next,e, (comparator));
    }

    //return 1;

}

node* realQuickSorter ( void* head, node* s, node* e, int (*comparator)(void*,void*)) { 

    char* piv = s->str; //will always be the first element

    node* leader = s->next;

    //node* ii = s->next;

    node* follower = leader;


    for ( follower = leader; follower != e ; follower = follower->next ) {

        if ( ((comparator)(follower->str,s->str)) == 1 ) { //pivot is bigger, we need to swap

            swap(&(follower->str),&(leader->str));

            leader = (leader == NULL) ? s : leader->next;
            //leader = leader->next;
        }

    }

    swap(&(s->str),&(leader->prev->str));

    return leader->prev;
}

swap、bringMeEnd等功能正确

链表版本似乎只在乱序时交换前两个,其余保持不变

【问题讨论】:

  • 不回答您的问题,但最好在数组上使用快速排序。如果你有一个链表,那么合并排序可能会更容易更好。
  • @ybungalobill 这根本不是快速排序。
  • @EOF 你介意详细说明一下
  • 在快速排序中,您会将当前索引的元素与枢轴进行比较。您根本没有使用枢轴。另外,我建议使用 Hoare 分区,它对大脑的损伤较小。
  • 请在问题中添加足够的代码,以便可以编译和测试代码。

标签: c linked-list quicksort


【解决方案1】:

您尝试基于现有的数组实现来实现链表的快速排序。您可以通过交换节点的值来做到这一点。在我看来,这并不理想。链表不是数组。您应该交换节点。

(您的数据以链表形式存在可能是有原因的。如果节点的有效负载很大,则交换数据效率低下。如果您有指向节点的外部指针,则交换将使它们无效。)

快速排序如何工作?

  • 选择一个枢轴并将其从列表中删除。
  • 将剩余列表划分为
    1. 小于主元 (le) 的元素,
    2. 元素等于枢轴和
    3. 在某些度量上大于基准 (gt) 的元素。
  • 对分区运行快速排序,您的列表现在看起来像这样:
    已排序的 le 分区 |枢轴 |排序的gt分区

在数组实现中,您可以通过交换元素和移动枢轴来实现这一点。这是一个很好的策略,因为这样你需要额外的空间。

在链表中,您可以通过将当前链表抽取成两个分区表来创建分区。对它们调用快速排序,然后用中间的枢轴将它们再次缝合在一起。

如果您的列表包含许多具有相同值的元素,即比较相等,您可以使单节点枢轴成为第三个列表,eq

这里有一些代码可以做到这一点。功能

  • pop,将第一个节点从列表中弹出;
  • append 在列表末尾附加一个节点,
  • join,将第二个列表附加到第一个列表

没有显示,但算法本身应该是清楚的。这很简单。每个节点都有一个next 和 ´prevpointer as well as somedata; a list hasheadandtail` 指针。

不管怎样,这里是:

void quicksort(List *list)
{
    if (list->head != list->tail) {
        List eq = {NULL, NULL};
        List lt = {NULL, NULL};
        List gt = {NULL, NULL};

        append(&eq, pop(list));

        while (list->head) {
            Node *node = pop(list);

            int cmp = compare(node->data, eq.head->data);

            if (cmp < 0) {
                append(&lt, node);
            } else if (cmp > 0) {
                append(&gt, node);
            } else {
                append(&eq, node);
            }
        }

        quicksort(&lt);
        quicksort(&gt);

        join(list, &lt);
        join(list, &eq);
        join(list, &gt);
    }
}

这种排序算法是稳定的:具有相同值的元素在已排序和原始列表中具有相同的顺序。包含函数popjoinextract的完整示例程序是here on ideone

【讨论】:

  • 为什么不只列出 3 个列表, 枢轴,然后只在 2 个列表上递归!= 枢轴?
  • 我建议在代码前的简介中。应该是一个简单的改进。 ´:)`
  • 如果str是一个指向字符串的指针,那么每个节点的“值”就是一个指向字符串的指针,并且交换指针的开销小于重新链接节点交换它们的开销。按照您的建议创建列表可能会更快。
  • 你想达到什么目的,@rcgldr?我已经说过“如果有效载荷很大”并且您提供了一个不是的情况。我的回答是,链表的快速排序可能不应该像数组上的快速排序那样实现。我知道我没有回答 OP 的问题(“我的 qs 有什么问题?”),但我认为这个问题是错误的。
  • (我也知道我的答案并不理想:我使用的是单链表而不是双链表,其中提取和插入节点的代码不同,并且列表自己已经抓住了尾巴。)
【解决方案2】:

假设 e 是指向子列表中最后一个节点的指针,realQuickSorter() 中的 for 循环在将最后一个节点与枢轴进行比较之前停止。可能还有其他问题。

如果包含比较和交换函数以及生成测试列表和调用 recQuick() 的代码,将会有所帮助。


这是基于原始问题的示例代码。修复记录在 cmets 中。我更改了变量名称以匹配我拥有的一些旧代码。 followerleader 的名称与它们的使用方式相反。在我的示例代码中,我切换到使用pipj,作为指向索引ij 用于数组的节点等效项的指针。我将 compare 函数的意义反转为与 strcmp 相同(假设目标是从最低到最高的字符串值排序)。

recQuick 缺少对 lo (s) == NULL 的检查,如果枢轴最终位于第一个节点(其中枢轴->prev == NULL),则可能发生这种情况。 realQuickSorter 需要两个修复:在比较与枢轴时包含最后一个节点(hi 或e)。如果枢轴在最后一个节点中结束,则 pi (leader) 可能以 NULL 结束(如果 hi->next == NULL),因此在这种情况下进行检查并将 pi 设置为 hi,否则为设置为 pi->prev.

typedef struct node_{
    struct node_ * next;
    struct node_ * prev;
    char * str;
}node;

void recQuick(node* lo, node* hi, int (*comparator)(void*,void*))
{
    node* pv;
    if(lo == NULL || hi == NULL || lo == hi || lo == hi->next) /* fix */
        return;
    pv = (node*) realQuickSorter(lo,hi,(comparator));
    recQuick(lo, pv->prev, (comparator));
    recQuick(pv->next, hi, (comparator));
}

node* realQuickSorter(node* lo, node* hi, int (*comparator)(void*, void*))
{ 
    node* pi = lo->next;
    node* pj;
    for(pj = pi; pj != hi->next ; pj = pj->next ){  /* fix */
        if(((comparator)(pj->str, lo->str)) <= 0 ){ /* changed comparator */
            swap(&(pj->str),&(pi->str));
            pi = pi->next;                          /* fix */
        }
    }
    if(pi == hi->next)                              /* fix (hi->next could be NULL) */
        pi = hi;                                    /* fix */
    else                                            /* fix */
        pi = pi->prev;                              /* fix */
    swap(&(lo->str),&(pi->str));                    /* fix */
    return pi;                                      /* fix */
}

这是一种对链表进行排序的低效方法。 M Oehm 的答案应该会好一些,但链表的自下而上合并排序会更快:

https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation_using_lists

在具有分散节点的大型列表中,无论使用什么算法,每个节点访问都可能涉及缓存未命中。假设内存足够,将列表数据复制到数组中,对数组进行排序,新建链表会更快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-13
    • 2018-05-22
    • 1970-01-01
    • 2016-02-28
    • 1970-01-01
    • 2015-05-15
    • 1970-01-01
    相关资源
    最近更新 更多