сопряженные целочисленные разбиения на месте

Я строю библиотеку C ++, которая включает в себя класс Partitions. Я пытаюсь реализовать сопряжение (объяснено ниже) на месте, и я не могу заставить его работать.

Мои ученики:

size_t _size;
size_t _length;
std::vector<int> _parts;

Как пример, целочисленный раздел [5,4,4,1] имеет

_size = 14   // 5 + 4 + 4 + 1
_length = 4  // 4 nonzero parts
_parts[0] = 5
_parts[1] = 4
_parts[2] = 4
_parts[3] = 1
_parts[i] = junk // i>3

Если раздел [m_1,m_2,...,m_k]то сопряженное [n_1,n_2,...,n_l] где

l = m_1 // length and the first part are switched
n_i = sum{ m_j | m_j > i}

Например, конъюгат [5,4,4,1] является [4,3,3,3,1], Другой способ убедиться в этом — нарисовать разделение в виде строк единичных квадратов, где число квадратов в iй ряд m_i, Чтение высоты столбцов затем дает сопряжение. Для того же примера, картина

1| x
4| x x x x
4| x x x x
5| x x x x x
__________
4 3 3 3 1

Математика переведена в синтаксис программирования как m_i = _parts[i-1] а также k = _length, Вот сломанная реализация для спряжения:

void
Partition::conjugate() {
size_t k = _length;
_length = _parts[0];
int newPart;
for (int i=(int)_length; i>0; --i) {
newPart = 0;
for (int j=0; j<k; ++j) {
if (_parts[j] >= i) newPart++;
else break;
}
_parts[i-1] = newPart;
}
}

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

Еще один способ думать о сопряжении состоит в том, чтобы понять, что сопряжение является следующей последовательностью

k...k   (k-1)...(k-1)   ...   1...1
x m_k   x(m_(k-1)-m_k)      x(m_1 - m_2)

Используя эту идею, у меня есть следующая реализация, которая дает правильный ответ:

void
Partition::conjugate() {
if (_length == _size) {
this->first();
return;
} else if (_length == 1) {
this->last();
return;
}

std::vector<int> diffs;
diffs.push_back(_parts[_length-1]);
for (size_t i=_length-1; i>0; --i)
diffs.push_back(_parts[i-1]-_parts[i]);

size_t pos = 0;
for (int i=0; i<_length; ++i) {
for (int j = diffs[i]; j>0; --j)
_parts[pos++] = (int)_length - i;
}
_length = pos;
}

Тем не менее, он использует другой стандартный вектор, который я пытаюсь избежать.


В соответствии с ответом Евгения Клюева (принятым ниже), вот окончательный код, который работает (подробности см. В его ответе):

void
Partition::conjugate() {
if (_length == _size) {
this->first();
return;
} else if (_length == 1) {
this->last();
return;
}

int last = _parts[_length-1];
for (int i=1; i<_length; ++i)
_parts[_size-i] = _parts[i-1] - _parts[i];
size_t pos = 0;
for (int i=0; i<last; ++i)
_parts[pos++] = (int)_length;
for (int i=1; i<_length; ++i) {
for (int j = _parts[_size-_length+i]; j>0; --j)
_parts[pos++] = (int)_length - i;
}
_length = pos;
}

10

Решение

Это можно сделать за 3 линейных прохода:

  1. Определить минимальный размер вектора, который позволяет выполнять сопряжение без наложения.
  2. Обратный оригинальный раздел; поскольку вывод по сравнению с вводом позволяет меньше перекрывать и, следовательно, меньше дополнительного пространства.
  3. Выполните сопряжение, заполнив вектор соответствующими индексами, повторенными столько раз, сколько различий между соседними значениями в исходном разделе.

Вот реализация C ++ 11 (см. Также полная программа на Ideone).

void conjugate()
{
size_t space = 0;
for (size_t i = 0; i < _length; ++i)
space = max(space, _parts[i] + i);
++space;

_parts.resize(space);
reverse(begin(_parts), end(_parts));

auto it_out = begin(_parts);
auto it_in = end(_parts) - _length;
size_t prev = 0;

for (; it_in < end(_parts); ++it_in)
{
it_out = fill_n(it_out, *it_in - prev, end(_parts) - it_in);
prev = *it_in;
}

_length = it_out - begin(_parts);
_parts.resize(_length);
}

Эта реализация в некотором смысле на месте. Это означает, что он использует один вектор и минимизирует дополнительное пространство, необходимое для сопряжения. В некоторых случаях (например, {4,1,1,1} или {4,3,2,1}) в вектор добавляется только один дополнительный элемент. В сложных случаях (например, {4,4,4,4}) размер вектора временно удваивается.

Можно использовать этот подход, не используя слишком много дополнительного пространства. Поскольку «плохие» случаи, такие как {4,4,4,4}, очевидно, имеют очень низкую энтропию, мы можем сжать исходное разбиение. Однако это усложнит код.

Комбинация RLE и дельта-кодирования делает этот алгоритм действительно на месте (что означает O (1) дополнительное пространство). Используйте положительные числа (или нулевой старший бит) для кодирования различий между соседними значениями в исходном разделе (так как этап сопряжения в любом случае требует только различий). Используйте отрицательные числа (или ненулевой старший бит) для кодирования серий нулей (оставшиеся биты числа говорят, сколько нулей). Все это ограничивает значение дельты и счетчик нуля до половины диапазона. Но в обоих случаях может быть не более одного значения, превышающего половину диапазона. Таким образом, мы могли бы просто поставить префикс этого негабаритного значения на ноль (и зарезервировать пространство в векторе максимум для 2 таких нулей).

2

Другие решения

В этом ответе описывается алгоритм, который, возможно, не используется из-за использования знакового бита. Поскольку вы используете подписанный тип, я все равно опубликую его.

Каждый раздел однозначно определяется своими максимумами. Для вашего примера раздела

4 | x
3 | x x x x
2 | x x x x
1 | x x x x x
- - - - -
1 2 3 4 5

где я обозначил оси координатами, а не внутренним представлением разбиения и его транспонированием, максимумы (1, 4), (4, 3) и (5, 1).

Первый / последний шаг алгоритма заключается в преобразовании в и из представления, описывающего максимумы. В общем, у нас есть место только для одной координаты, поэтому мы должны использовать положение в векторе для описания другой. Прямое преобразование просто заменяет все, кроме последнего вхождения каждого числа, на 0.

5 4 4 1   -> 5 0 4 1
4 3 3 3 1 -> 4 0 0 3 1
- - - - -
1 2 3 4 5

Как прямое, так и обратное преобразование легко реализовать.

Средний шаг — сделать транспозицию на этом альтернативном представлении. Просканируйте массив, ищите натуральные числа. Когда мы найдем, скажем x в положении y, Напиши 0 позиционировать y и писать -y позиционировать x, На самом деле жизнь немного сложнее, чем это; если позиция x содержит положительное целое число, тогда нам нужно сохранить его во временном, а затем обработать его дальше. Наконец, просканируйте массив, заменив -x от x (очевидно, это не должен быть отдельный шаг). Таким образом, мы осуществляем перестановку, и у нас есть немного, чтобы запомнить, переместился ли конкретный элемент.

1

входной массив in[] = [1,4,4,5],предполагать in[i]>=i+1(если это не так, добавьте х к каждому элементу).

тогда мы устанавливаем out[] = [1,4,4,5,0](out[i]=in[i]);

мы сканируем out[] с конца, и найти первый ненулевой элемент, сохранить его как индекс p, а также p значит, разберемся out[p],

Посчитайте, сколько элементов появляется, и мы получим результат.

[1,4,4,5,0] , p = 3, cnt = 1 (parse out[3] = 5)
->
[1,4,4,5,1] , p = 2, cnt = 3 (parse 4)
->
[1,3,3,3,1] , p = 0, cnt = 3 (parse 1)
->
[4,3,3,3,1] , p = -1, cnt = 4 (done)

когда мы разбираем out[i](то есть in[i]), мы назначим next_parse_element ~last_element_assigned-1по нашему предположению, это next_element_index + 1~last element assigned-1, но next_element_index + 1~last element assigned будет разобрано нашей программой и бесполезно.

Пример ([1,4,4,5]): когда мы разбираем 4следующий элемент 1так что мы должны рассчитать out[1](next parse element) ~ out[3](last_assigned - 1), Но вы видите, out[1] - out[3] бесполезен, и мы не теряем никакой информации.

если не соответствует предположению, мы можем добавить х к каждому элементу (in[i]+=x), чтобы соответствовать предположению (максимально добавить размер in, еще O(n) пространство). Затем мы делаем это, как описано выше, и игнорируем x элементы в начале out[]и это реальный ответ для ввода.

add 1 to every element in int[]
4 | x x
3 | x x x x x
2 | x x x x x
1 | x x x x x x
- - - - - -
1 2 3 4 5 6

Это [4,4,3,3,3,1], игнорируйте первый элемент, и это реальный ответ.

Сложность времени. Если in[] не сортируется, то это O(nlogn)или это O(n),

Смотрите мой код для деталей:

import java.util.Arrays;public class Conjugate {
int[] part;
public Conjugate(int[] array) {
Arrays.sort(array);
int max = 0;
for (int i = 0; i < array.length; i++)
{
max = Math.max(max, array[i]);
}
part = new int[Math.max(max,array.length)];
for (int i = 0;i < array.length; i++)
{
part[i] = array[i];
}
int cnt = 0, p = part.length - 1, next = 0;
for (int i = array.length-1; i >= 0; i--)
{
for (int j = i; j>=0&&part[i]==part[j]; j--)
{
cnt ++;
if (j - 1 < 0)
next = 0;
else
next = part[j-1];
i=j;
}
for (int j = p; j >= next ; j--)
{
part[j] = cnt;
}
p = next - 1 ;
}
}
void output()
{
for (int i = 0; i < part.length; i++)
{
System.out.print(part[i] + " ");
}
}
public static void main(String[] args) {
int[] a = {1,4,4,5};
Conjugate con = new Conjugate(a);
con.output();
}
}
1
По вопросам рекламы [email protected]