对重新编号最小的项目进行排序

时间:2009-08-12 19:19:14

标签: algorithm language-agnostic sorting

我需要快速将重新排序的序列保存回我的项目的整数sortOrder列。

简单的重新编号方法可能很慢 - 如果最后一项移到第一项,则修改所有N行。多行更新语句可以让数据库完成工作,但我想探索更智能的方法,比如使用sortOrder浮点,除了我没有这个选项:(

我想象的解决方案将采用这样的重新编号列表:(100,200,1700,300,400 ... 1600,1800,...)并生产(100,200,250,300,400 ... 1600,1800,...)(通过修改这个例子中的一行)。起初看起来很简单,但我很难在代码中表达它......

有人可以帮我解释这个逻辑吗?可能会有相邻项目的序列需要转移以适应新的项目 - 我希望有人可能已经写好了吗?它必须比我现在的速度更快,但仍然可读且易于理解/维护。

好的,回答后,回复了我想出的结果代码(欢迎评论):

/**
 * Renumber list with minimal changes
 * 
 * Given a list of SortOrderNumbers in the 'new' sequence they are to be saved in, determine the
 * minimal set of changes (described by Change(i, newSortOrderNumber)) that can be saved that
 * results in a properly ordered sequence.
 * 
 * A simple answer would always be List(change<1,1>, change<2,2>, ...change<n,n>) which is of length N.
 * This method returns a set of changes no larger than N (often much smaller for simple moves).
 * 
 * @author Jim Pinkham
 * @param s
 * @return Set<Change>
 */
private Set<Change> renumber(int[] s) {
    Set<Change> ret = new HashSet<Change>();
    // pass1 goes from start forwards looking for decrease in numbering
    for (int i=1; i<s.length; i++) {
        // if predecessor of item is larger and it's large enough to renumber from start of list 
        if (s[i-1]>s[i] && s[i]>i) {                
            int beforeStart=0;
            int beforeIndex=-1;
            // go back towards start looking for anchor
            for (int j=i-2; j>=0; --j) {
                int diff = s[i]-s[j];
                if (diff>(i-j)) {
                    beforeIndex=j;
                    beforeStart=s[beforeIndex];
                    break;
                }
            }
        int diff = s[i]-beforeStart;
        int stepsToDiff=i-beforeIndex;
            int delta = diff/stepsToDiff;
            // renumber from start of list or anchor thru decrease
            int fixCnt=0;
            for (int j=beforeIndex+1; j<i; ++j) {
                s[j] = beforeStart + (delta*++fixCnt); 
                System.out.println("s["+j+"]="+s[j]);
                ret.add(new Change(j, s[j]));
            }
        }
    }
    // pass1 could leave some decreases in sequence  
    // pass2 goes from end back to start
    for (int i=s.length-1; i>0; i--) {
        // if predecessor of item is larger 
        if (s[i-1] > s[i]) {
            int afterIndex=s.length;
            int delta=DEFAULT_RENUMBER_GAP;
            // go back towards end looking for anchor               
            for (int j=i; j<s.length; ++j) {
                int diff = s[j]-s[i-1];
                if (diff>(j-(i-1))) {
                    afterIndex=j;
                    int afterEnd=s[afterIndex];
                    int stepsToDiff=afterIndex-(i-1);
                    int gap = afterEnd-s[i-1];
                    delta = gap/stepsToDiff;
                    break;
                }
            }
            // renumber from decrease thru anchor or end of list
            int fixCnt=0;
            for (int j=i; j<afterIndex; ++j) {
                s[j] = s[i-1] + (delta*++fixCnt); 
                System.out.println("s["+j+"]="+s[j]);
                ret.add(new Change(j, s[j]));                   
            }
        }
    }
    return ret;
}
class Change { 
    int i; 
    int sortOrder; 
    Change(int i, int sortOrder) { 
        this.i=i; this.sortOrder=sortOrder; 
    }
    public boolean equals(Change other) {
        return Integer.valueOf(i).equals(Integer.valueOf(other.i));
    }
    public int hashCode() {
        return Integer.valueOf(i).hashCode();
    }
}

3 个答案:

答案 0 :(得分:2)

  

我想探索更智能的方法,比如使用sortOrder浮点除了我没有那个选项

如果你发现在浮点方面更容易想到它,为什么不把数字想象成固定点。

e.g。出于算法的目的,将1000000解释为100.0000。您需要选择点位置,以便给出尽可能多的十进制(或二进制)位数(数组中的最大项数+ 2)与整数大小。因此,假设最大条目数为998,您需要在该点前3位数,其余条目可用于“空白”。

然后,移动操作可以简单到将其新的排序数设置为任一侧的项目的排序数量的总和的一半,即,在其新邻居之间插入移动的项目。使用0和size(array)+1作为结束案例。我再次假设您的用户界面可以记录用户完成的移动 - 无论我认为它应该是相当简单的,并且可能会使用标准排序算法,只需重新定义'交换'。

所以例如在这个数组中移动到最后一个(带有虚数小数点):

1.0000 2.0000 3.0000 4.0000 5.0000

变为

1.0000 2.0000 3.0000 4.0000 0.5000 =(0.0000 + 1.0000)/ 2

给出

的排序顺序

0.5000 1.0000 2.0000 3.0000 4.0000

只更改了一条记录,即数组中的最后一条记录

将最后一个移到第二个会这样做:

1.0000 2.0000 3.0000 4.0000 5.0000

变为

1.0000 2.0000 3.0000 4.0000 1.5000 =(1.0000 + 2.0000)/ 2

导致

的排序顺序

1.0000 1.5000 2.0000 3.0000 4.0000

再次,只有一条记录改变了。

你仍然需要照顾你两个号码之间的空间不足的情况,你最终会这样。我认为无论算法如何都是如此。这将需要'swap'重新编号更多条目以腾出更多空间。无论算法如何,我认为你不能排除所有必须重新编号的情况,这将是非常不可能的。我还怀疑随着时间的推移,广泛的重新编号变得更有可能,无论你做什么 - 可用空间都会碎片化。然而,通过选择点的位置以尽可能多地提供空间,它应该是最佳的,即你尽可能地推迟。

为了避免在不方便的时候进行更广泛的重新编号,可能建议在安静期间定期进行某种批量重新编号 - 基本上再次拉伸间隙以便为进一步的用户驱动排序腾出空间。同样,无论你使用什么方法,我认为你可能都需要这个。

这只是一个草图,我认为它可能等同于任何其他方式,尽管可能是一种更直观/可维护的思考方式,以及最大化扩展空间的方法。此外,如果您真的担心退化情况的性能不佳 - 并且从您的描述中听起来应该是这样 - 我建议您在具有大量随机数据的测试工具中运行您使用的任何算法(无数据库)在很长一段时间内,看看它在实践中确实执行了多少重新编号,特别是看它是否随着长期使用而降级。我怀疑任何算法都会这样。

答案 1 :(得分:1)

按照你的例子,你可以这样做:

走你的数字阵列。如果项x的后继项小于x,则向后遍历数组,直到找到y和x + 1之间的最小差异的项y。计算你向后走的步数,取最小距离,从y向前走,并将项目设置为y +((x + 1)-y)/ count。

答案 2 :(得分:0)

额外的间接水平可能会有所帮助,例如:为每一行实现一个可重定位的句柄来代替行索引。因此,不处理row [x],而是处理row [handle [x]]

编辑:好的,所以这在你的情况下是不可能的...你能澄清一下你期望的重新订购量吗?

我从问题的短语中收集到你预计只有M个N项移动,其中M明显小于N.所以你想要避免N个更新 - 你宁愿有类似M更新的东西。

如果M小于N / 2,则根据交换操作定义重新排序应该更快。你没有说你的UI是什么,但无论如何用户可能正在有效地进行交换操作。因此,通过记录这些,或使用标准排序算法从原始状态进入所需状态,您应该能够生成重新排序元素所需的M个交换操作集。这应该只需要M * 2行更新 - 即如果只有两个项目交易位置,则只需更新2行。

可能会有一些退化的情况,这实际上比只重写所有内容要慢 - 虽然如果问题暗示只是用户重新排序的东西,似乎不太可能。