使用“子向量反转”优化排序


23

这是挑战,其目标是使用最少的反转将向量排序为升序。您的算法只能使用“子向量反转” 1对向量进行排序,但是它可以将其他运算用于算术运算,循环,检查其是否已排序等。算法执行的子向量反转的次数是其得分。


1个“子向量反转”:

  • 在向量中选择一个数字范围,然后反转该范围内的元素。

举一个简单的例子,如果从vector开始{4,3,2,1},则可以用许多不同的方式对其进行排序:

  1. 反转整个向量。显然,这是最短的方法,因为它只需要一次逆转即可:{4,3,2,1} -> {1,2,3,4}
  2. 您可以执行冒泡排序的一个版本,该过程需要6个冲销: {4,3,2,1} -> {3,4,2,1} -> {3,2,4,1} -> {2,3,4,1} -> {2,3,1,4} -> {2,1,3,4} -> {1,2,3,4}
  3. 您可以从前三个元素开始,然后是最后三个元素,最后是两个第一个和最后两个,这需要进行4次交换: {4,3,2,1} -> {2,3,4,1} -> {2,1,4,3} -> {1,2,4,3} -> {1,2,3,4}
  4. ... 等等。有无数的可用选项(您可以根据需要重复执行任何操作)。

规则和要求:

  • 对于包含100个数字的列表,您的代码必须在不到一分钟的时间内完成。您可以自己安排时间,但是请公平地玩2

  • 您必须存储所执行的所有交换的开始索引和结束索引,以便可以验证解决方案。(我将在下面解释这是什么意思)。

  • 该代码必须是确定性的。

  • 您可以根据需要使用任何格式的输入:数字矢量,链表,带长度的数组...任何您喜欢的格式。

  • 您可以对向量的副本执行任何您喜欢的操作。这包括尝试不同的冲销并检查哪个效率最高。蛮力强行完全可以,但是要遵守时间限制。

  • 分数是5个测试向量的翻转总数。抢七局将加盖日期戳。


例:

4 1 23 21 49 2 7 9 2 | 初始向量/列表
4 1 2 9 7 2 49 21 23 | (2,8)(在索引2和8之间翻转元素)
4 1 2 2 7 9 49 21 23 | (3、5)
4 1 2 2 7 9 23 21 49 | (6、8)
4 1 2 2 7 9 21 23 49 | (6,7)
 2 2 1 4 7 9 21 23 49 | (0,3)
 1 2 2 4 7 9 21 23 49 | (0,2)

得分是6,因为您执行了6次冲销。您必须以合适的格式存储(而不是打印)右侧列出的索引,该格式可以轻松打印/输出以进行验证。

测试向量:

133, 319, 80, 70, 194, 333, 65, 21, 345, 142, 82, 491, 92, 167, 281, 386, 48, 101, 394, 130, 111, 139, 214, 337, 180, 24, 443, 35, 376, 13, 166, 59, 452, 429, 406, 256, 133, 435, 446, 304, 350, 364, 447, 471, 236, 177, 317, 342, 294, 146, 280, 32, 135, 399, 78, 251, 467, 305, 366, 309, 162, 473, 27, 67, 305, 497, 112, 399, 103, 178, 386, 343, 33, 134, 480, 147, 466, 244, 370, 140, 227, 292, 28, 357, 156, 367, 157, 60, 214, 280, 153, 445, 301, 108, 77, 404, 496, 3, 226, 37

468, 494, 294, 42, 19, 23, 201, 47, 165, 118, 414, 371, 163, 430, 295, 333, 147, 336, 403, 490, 370, 128, 261, 91, 173, 339, 40, 54, 331, 236, 255, 33, 237, 272, 193, 91, 232, 452, 79, 435, 160, 328, 47, 179, 162, 239, 315, 73, 160, 266, 83, 451, 317, 255, 491, 70, 18, 275, 339, 298, 117, 145, 17, 178, 232, 59, 109, 271, 301, 437, 63, 103, 130, 15, 265, 281, 365, 444, 180, 257, 99, 248, 378, 158, 210, 466, 404, 263, 29, 117, 417, 357, 44, 495, 303, 428, 146, 215, 164, 99

132, 167, 361, 145, 36, 56, 343, 330, 14, 412, 345, 263, 306, 462, 101, 453, 364, 389, 432, 32, 200, 76, 268, 291, 35, 13, 448, 188, 11, 235, 184, 439, 175, 159, 360, 46, 193, 440, 334, 128, 346, 192, 263, 466, 175, 407, 340, 393, 231, 472, 122, 254, 451, 485, 257, 67, 200, 135, 132, 421, 205, 398, 251, 286, 292, 488, 480, 56, 284, 484, 157, 264, 459, 6, 289, 311, 116, 138, 92, 21, 307, 172, 352, 199, 55, 38, 427, 214, 233, 404, 330, 105, 223, 495, 334, 169, 168, 444, 268, 248

367, 334, 296, 59, 18, 193, 118, 10, 276, 180, 242, 115, 233, 40, 225, 244, 147, 439, 297, 115, 354, 248, 89, 423, 47, 458, 64, 33, 463, 142, 5, 13, 89, 282, 186, 12, 70, 289, 385, 289, 274, 136, 39, 424, 174, 186, 489, 73, 296, 39, 445, 308, 451, 384, 451, 446, 282, 419, 479, 220, 35, 419, 161, 14, 42, 321, 202, 30, 32, 162, 444, 215, 218, 102, 140, 473, 500, 480, 402, 1, 1, 79, 50, 54, 111, 189, 147, 352, 61, 460, 196, 77, 315, 304, 385, 275, 65, 145, 434, 39

311, 202, 126, 494, 321, 330, 290, 28, 400, 84, 6, 160, 432, 308, 469, 459, 80, 48, 292, 229, 191, 240, 491, 231, 286, 413, 170, 486, 59, 54, 36, 334, 135, 39, 393, 201, 127, 95, 456, 497, 429, 139, 81, 293, 359, 477, 404, 129, 129, 297, 298, 495, 424, 446, 57, 296, 10, 269, 350, 337, 39, 386, 142, 327, 22, 352, 421, 32, 171, 452, 2, 484, 337, 359, 444, 246, 174, 23, 115, 102, 427, 439, 71, 478, 89, 225, 7, 118, 453, 350, 109, 277, 338, 474, 405, 380, 256, 228, 277, 3

我相当确定,找到最佳解决方案是NP难题的(因为常规的煎饼分类是)。

2是的,由于时间限制为一分钟,计算机速度非常快的人们可能会从中受益。经过大量讨论,我发现最好是每个人都进行自己的基准测试,这并不是最快的代码挑战。



1
最佳解决方案最多应该等于插入次数的反转次数,每个反转可以放置一个数字。
fəˈnɛtɪk 17-4-14

3
这不是薄煎饼翻转(只能从一个位置翻转到最后)。选择排序为O(n),并使用n-1个交换。在最坏的情况下,必须进行n-1次交换。选择排序是渐近最优的。
orlp

1.输入是否为整数列表/向量?2.程序的输出应该是什么?3.程序是否可以对向量或向量的一部分进行多次排序,也许使用不同的方法(例如快速排序),以便确定如何优化操作,只要它对输入进行子向量反转排序向量(按要求)到底?
aditsu

1
@orlp您能证明n-1翻转有最坏的情况吗?我只能证明一个下界约50
user202729

Answers:


6

Java,遗传算法,80 + 81 + 79 + 78 + 80 = 398(以前是418

在尝试了许多不同的想法并且大多数都失败之后,我决定使用这种算法:从输入数组开始,尝试所有可能的反转,并以最小的运行次数保留一定数量的结果,然后对这些结果执行相同的操作,直到我们得到一个排序数组。

“游程”是指在排序数组中完全出现或颠倒的最大子数组。基本上,它们是最大排序的子数组,但是如果元素重复,则中间的元素数量应该匹配。例如,如果排序后的数组中2, 2, 3, 3, 4, 4然后4, 3, 3, 2是一个运行但2, 2, 3, 4不是(既不是2, 3, 2)。

在此版本中,我优化了算法,使其仅在运行边界处反转,并且仅当反转的运行可以与新相邻的运行合并时才进行。同样,在每个步骤中调整并合并运行,以避免从修改后的数组重新计算它们。这使我可以将“人口规模”从30增加到大约3000,并以不同的规模运行多个模拟。

import java.io.*;
import java.util.*;

public class SubReversal {
    static int n;
    static int[] a;
    static int[] srt;
    static List<int[]> rev;
    static Map<Integer, Integer> idx;
    static Map<Integer, Integer> count;

    static final int NB = 2000;
    static State[] best = new State[NB + 1];
    static int ns;

    static class Run {
        int start;
        int end;
        int dir;
        int nstart = 1;
        int nend = 1;

        Run(final int start) {
            this.start = start;
        }

        Run(final Run r) {
            start = r.start;
            end = r.end;
            dir = r.dir;
            nstart = r.nstart;
            nend = r.nend;
        }

        Run copy() {
            return new Run(this);
        }

        Run reverse() {
            int t = start;
            start = end;
            end = t;
            t = nstart;
            nstart = nend;
            nend = t;
            dir = -dir;
            return this;
        }

        boolean canJoin(final Run r) {
            if (dir * r.dir == -1) {
                return false;
            }
            final int t = idx.get(a[r.start]) - idx.get(a[end]);
            if (Math.abs(t) > 1) {
                return false;
            }
            if (t != 0 && dir + r.dir != 0 && t != dir && t != r.dir) {
                return false;
            }
            if (t == 0) {
                if (dir * r.dir == 0) {
                    return true;
                }
                return nend + r.nstart == count.get(a[end]);
            }
            return (dir == 0 || nend == count.get(a[end])) && (r.dir == 0 || r.nstart == count.get(a[r.start]));
        }

        Run join(final Run r) {
            if (a[start] == a[r.start]) {
                nstart += r.nstart;
            }
            if (a[end] == a[r.end]) {
                nend += r.nend;
            }
            else {
                nend = r.nend;
            }
            end = r.end;
            if (dir == 0) {
                dir = r.dir;
            }
            if (dir == 0 && a[start] != a[end]) {
                dir = idx.get(a[end]) - idx.get(a[start]);
            }
            return this;
        }

        @Override
        public String toString() {
            return start + "(" + nstart + ") - " + end + '(' + nend + "): " + dir;
        }
    }

    static class State implements Comparable<State> {
        int[] b;
        int[] rv;
        State p;
        List<Run> runs;

        public State(final int[] b, final int[] rv, final State p, final List<Run> runs) {
            this.b = Arrays.copyOf(b, b.length);
            this.rv = rv;
            this.p = p;
            this.runs = runs;
        }

        @Override
        public int compareTo(final State o) {
            return runs.size() - o.runs.size();
        }

        @Override
        public String toString() {
            return Arrays.toString(b) + " - " + Arrays.toString(rv) + " - " + runs.size();
        }

        int getCount() {
            return p == null ? 0 : p.getCount() + 1;
        }
    }

    static void reverse(int x, int y) {
        while (x < y) {
            int t = a[x];
            a[x] = a[y];
            a[y] = t;
            x++;
            y--;
        }
    }

    static List<Run> runs() {
        final List<Run> l = new ArrayList<>();
        Run run = new Run(0);
        for (int i = 1; i < n; ++i) {
            final int t = idx.get(a[i]) - idx.get(a[i - 1]);
            if (Math.abs(t) > 1) {
                run.end = i - 1;
                l.add(run);
                run = new Run(i);
            }
            else if (t == 0) {
                run.nend++;
                if (run.dir == 0) {
                    run.nstart++;
                }
            }
            else {
                if (run.dir == 0) {
                    run.dir = t;
                }
                else if (run.dir != t || run.nend != count.get(a[i - 1])) {
                    run.end = i - 1;
                    l.add(run);
                    run = new Run(i);
                }
                run.nend = 1;
            }
        }
        run.end = n - 1;
        l.add(run);
        return l;
    }

    static void show() {
        if (!Arrays.equals(a, srt)) {
            System.out.println("bug!");
            System.out.println(Arrays.toString(a));
            throw new RuntimeException();
        }
        System.out.println("Sorted: " + Arrays.toString(a));
        System.out.println(rev.size() + " reversal(s):");
        for (int[] x : rev) {
            System.out.println(Arrays.toString(x));
        }
    }

    static void sort() {
        State bestest = null;
        final int[] a1 = Arrays.copyOf(a, n);
        final int[] sizes = {10, 20, 30, 50, 100, 200, 300, 500, 1000, 2000};

        for (int nb : sizes) {
            System.arraycopy(a1, 0, a, 0, n);
            ns = 1;
            best[0] = new State(a, null, null, runs());
            while (best[0].runs.size() > 1) {
                final State[] s = Arrays.copyOf(best, ns);
                ns = 0;
                for (State x : s) {
                    System.arraycopy(x.b, 0, a, 0, n);
                    final int m = x.runs.size();
                    for (int i = 0; i < m; ++i) {
                        for (int j = i; j < m; ++j) {
                            boolean b = false;
                            if (i > 0) {
                                final Run r = x.runs.get(j);
                                r.reverse();
                                b = x.runs.get(i - 1).canJoin(r);
                                r.reverse();
                            }
                            if (!b && j < m - 1) {
                                final Run r = x.runs.get(i);
                                r.reverse();
                                b = r.canJoin(x.runs.get(j + 1));
                                r.reverse();
                            }
                            if (!b) {
                                continue;
                            }
                            final List<Run> l = new ArrayList<>(x.runs);
                            final int rstart = l.get(i).start;
                            final int rend = l.get(j).end;
                            final int t = rstart + rend;
                            reverse(rstart, rend);
                            for (int k = i; k <= j; ++k) {
                                final Run r = x.runs.get(i + j - k).copy().reverse();
                                r.start = t - r.start;
                                r.end = t - r.end;
                                l.set(k, r);
                            }
                            if (j < m - 1 && l.get(j).canJoin(l.get(j + 1))) {
                                l.get(j).join(l.get(j + 1));
                                l.remove(j + 1);
                            }
                            if (i > 0 && l.get(i - 1).canJoin(l.get(i))) {
                                l.set(i - 1, l.get(i - 1).copy().join(l.get(i)));
                                l.remove(i);
                            }

                            if (ns < nb || l.size() < best[ns - 1].runs.size()) {
                                best[ns++] = new State(a, new int[]{rstart, rend}, x, l);
                                Arrays.sort(best, 0, ns);
                                if (ns > nb) {
                                    ns = nb;
                                }
                            }
                            reverse(rstart, rend);
                        }
                    }
                }

                if (ns == 0) {
                    for (State x : s) {
                        System.arraycopy(x.b, 0, a, 0, n);
                        final List<Run> l = new ArrayList<>(x.runs);
                        final int rstart = l.get(0).start;
                        final int rend = l.get(0).end;
                        final int t = rstart + rend;
                        reverse(rstart, rend);
                        final Run r = x.runs.get(0).copy().reverse();
                        r.start = t - r.start;
                        r.end = t - r.end;
                        l.set(0, r);

                        best[ns++] = new State(a, new int[]{rstart, rend}, x, l);
                        reverse(rstart, rend);
                    }
                    Arrays.sort(best, 0, ns);
                }
            }
            State r = null;
            for (int i = 0; i < ns; ++i) {
                if (Arrays.equals(best[i].b, srt)) {
                    r = best[i];
                    break;
                }
            }
            if (r == null) {
                final State x = best[0];
                System.arraycopy(x.b, 0, a, 0, n);
                reverse(0, n - 1);
                r = new State(a, new int[]{0, n - 1}, x, runs());
            }
            if (!Arrays.equals(r.b, srt)) {
                throw new RuntimeException("bug");
            }

            if (bestest == null || r.getCount() < bestest.getCount()) {
                bestest = r;
            }
        }

        while (bestest.p != null) {
            rev.add(bestest.rv);
            bestest = bestest.p;
        }
        Collections.reverse(rev);
        a = a1;
        for (int[] x : rev) {
            reverse(x[0], x[1]);
        }
        if (!Arrays.equals(a, srt)) {
            throw new RuntimeException("bug");
        }
    }

    static void init(final String s) {
        final String[] b = s.split(s.contains(",") ? "," : " ");
        n = b.length;
        a = new int[n];
        count = new HashMap<>();
        for (int i = 0; i < n; ++i) {
            a[i] = Integer.parseInt(b[i].trim());
            final Integer x = count.get(a[i]);
            count.put(a[i], x == null ? 1 : x + 1);
        }
        srt = Arrays.copyOf(a, n);
        Arrays.sort(srt);
        idx = new HashMap<>();
        int j = 0;
        for (int i = 0; i < n; ++i) {
            if (i == 0 || srt[i] != srt[i - 1]) {
                idx.put(srt[i], j++);
            }
        }
        rev = new ArrayList<>();
    }

    static void test5() {
        final String[] t = {"133, 319, 80, 70, 194, 333, 65, 21, 345, 142, 82, 491, 92, 167, 281, 386, 48, 101, 394, 130, 111, 139, 214, 337, 180, 24, 443, 35, 376, 13, 166, 59, 452, 429, 406, 256, 133, 435, 446, 304, 350, 364, 447, 471, 236, 177, 317, 342, 294, 146, 280, 32, 135, 399, 78, 251, 467, 305, 366, 309, 162, 473, 27, 67, 305, 497, 112, 399, 103, 178, 386, 343, 33, 134, 480, 147, 466, 244, 370, 140, 227, 292, 28, 357, 156, 367, 157, 60, 214, 280, 153, 445, 301, 108, 77, 404, 496, 3, 226, 37",
                "468, 494, 294, 42, 19, 23, 201, 47, 165, 118, 414, 371, 163, 430, 295, 333, 147, 336, 403, 490, 370, 128, 261, 91, 173, 339, 40, 54, 331, 236, 255, 33, 237, 272, 193, 91, 232, 452, 79, 435, 160, 328, 47, 179, 162, 239, 315, 73, 160, 266, 83, 451, 317, 255, 491, 70, 18, 275, 339, 298, 117, 145, 17, 178, 232, 59, 109, 271, 301, 437, 63, 103, 130, 15, 265, 281, 365, 444, 180, 257, 99, 248, 378, 158, 210, 466, 404, 263, 29, 117, 417, 357, 44, 495, 303, 428, 146, 215, 164, 99",
                "132, 167, 361, 145, 36, 56, 343, 330, 14, 412, 345, 263, 306, 462, 101, 453, 364, 389, 432, 32, 200, 76, 268, 291, 35, 13, 448, 188, 11, 235, 184, 439, 175, 159, 360, 46, 193, 440, 334, 128, 346, 192, 263, 466, 175, 407, 340, 393, 231, 472, 122, 254, 451, 485, 257, 67, 200, 135, 132, 421, 205, 398, 251, 286, 292, 488, 480, 56, 284, 484, 157, 264, 459, 6, 289, 311, 116, 138, 92, 21, 307, 172, 352, 199, 55, 38, 427, 214, 233, 404, 330, 105, 223, 495, 334, 169, 168, 444, 268, 248",
                "367, 334, 296, 59, 18, 193, 118, 10, 276, 180, 242, 115, 233, 40, 225, 244, 147, 439, 297, 115, 354, 248, 89, 423, 47, 458, 64, 33, 463, 142, 5, 13, 89, 282, 186, 12, 70, 289, 385, 289, 274, 136, 39, 424, 174, 186, 489, 73, 296, 39, 445, 308, 451, 384, 451, 446, 282, 419, 479, 220, 35, 419, 161, 14, 42, 321, 202, 30, 32, 162, 444, 215, 218, 102, 140, 473, 500, 480, 402, 1, 1, 79, 50, 54, 111, 189, 147, 352, 61, 460, 196, 77, 315, 304, 385, 275, 65, 145, 434, 39",
                "311, 202, 126, 494, 321, 330, 290, 28, 400, 84, 6, 160, 432, 308, 469, 459, 80, 48, 292, 229, 191, 240, 491, 231, 286, 413, 170, 486, 59, 54, 36, 334, 135, 39, 393, 201, 127, 95, 456, 497, 429, 139, 81, 293, 359, 477, 404, 129, 129, 297, 298, 495, 424, 446, 57, 296, 10, 269, 350, 337, 39, 386, 142, 327, 22, 352, 421, 32, 171, 452, 2, 484, 337, 359, 444, 246, 174, 23, 115, 102, 427, 439, 71, 478, 89, 225, 7, 118, 453, 350, 109, 277, 338, 474, 405, 380, 256, 228, 277, 3"};
        int r = 0;
        for (String s : t) {
            init(s);
            sort();
            System.out.println(rev.size());
            r += rev.size();
        }
        System.out.println("total: " + r);
    }

    public static void main(final String... args) throws IOException {
        System.out.print("Input: ");
        final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        final String s = br.readLine();
        final long t = System.currentTimeMillis();
        if (s.isEmpty()) {
            System.out.println("Running tests");
            test5();
        }
        else {
            init(s);
            sort();
            show();
        }
        System.out.println("Time: " + (System.currentTimeMillis() - t + 500) / 1000 + " sec");
    }
}

输入是一个由逗号和/或空格分隔的数字列表(与stdin分开)。如果输入为空,则程序将运行5个测试。每个人大约需要40秒。


有趣的是,新版本并没有改善第5个测试用例的冲销数量。其他的改进很多。我很高兴您决定再尝试一次:)
Stewie Griffin

@StewieGriffin谢谢,您帮助我突破了20k :)我想我以前遇到的最后一种情况有点幸运。随机方法可能会产生更好的结果。
aditsu

5

一手蛮力然后选择排序(也是幼稚的解决方案),90 + 89 + 88 + 87 + 89 = 443个动作

let doReverse = (a, l, r) => {
  a.splice(l, r - l, ...a.slice(l, r).reverse());
};
let selectSubVectorReverseSort = a => {
  let log = [];

  for (let i = 0, l = a.length; i < l; i++) {
    let j, p = i;
    for (j = i; j < l; j++) {
      if (a[j] < a[p]) p = j;
    }
    if (p === i) continue;
    log.push([i, p + 1]);
    doReverse(a, i, p + 1);
  }
  return log;
};

let a = JSON.parse(`[${readline()}]`);
let copiedArray = a => a.map(x => x);
let minLog = selectSubVectorReverseSort(copiedArray(a));
for (let i = 0, l = a.length; i < l; i++) {
  for (let j = i + 1; j < l; j++) {
    let b = copiedArray(a);
    doReverse(b, i, j + 1);
    let log = [[i, j + 1], ...selectSubVectorReverseSort(b)];
    if (log.length < minLog.length) minLog = log;
  }
}

print(minLog.length);

对于每个可能的第一步,尝试一下,然后进行选择排序。

是的,这是另一个幼稚的解决方案。

我不确定这应该是编辑还是其他文章,但是似乎解决方案太简单了,因此选择编辑。


选择排序(天真解决方案),92 + 93 + 95 + 93 + 96 = 469移动

let log = [];
let doReverse = (a, l, r) => {
  log.push([l, r]);
  a.splice(l, r - l, ...a.slice(l, r).reverse());
}

let a = JSON.parse(`[${readline()}]`);
for (let i = 0, l = a.length; i < l; i++) {
  let j, p = i;
  for (j = i; j < l; j++) {
    if (a[j] < a[p]) p = j;
  }
  if (p === i) continue;
  doReverse(a, i, p + 1);
}
print(log.length)

天真的解决方案使用选择排序。

这里必须有一些更好的解决方案,但发布此,因为我没有找到一个更好的(不包括强力搜索)。

(以上代码是JavaScript Shell

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.