自定义排序,总是强制0到升序的后退?

时间:2009-07-15 15:34:32

标签: c++ sorting

前提

这个问题有一个已知的解决方案(实际上如下所示),我只是想知道是否有人有更优雅的算法或任何其他想法/建议如何使这更可读,更有效或更健壮。

背景

我有一个体育比赛列表,我需要在一个数组中排序。由于这个数组的数量的性质,95%的时间列表将被预先排序,所以我使用改进的冒泡排序算法对它进行排序(因为它接近O(n)与几乎排序的列表)。

冒泡排序有一个名为CompareCompetitions的辅助函数,它比较两个竞争,如果comp1更大则返回> 0,如果comp2更大则返回< 0,如果两个相等则返回0。比赛首先按优先级字段进行比较,然后按游戏开始时间进行比较,然后按主页团队名称进行比较。

优先级字段是解决此问题的技巧。它是一个保持正值或0的int。它们排序为1为first,2为second,依此类推,但0或无效值始终为last。 例如优先事项清单
0,0,0,2,3,1,3,0 将被分类为 1,2,3,0,0,0,0,0

另一个小怪癖,这对问题很重要,是95%的时间,优先级将是默认值0,因为只有在用户想要手动更改时才会更改排序顺序很少。因此,比较函数中最常见的情况是优先级相等且为0。

守则

这是我现有的比较算​​法。

int CompareCompetitions(const SWI_COMPETITION &comp1,const SWI_COMPETITION &comp2)
{

    if(comp1.nPriority == comp2.nPriority)
    {
        //Priorities equal
        //Compare start time
        int ret = comp1.sStartTime24Hrs.CompareNoCase(comp2.sStartTime24Hrs);
        if(ret != 0)
        {
            return ret; //return compare result
        }else
        {
            //Equal so far
            //Compare Home team Name
            ret = comp1.sHLongName.CompareNoCase(comp2.sHLongName);
            return ret;//Home team name is last field to sort by, return that value
        }
    }
    else if(comp1.nPriority > comp2.nPriority)
    {
        if(comp2.nPriority <= 0)
            return -1;
        else
            return 1;//comp1 has lower priority
    }else /*(comp1.nPriority < comp2.nPriority)*/
    {
        if(comp1.nPriority <= 0)
            return 1;
        else
            return -1;//comp1 one has higher priority
    }
}

问题

如何改进此算法?
更重要的是......
有没有更好的方法强制0到排序顺序的后面?

我想强调一下,这段代码似乎工作正常,但我想知道是否有更优雅或更有效的算法,任何人都可以建议。请记住,nPriority几乎总是0,比赛通常按开始时间或主队名称排序,但优先级必须 始终 覆盖其他两个。

7 个答案:

答案 0 :(得分:2)

不仅仅是这个吗?

if (a==b) return other_data_compare(a, b);
if (a==0) return 1;
if (b==0) return -1;
return a - b;

答案 1 :(得分:1)

您还可以使用三元运算符减少一些代码详细程度,如下所示:

int CompareCompetitions(const SWI_COMPETITION &comp1,const SWI_COMPETITION &comp2)
{

if(comp1.nPriority == comp2.nPriority)
{
    //Priorities equal
    //Compare start time
    int ret = comp1.sStartTime24Hrs.CompareNoCase(comp2.sStartTime24Hrs);

    return ret != 0 ? ret : comp1.sHLongName.CompareNoCase(comp2.sHLongName);
}
else if(comp1.nPriority > comp2.nPriority)
    return comp2.nPriority <= 0 ? -1 : 1;
else /*(comp1.nPriority < comp2.nPriority)*/
    return comp1.nPriority <= 0 ? 1 : -1;
}

请参阅? 这个更短,在我看来很容易阅读 我知道这不是你要求的,但也很重要。

答案 2 :(得分:1)

如果案件nPriority1&lt; 0和nPriority2&lt; 0但nPriority1!= nPriority2其他数据未进行比较?

如果不是,我会使用像

这样的东西
int nPriority1 = comp1.nPriority <= 0 ? INT_MAX : comp1.nPriority;
int nPriority2 = comp2.nPriority <= 0 ? INT_MAX : comp2.nPriority;

if (nPriority1 == nPriority2) {
   // current code
} else {
   return nPriority1 - nPriority2;
}

将认为小于或等于0的值与最大可能值相同。

(请注意,如果您认为在最常见的路径中存在不敏感的比较,那么优化性能可能是不值得的。)

答案 3 :(得分:0)

如果可以,似乎修改优先级方案将是最优雅的,因此您可以正常排序。例如,不是将默认优先级存储为0,而是将其存储为999,并将用户定义的优先级设置为998.那么您将不再需要处理特殊情况,并且您的比较功能可以具有更直接的结构,没有if的嵌套: (伪代码)

if (priority1 < priority2) return -1;
if (priority1 > priority2) return 1;

if (startTime1 < startTime2) return -1;
if (startTime1 > startTime2) return 1;

if (teamName1 < teamName2) return -1;
if (teamName1 > teamName2) return -1;

return 0;  // exact match!

答案 4 :(得分:0)

我认为您对解决方案的不适应性来自零优先级异常的重复代码。 The Pragmatic Programmer解释说,您的来源中的每条信息都应该在“一个真实”的位置定义。对于那些阅读你的函数的天真的程序员来说,你希望异常在一个地方脱颖而出,与其他逻辑分开,这样它就很容易理解。怎么样?

    if(comp1.nPriority == comp2.nPriority)
    {
        // unchanged
    }
    else
    {
        int result, lowerPriority;
        if(comp1.nPriority > comp2.nPriority)
        {
            result = 1;
            lowerPriority = comp2.nPriority;
        }
        else
        {
            result = -1;
            lowerPriority = comp1.nPriority;
        }

        // zero is an exception: always goes last
        if(lowerPriority == 0)
            result = -result;

        return result;
    }

答案 5 :(得分:0)

我将它Java化了,但这种方法在C ++中可以正常工作:

int CompareCompetitions(Competition comp1, Competition comp2) {
    int n = comparePriorities(comp1.nPriority, comp2.nPriority);
    if (n != 0)
        return n;
    n = comp1.sStartTime24Hrs.compareToIgnoreCase(comp2.sStartTime24Hrs);
    if (n != 0)
        return n;
    n = comp1.sHLongName.compareToIgnoreCase(comp2.sHLongName);
    return n;
}

private int comparePriorities(Integer a, Integer b) {
    if (a == b)
        return 0;
    if (a <= 0)
        return -1;
    if (b <= 0)
        return 1;
    return a - b;
}

基本上,只需将特殊处理为零的行为提取到自己的函数中,然后按照排序优先级顺序迭代字段,一旦有非零值就返回。

答案 6 :(得分:0)

只要最高优先级不大于INT_MAX / 2,您就可以

#include <climits>

const int bound = INT_MAX/2;
int pri1 = (comp1.nPriority + bound) % (bound + 1);
int pri2 = (comp2.nPriority + bound) % (bound + 1);

这会将优先级0转换为bound并将所有其他优先级降低1.优点是您可以避免比较并使代码的其余部分看起来更自然。

在回复您的评论时,这是一个完整的解决方案,可以避免在优先级相同的95%情况下进行翻译。但请注意,您对此的担忧是错误的,因为这种微小的开销在这种情况下的整体复杂性可以忽略不计,因为等优先级的情况至少涉及对时间比较方法的函数调用,最坏的情况是额外调用名称比较器,这肯定比你比较优先级的任何事情都要慢一个数量级。如果您真的关心效率,请继续进行实验。我预测在这个帖子中表现最差且表现最佳的建议之间的差异不会超过2%。

#include <climits>

int CompareCompetitions(const SWI_COMPETITION &comp1,const SWI_COMPETITION &comp2)
{
    if(comp1.nPriority == comp2.nPriority)
       if(int ret = comp1.sStartTime24Hrs.CompareNoCase(comp2.sStartTime24Hrs))
          return ret;
       else
          return comp1.sHLongName.CompareNoCase(comp2.sHLongName);

    const int bound = INT_MAX/2;
    int pri1 = (comp1.nPriority + bound) % (bound + 1);
    int pri2 = (comp2.nPriority + bound) % (bound + 1);
    return pri1 > pri2 ? 1 : -1;
}

根据您的编译器/硬件,您可以通过用

替换最后一行来挤出更多周期
return (pri1 > pri2) * 2 - 1;

return (pri1-pri2 > 0) * 2 - 1;

或(假设2的补码)

return ((pri1-pri2) >> (CHAR_BIT*sizeof(int) - 1)) | 1;

最终评论:您真的希望CompareCompetitions返回1,-1,0吗?如果你需要它只是冒泡排序,你最好使用一个函数返回bool true如果comp1是“>=comp2并且false否则)。这将简化(尽管稍微)CompareCompetitions的代码以及冒泡分拣机的代码。另一方面,它会使CompareCompetitions不那么普遍。