【问题标题】:Sort items with minimal renumber以最少的重新编号对项目进行排序
【发布时间】:2010-11-19 02:01:30
【问题描述】:

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

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

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

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

好的,回答后,将我想出的结果代码发回(欢迎 cmets):

/**
 * 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();
    }
}

【问题讨论】:

  • 对不起,我显然是新来的 - 如果有人可以让它可读,请随意。
  • 您可以编辑自己的问题并在其中添加源代码并将其标记为“编辑:”或类似的内容。

标签: algorithm language-agnostic sorting


【解决方案1】:

我想探索更聪明的方法,比如制作 sortOrder 浮点数,但我没有那个选项

如果您发现用浮点数更容易理解,为什么不把数字想象成定点数。

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

然后,移动操作可以很简单,只需将其新的排序号设置为两侧项目排序号之和的一半,即将移动的项目插入其新邻居之间。使用 0 和 size(array)+1 作为结束情况。我再次假设您的 UI 可以记录用户所做的移动 - 不管我认为计算它们应该相当简单,并且可能使用标准排序算法,只需重新定义“交换”。

例如,在这个数组中从最后到第一个移动(带假想小数点):

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

同样,只有一条记录发生了变化。

您仍然需要解决两个数字“之间”空间不足的情况,您最终会遇到这种情况。我认为无论算法如何,这都是正确的。这将需要“交换”来重新编号更多条目以腾出更多空间。同样,无论算法如何,我认为您不能排除所有内容都必须重新编号的情况,这不太可能。我还怀疑随着时间的推移,更可能进行大量的重新编号,无论您做什么 - 可用空间都会碎片化。但是,通过选择点的位置以提供尽可能多的空间,它应该是最佳的,即您尽可能推迟它。

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

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

【讨论】:

  • 感谢您抽出宝贵时间提出此建议。我几乎完全考虑过这个想法——正如你最终提到的那样,你需要重新编号。我想我通过使用较大的 RENUMBER_GAP 提出了一种混合方法,在大多数情况下,我最终会得到“浮点近似”,但它也会在需要时处理重新编号。
【解决方案2】:

按照您的示例,您可以执行以下操作:

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

【讨论】:

  • 谢谢,这正是我正在寻找的想法。
【解决方案3】:

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

编辑:好的,所以在你的情况下这是不可能的......你能澄清一下你期望多少重新排序?

我从问题的措辞中得知,您预计 N 项中只有 M 项会移动,其中 M 明显小于 N。因此,您希望避免 N 次更新 - 您宁愿进行 M 次更新。

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

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

【讨论】:

  • 换句话说,使用外部索引 - 可能会帮助其他人,但在我的情况下这是不可能的。我的主要问题是,我只能达到大约 5 记录/秒的更新速度,因此在某些情况下保存需要 6 分钟以上。希望参考一些重新编号算法。
  • 我很欣赏这个想法,但除非我使用的是链表,否则我不认为“如果只有两个项目交换位置,您只需要更新 2 行”适用。
  • 实际上它确实独立于所使用的数据结构 - 但是在纸上处理它我发现它仍然受到退化情况的影响,例如你提到的那种情况,例如从最后移动到第一个将需要足够的交换来触及每一行,因此它不会满足您的需求。我想我明白你现在想用这些差距做什么——我有一个不同的想法可能对你有用。将其作为不同的答案发布。
猜你喜欢
  • 2020-12-23
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-24
  • 2020-02-19
  • 2023-01-02
相关资源
最近更新 更多