Сортировка подсчетом: зачем нам сумма предыдущих подсчетов?

Я читал о сортировке подсчета, и один из шагов алгоритма

Сортировка подсчетом

for(i = 1 to k) 
   c[i] = c[i]+c[i-1];

Зачем нам нужен этот шаг?

Почему мы не можем использовать это вместо этого

for(i = 0 to k)
    while(c[i]--)
        Output i/Use i as key.

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

Но тогда мы могли бы использовать 2d вектор. Что плохого в этом подходе, когда мы сортируем данные от 0 до 99.

int a[100];
for(int i = 0 ; i < 100; ++i)
    a[i] = 0;
string s;
vector<vector<string>> data(100);
int temp;
for(int i = 0 ; i < n ; ++i){
    cin>>temp;
    a[temp]++;
    getline(cin,s);
    data[temp].push_back(s);
}

for(int i = 0 ; i < 100; ++i){
    int current = 0;
    while(a[i]--){
        cout<<data[i][current]<<" ";
        ++current;
    }
}

person Kartik Sharma    schedule 26.08.2015    source источник
comment
не могли бы вы добавить ссылку на полный алгоритм? Вот так сложно сказать, что происходит...   -  person dingalapadum    schedule 26.08.2015


Ответы (1)


Есть два случая, в которых вы можете захотеть использовать сортировку подсчетом. Во-первых, у вас может быть массив реальных чисел, которые вы хотите отсортировать, и цель состоит в том, чтобы отсортировать эти числа. Во-вторых, у вас может быть массив значений для сортировки, каждое из которых отсортировано в соответствии с некоторым ключом, представляющим собой число в фиксированном диапазоне.

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

/* Initialize histogram. */
const unsigned kMaxValue = 100;
std::vector<unsigned> counts(kMaxValue);

/* Read values. */
const unsigned kNumValues = 100;
for (unsigned i = 0; i < kNumValues; i++) {
    unsigned nextValue;
    std::cin >> nextValue; // Don't forget error-checking!

    counts[nextValue]++;
}

/* Output values. */
std::vector<unsigned> result;
result.reserve(kNumValues);
for (unsigned i = 0; i < counts.size(); i++) {
    for (unsigned j = 0; j < counts[i]; j++) {
        result.push_back(i);
    }
}

Обратите внимание, что числа, которые добавляются к вектору result, не считываются из ввода, а вместо этого генерируются просто с помощью счетчика цикла.

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

Вот тут-то и появляется идея частотной гистограммы. Основная идея заключается в следующем — мы хотим определить для каждого элемента входного массива индекс, под которым этот элемент должен оказаться в отсортированном массиве. Давайте представим, что мы начинаем с получения частотной гистограммы H для входного массива. Эта гистограмма обладает тем свойством, что H[i] сообщает нам, сколько существует различных элементов с ключом i. Теперь предположим, что мы делаем гистограмму кумулятивной частоты C, где C[i] = C[0] + C[1] + ... + C[i]. В этом случае C[i] сообщает нам, сколько элементов во входном массиве имеют ключ, меньший или равный ему.

Представьте, что у вас есть только входной массив и гистограмма накопленной частоты. Что вы можете с этим сделать? Итак, предположим, что у вас есть некоторый элемент A[i] из исходного массива. На основании гистограммы кумулятивной частоты мы знаем, что в массиве есть элементы C[i], ключ которых меньше или равен A[i]. Следовательно, если бы мы хотели переупорядочить входной массив так, чтобы все было в порядке сортировки, мы могли бы поместить элемент A[i] в ​​позицию C[key(A[i])] - 1, так как существуют C[key(A[ i])] - 1 элемент меньше или равен ему. Предполагая, что в массиве нет повторяющихся значений, итерация по массиву A и изменение положения всего в соответствии с этой формулой правильно расположит массив в отсортированном порядке.

Все немного сложнее, если у нас есть дубликаты. Предположим, что есть два элемента A[i] и A[j], где key(A[i]) = key(A[j]). В этом случае мы не можем поместить оба элемента в позицию C[key(A[i])] - 1, так как они столкнутся. Однако мы могли бы сделать следующее. Мы поместим один из элементов в позицию C[key(A[i])] - 1, а затем деструктивно изменим массив C, вычитая 1 из C[key(A[i])]. Затем, когда мы увидим элемент A[j], мы поместим его в позицию C[key(A[j])] - 1, которая является пустым слотом. Интуитивно понятно, что вся идея гистограммы кумулятивной частоты состоит в том, чтобы иметь возможность мгновенно знать, где расположить объекты, сохраняя, сколько объектов будет стоять перед любым конкретным элементом с заданным ключом. Каждый раз, когда мы видим элемент с каким-то ключом, мы хотим указать, что для любого будущего элемента с тем же ключом перед ним будет на один элемент меньше.

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

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

/* Initialize histogram. */
const unsigned kMaxValue = 100;
std::vector<unsigned> counts(kMaxValue);

/* Read values. Each value will be a string with an associated key. */
const unsigned kNumValues = 100;
std::vector<std::pair<unsigned, std::string>> elems;
elems.reserve(kNumValues);

for (unsigned i = 0; i < kNumValues; i++) {
    unsigned key;
    std::cin >> key; // Don't forget error-checking!

    std::string value;
    std::cin >> value;       // Don't forget error-checking!
    elems.push_back(std::make_pair<key, value>);

    counts[key]++;
}

/* Transform histogram into cumulative histogram. */
for (unsigned i = 1; i < counts.size(); i++) {
   counts[i] += counts[i - 1];
}

/* Output values. */
std::vector<unsigned> result(kNumValues);
for (unsigned i = counts.size() - 1; i >= 0 && i < counts.size(); i--) { // See note
    result[--counts[elems[i].first]] = elems[i].second;
}

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

person templatetypedef    schedule 26.08.2015
comment
Причина, по которой я добавил вектор строки, заключалась в том, что мне нужно было вывести строку, соответствующую индексу. Например, если массив содержит следующие данные: 0 0 1 4 3 2 0 1 и соответствующие строки в виде a b c d e f g h, то мой ответ будет a b g c h f e d. Здесь меня интересует не только сортировка индексов, но и получение соответствующих данных. - person Kartik Sharma; 27.08.2015
comment
@KartikSharma: Для этого вам не нужны все эти вещи. Просто прочитайте ввод, как вы уже делаете. Затем выполните итерацию по внешнему вектору внутри каждой из этих итераций, выполните итерацию по внутреннему вектору и напечатайте строки. Вы можете избавиться от всего массива a. - person dingalapadum; 27.08.2015
comment
Извините, я не могу понять, как это сделать. Кроме того, зачем нам сумма предыдущих элементов (как показано в исходном алгоритме). Там должно быть какое-то использование, которое я пропускаю. - person Kartik Sharma; 27.08.2015
comment
@KartikSharma Ответ обновлен - я пропустил тот факт, что у вас были строки с ключами для сортировки! - person templatetypedef; 27.08.2015