Действительно ли возможно перестроить массив на месте в O (N)?

Намеченный вопрос был о том, когда результат не использован (это ясно из вопроса для C). Кто-то может зафиксировать это, так как вопросом является "сообщество Wiki"?

О преждевременной оптимизации, Knuth часто заключается в кавычки.Верно. но Donald Knuth никогда не защищал бы с этим ужасный код, который Вы видите в эти дни. Когда-нибудь замеченный = b + c среди Целых чисел Java (не интервал)? Это составляет 3 преобразования упаковки/распаковывания. Предотвращение материала как этот важно. И бесполезно при записи i ++ вместо ++ я - та же ошибка.Править: Как phresnel приятно выражается в комментарии, этому можно подвести итог, поскольку "преждевременная оптимизация является злой, как преждевременный pessimization".

Даже то, что люди больше привыкли ко мне ++, является неудачным наследием C, вызванным концептуальной ошибкой K& R (если Вы следуете за поглощенным аргументом, это - логический вывод; и защита K& R, потому что они - K& R бессмыслен, они являются великими, но они не являются великими как разработчики языка; бесчисленные ошибки в дизайне C существуют, в пределах от добирается () до strcpy (), до strncpy () API (это должно было иметь strlcpy () API со дня 1)).

Btw, я - один из не используемых достаточно к C++, чтобы найти ++ я раздражающий читать. Однако, я использую это, так как я подтверждаю, что это правильно.

17
задан int3 17 November 2012 в 19:13
поделиться

8 ответов

Думаю, это должно работать:

static <T> void arrange(T[] data, int[] p) {
    boolean[] done = new boolean[p.length];        
    for (int i = 0; i < p.length; i++) {
        if (!done[i]) {
            T t = data[i];
            for (int j = i;;) {
                done[j] = true;

                if (p[j] != i) {
                    data[j] = data[p[j]];
                    j = p[j];
                } else {
                    data[j] = t;
                    break;
                }
            }                
        }
    }
}

Примечание: это Java. Если вы делаете это на языке без сборки мусора, обязательно удалите done .

Если вам небезразлично пространство, вы можете использовать BitSet для done . Я предполагаю, что вы можете позволить себе дополнительный бит на элемент, потому что вы, кажется, готовы работать с массивом перестановок, размер которого в несколько раз больше.

Этот алгоритм копирует экземпляры T n + k раз, где k - количество циклов в перестановка. Вы можете уменьшить это число до оптимального, пропустив те i, где p [i] = i.

16
ответ дан 30 November 2019 в 12:20
поделиться

Подход состоит в том, чтобы следовать «циклам перестановки» перестановки, а не индексировать массив слева направо. Но так как вам нужно с чего-то начать, каждый раз, когда требуется новый цикл перестановки, поиск неперестановочных элементов выполняется слева направо:

// Pseudo-code
N : integer, N > 0 // N is the number of elements
swaps : integer [0..N]
data[N] : array of object
permute[N] : array of integer [-1..N]  denoting permutation (used element is -1)
next_scan_start : integer;
next_scan_start = 0;
while (swaps < N ) { // Search for the next index that is not-yet-permtued. for (idx_cycle_search = next_scan_start; idx_cycle_search < N; ++ idx_cycle_search) if (permute[idx_cycle_search] >= 0) break;
next_scan_start = idx_cycle_search + 1;
// This is a provable invariant. In short, number of non-negative // elements in permute[] equals (N - swaps) assert( idx_cycle_search < N );
// Completely permute one permutation cycle, 'following the // permutation cycle's trail' This is O(N) while (permute[idx_cycle_search] >= 0) { swap( data[idx_cycle_search], data[permute[idx_cycle_search] ) swaps ++; old_idx = idx_cycle_search; idx_cycle_search = permute[idx_cycle_search]; permute[old_idx] = -1; // Also '= -idx_cycle_search -1' could be used rather than '-1' // and would allow reversal of these changes to permute[] array } }
7
ответ дан 30 November 2019 в 12:20
поделиться

Вы имеете в виду, что у вас есть массив объектов O [1..N], а затем у вас есть массив P [1..N], который содержит перестановку чисел 1..N и в конце вы хотите получить массив объектов O1 такой, что O1 [k] = O [P [k]] для всех k = 1..N?

Например, если ваши объекты представляют собой буквы A, B, C ..., Y, Z и ваш массив P равен [26,25,24, .., 2,1]. Это желаемый результат Z, Y, ... C, B, A?

Если да, я считаю, что вы можете сделать это за линейное время, используя только O (1) дополнительной памяти. Реверсирование элементов массива - частный случай этого сценария. В общем, я думаю, вам нужно рассмотреть декомпозицию вашей перестановки P на циклы, а затем использовать ее для перемещения элементов исходного массива O [].

Если это то, что вы ищете, я могу уточнить детали.

РЕДАКТИРОВАТЬ: Другие уже представили отличные решения, пока я спал, так что не нужно повторять это здесь. ^ _ ^

РЕДАКТИРОВАТЬ: Мое дополнительное пространство O (1) действительно не совсем правильно. Я думал только об элементах «данных», но на самом деле вам также нужно хранить один бит на элемент перестановки, поэтому, если мы точны, нам нужно O (log n) дополнительных битов для этого. Но в большинстве случаев использование знакового бита (как было предложено Дж. Ф. Себастьяном) нормально, поэтому на практике нам может не понадобиться ничего больше, чем мы уже имеем.

7
ответ дан 30 November 2019 в 12:20
поделиться

Если вы не возражаете против выделения памяти для дополнительного хэша индексов, вы можете сохранить сопоставление исходного местоположения с текущим местоположением, чтобы получить временную сложность около O (n). Вот пример на Ruby, так как он читабельный и псевдокодовый. (Это могло быть короче или более идиоматически в стиле Ruby, но я написал это для ясности.)

#!/usr/bin/ruby

objects       = ['d', 'e', 'a', 'c', 'b']
order         = [2, 4, 3, 0, 1]
cur_locations = {}

order.each_with_index do |orig_location, ordinality|
  # Find the current location of the item.
  cur_location = orig_location
  while not cur_locations[cur_location].nil? do
    cur_location = cur_locations[cur_location]
  end

  # Swap the items and keep track of whatever we swapped forward.
  objects[ordinality], objects[cur_location] = objects[cur_location], objects[ordinality]
  cur_locations[ordinality] = orig_location
end

puts objects.join(' ')

Это, очевидно, требует дополнительной памяти для хеша, но поскольку это только для индексов, а не для вашего «довольно большого» объекты, надеюсь, это приемлемо. Так как поиск по хешу составляет O (1), даже несмотря на то, что есть небольшое увеличение сложности из-за случая, когда элемент был перемещен вперед более одного раза, и вам придется перезаписывать cur_location несколько раз, алгоритм в целом должно быть достаточно близко к O (n).

Если вы хотите, вы можете заранее построить полный хэш исходных данных до текущих позиций или сохранить обратный хэш текущего значения в исходное и немного изменить алгоритм, чтобы уменьшить его строго до O (n). Это было бы немного сложнее и заняло бы немного больше места, так что это та версия, которую я написал, но модификации не должны быть трудными.

РЕДАКТИРОВАТЬ: На самом деле, я почти уверен, что временная сложность просто O (n), поскольку с каждым порядком может быть связано не более одного интервала, и, таким образом, максимальное количество поисков ограничено n.

1
ответ дан 30 November 2019 в 12:20
поделиться
#!/usr/bin/env python

def rearrange(objects, permutation):
    """Rearrange `objects` inplace according to `permutation`.

       ``result = [objects[p] for p in permutation]``
    """
    seen = [False] * len(permutation)
    for i, already_seen in enumerate(seen):
        if not already_seen: # start permutation cycle
            first_obj, j = objects[i], i
            while True:
                seen[j] = True
                p = permutation[j]
                if p == i: # end permutation cycle
                    objects[j] = first_obj    # [old] p -> j
                    break
                objects[j], j = objects[p], p #       p -> j

Алгоритм (как я заметил после того, как написал его) такой же, как и из ответа @ meriton на Java .

Вот функция test для кода:

def test():
    import itertools
    N = 9
    for perm in itertools.permutations(range(N)):
        L = range(N)
        LL = L[:]
        rearrange(L, perm)
        assert L == [LL[i] for i in perm] == list(perm), (L, list(perm), LL)

    # test whether assertions are enabled
    try:
        assert 0
    except AssertionError:
        pass
    else:
        raise RuntimeError("assertions must be enabled for the test")

if __name__ == "__main__":
    test()
1
ответ дан 30 November 2019 в 12:20
поделиться

There's a histogram sort, though the running time is given as a bit higher than O(N) (N log log n).

0
ответ дан 30 November 2019 в 12:20
поделиться

I can do it given O(N) scratch space -- copy to new array and copy back.

EDIT: I am aware of the existance of an algorithm that will proceed through. The idea is to perform the swaps on the array of integers 1..N while at the same time mirroring the swaps on your array of large objects. I just cannot find the algorithm right now.

0
ответ дан 30 November 2019 в 12:20
поделиться

Проблема заключается в применении перестановки на месте с минимальным объемом памяти O (1): «перестановка на месте».

Он разрешим, но алгоритм заранее не очевиден.

Он кратко описан как упражнение у Кнута, и для работы мне пришлось расшифровать его и выяснить, как он работает. Посмотрите на 5.2 # 13.

Для более современной работы по этой проблеме с псевдокодом:

http://www.fernuni-hagen.de/imperia/md/content/fakultaetfuermathematikundinformatik/forschung/berichte/bericht_273.pdf

0
ответ дан 30 November 2019 в 12:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: