微妙的快速排序稳定性问题

Posted

技术标签:

【中文标题】微妙的快速排序稳定性问题【英文标题】:Subtle Quicksort Stability Issue 【发布时间】:2012-02-10 20:41:31 【问题描述】:

我正在尝试为一个对 CSV 文件进行排序的学校项目创建一个快速排序实现,但我很难使用它。根据项目规范,对CSV文件的每一列进行排序会强制一个不稳定的排序变得稳定,即:“./sort --algorithm quicksort -k 1,2,3,4,5 input.csv”应该产生与“./sort --algorithm insert -k 1,2,3,4,5 input.csv”相同的结果

为了保留以前的排序,排序是反向执行的,如下所示:

for (int current_key = config.sort_columns.size()-1; current_key >= 0; current_key--)
    sorting_algorithm(records, config, config.sort_columns[current_key]-1);

其中 config.sort_columns 是 -k 参数指定的所有排序列的向量。

这是我的输入:

name,breed,date of birth,date of death,avg eggs per week 
Marilyn,Isa Red,2011-04-24,N/A,6 
Brian,Derp,2010-01-15,2011-12-01,4 
Chrissy,Ent,2012-02-08,N/A,3 
Mildred,Araucana,2011-05-01,N/A,3 
Jimmy,Ent,2006-02-30,N/A,15 
Mabel,Isa Red,2011-04-26,N/A,5 
Myrtle,Araucana,2011-08-01,N/A,0 
Myrtle,Araucana,2011-05-01,2011-07-13,0 
Adam,Ent,2010-01-01,N/A,10 
Isabel,Ent,2009-04-01,N/A,2 
Jimmy,Ent,2006-02-30,2011-03-24,1 
Jimmy,Derp,2003-02-30,2010-03-24,8 
Myrtle,Herp,2011-08-01,N/A,0 

这是我的插入排序的输出(应该并且看起来是正确的):

name,breed,date of birth,date of death,avg eggs per week 
Adam,Ent,2010-01-01,N/A,10 
Brian,Derp,2010-01-15,2011-12-01,4 
Chrissy,Ent,2012-02-08,N/A,3 
Isabel,Ent,2009-04-01,N/A,2 
Jimmy,Derp,2003-02-30,2010-03-24,8 
Jimmy,Ent,2006-02-30,2011-03-24,1 
Jimmy,Ent,2006-02-30,N/A,15 
Mabel,Isa Red,2011-04-26,N/A,5 
Marilyn,Isa Red,2011-04-24,N/A,6 
Mildred,Araucana,2011-05-01,N/A,3 
Myrtle,Araucana,2011-05-01,2011-07-13,0 
Myrtle,Araucana,2011-08-01,N/A,0 
Myrtle,Herp,2011-08-01,N/A,0

这是我的快速排序的输出:

name,breed,date of birth,date of death,avg eggs per week
Adam,Ent,2010-01-01,N/A,10
Brian,Derp,2010-01-15,2011-12-01,4
Chrissy,Ent,2012-02-08,N/A,3
Isabel,Ent,2009-04-01,N/A,2
Jimmy,Ent,2006-02-30,2011-03-24,1
Jimmy,Ent,2006-02-30,N/A,15
Jimmy,Derp,2003-02-30,2010-03-24,8
Mabel,Isa Red,2011-04-26,N/A,5
Marilyn,Isa Red,2011-04-24,N/A,6
Mildred,Araucana,2011-05-01,N/A,3
Myrtle,Herp,2011-08-01,N/A,0
Myrtle,Araucana,2011-08-01,N/A,0
Myrtle,Araucana,2011-05-01,2011-07-13,0

如您所见,这几乎是正确的,只是当第一列匹配时第二列是错误的(例如,“Derp”应该出现在两个“Ents”之前)。

最后,这是我的快速排序实现:

int sort_quick_partition(std::vector<Record> &records, bool (*comparison)(string, string), int sort_key, int left, int right)
    /*
    Partition the vector and return the address of the new pivot.

    @param less - Vector of elements less than pivot.
    @param greater - Vector of elements greater than pivot.
    */

    // Setup 
    int store_location;
    Record pivot = records[right];
    Record temp_record;

    // Loop through and partition the vector within the provided bounds
    store_location = left - 1;
    for (int j = left; j < right; j++)
        if (comparison(records[j].fields[sort_key],pivot.fields[sort_key]))
            store_location += 1;
            std::swap(records[store_location], records[j]);
        
    

    std::swap(records[store_location+1], records[right]);

    return store_location+1;


void sort_quick_helper(std::vector<Record> &records, bool (*comparison)(string, string), int sort_key, int left, int right)
    /*
    Actually performs the quick sort.

    @param sub_list - Vector to sort.
    @param *comparison - Comparison to perform.
    @param sort_key - Which column to sort by.
    @param left - Left side of active sort zone.
    @param right - Right side of active sort zone.
    */

    // Setup
    int new_pivot;

    // Make sure the list has 2 or more items
    if (left < right)
        // Partition the vector and get the new pivot value
        new_pivot = sort_quick_partition(records, comparison, sort_key, left, right);

        // Sort elements less than the pivot
        sort_quick_helper(records, comparison, sort_key, left, new_pivot-1);

        // Sort elements greater than the pivot
        sort_quick_helper(records, comparison, sort_key, new_pivot+1, right);
    


void sort_quick(std::vector<Record> &records, Configuration &config, int sort_key)
    /*
    Perform a quick sort on the records.

    @param &records - Vector of Record structures representing the file.
    @param &config - Reference to a global configuration structure.
    @param sort_key - Which column to sort by.
    */

    // Decide if it needs to be reversed
    bool (*comparison)(string, string);
    if (config.reverse)
        comparison = &comparison_gte;
     else 
        comparison = &comparison_lte;
    

    // Call the sort
    sort_quick_helper(records, comparison, sort_key, 0, records.size()-1);

请注意,“sorting_algorithm”是指向活动排序的函数指针,在本例中为“sort_quick”。

有谁知道可能出了什么问题?几天来我一直在努力解决这个问题,此时我正在拔头发。谢谢大家!

【问题讨论】:

在您的示例中,-k 是什么?如果是 1,2,3,4,5 这不是稳定性问题,因为无论稳定性如何,它都是错误的顺序。 在分区步骤不使用额外内存的情况下,很难编写稳定的快速排序实现。我建议研究稳定的分区算法作为起点。 是 -k 默认为 1,2,3,4,5。是的,我有点怀疑,但我真的不知道如何解决它。我从讲座幻灯片中几乎逐字复制了我的排序实现,但它似乎无法正确排序。这使我认为我的程序的其余部分可能是错误的(收集输入的部分,决定使用什么算法等),但后来我意识到这不可能,因为它与其他类型的预期一样。 【参考方案1】:

通过反复排序可以将不稳定的排序变成稳定的排序是不正确的。考虑最后的排序:当它看到相等的键时,不能保证保留以前的排序(这就是不稳定的意思)。

相反,您需要对明确排序输入的键进行排序 - 因此您需要进行一种排序,其中排序的键是所有列而不是单个列。

因此,当您比较记录“Myrtle,Araucana,2011-08-01,N/A,0”和“Myrtle,Araucana,2011-05-01,2011-07-13,0”时,您需要比较字段顺序,直到找到不相等的对。 (这称为字典顺序。)如果您需要保留完全相等记录的顺序,您甚至可能需要合并原始位置。

当然,如果这不是家庭作业,您可能会查看std::stable_sort。 (以相反的顺序对列进行一系列稳定的排序就可以了。)

【讨论】:

这是项目指南所说的:“可以通过按顺序指定每一列来强制不稳定的排序稳定,使用“-k”选项指定文件中的每一列。例如,“./csort --algorithm quicksort -k 1,2,3,4,5 chickens.csv”的输出将与“./csort -a insert -k 1,2, 3,4,5 chickens.csv””它说应该对每个字段使用 ASCII 字符串比较。 是的 - 问题是:-k 是指在不同的键上重复排序,还是用复合键排序一次?只有后者有效。 嗯,请您详细说明什么是复合键?我在网上阅读,我认为我必须做的是以相反的顺序对列进行排序,即“-k 1,2,3,4,5”表示按列 5-1 对整个列表进行排序。我现在使用的技术适用于稳定的排序(不确定这是否意味着什么或者这只是巧合) 在这种情况下,复合键意味着按顺序将所有列视为一个键,正如我在上面的回答中试图解释的那样。 Smith, Alan 在电话簿中排在 Smith, John 之前 - 姓氏相同,因此您按名字排序。 嗯,所以在我的 Record struct 向量中,我需要一些变量来跟踪是否应该根据给定列之前是否已排序而将其包含在排序中?【参考方案2】:

好吧,您的排序看起来很稳定,因为您选择了枢轴作为rightmost 元素。但是,最后一行。

std::swap(records[store_location+1], records[right]);

就是这样交换两条记录,即使它们相等。添加检查以仅在它们不相等时进行排序:

// You'll probably use your comparison() function here.
if ( records[store_location+1].fields[sort_key] != records[right].fields[sort_key] ) 
    std::swap(records[store_location+1], records[right]);

【讨论】:

我试过了,没有任何变化,不过感谢您的回复。

以上是关于微妙的快速排序稳定性问题的主要内容,如果未能解决你的问题,请参考以下文章

快速排序堆排序归并排序比较

选择排序快速排序插入排序等经典八大算法稳定性分析

快速排序

2golang之快速排序

深度解析(十六)快速排序

快速排序不能变成稳定排序吗?