Я искал вокруг, и хотя я понял общую идею использования Radix Sort для алфавитного размещения массива строк, я знаю, что я иду в неправильном направлении.
Это то, что я до сих пор:
void radixSort(string* sortMe, int l)
{
queue<string>* sections = new queue<string>[27]; //Have a-z, and also one for strings that are null terminated.
for(int i = 0; i < numElements; i++)
{
if(!(sortMe[i][l] == 32))
sections[sortMe[i][l]-96].push(sortMe[i]); //-96 because the ascii code for a is 97. If, for example a is the character, it will be placed at 1. 0 is left for null characters
}
for(int i =0; i < 26; i++)
{
while(!sections[i].empty())
{
temp.push_back(sections[i].front());
sections[i].pop();
}
}
}
То, что у меня есть, до сих пор сортирует все строки по первому символу, и я знаю, что затем мне нужно пройти и создать подмассивы оставшихся символов и отсортировать их, но как я могу реализовать это эффективно? Строки имеют переменный размер и могут включать пробелы, например:
Это то, что я нашел, что, кажется, будет полезно:
http://algs4.cs.princeton.edu/lectures/51DemoKeyIndexedCounting.pdf
Слайды, которые вы нашли, великолепны! Но откуда взялись эти очереди в вашем коде?
Во всяком случае, вот и ты (живой пример):
template <typename E>
size_t bin(const E& elem, size_t digit)
{
return elem.size() > digit ? size_t(elem[digit]) + 1 : 0;
}
template <size_t R, typename C, typename P>
void radix_sort(P& pos, const C& data, size_t digit)
{
using A = std::array<size_t, R + 1>;
A count = {};
P prev(pos);
for (auto i : prev)
++count[bin(data[i], digit)];
A done = {}, offset = {{0}};
std::partial_sum(count.begin(), count.end() - 1, offset.begin() + 1);
for (auto i : prev)
{
size_t b = bin(data[i], digit);
pos[offset[b] + done[b]++] = i;
}
}
struct shorter
{
template <typename A>
bool operator()(const A& a, const A& b) { return a.size() < b.size(); }
};
template <size_t R, typename C>
std::vector<size_t> radix_sort(const C& data)
{
std::vector<size_t> pos(data.size());
std::iota(pos.begin(), pos.end(), 0);
size_t width = std::max_element(data.begin(), data.end(), shorter())->size();
for (long digit = long(width) - 1; digit >= 0; --digit)
radix_sort<R>(pos, data, size_t(digit));
return pos;
}
который вы можете использовать так
int main()
{
std::vector<std::string> data = generate();
std::vector<size_t> pos = radix_sort<128>(data);
for (auto i : pos)
std::cout << data[i] << std::endl;
}
где generate()
включен в живой пример и генерирует строки, приведенные в вашем вопросе.
Я не пытаюсь объяснить, как это работает здесь, я полагаю, вы можете выяснить, так как вы работаете над проблемой. Но несколько комментариев в порядке.
Мы не сортируем входную последовательность на месте и не возвращаем отсортированную копию; мы просто возвращаем последовательность позиции входных элементов в отсортированной последовательности.
Мы обрабатываем строки справа налево.
Сложность O(lw)
где l
длина ввода (количество строк ввода) и w
максимальная ширина ввода (максимальная длина всех строк ввода). Так что этот алгоритм имеет смысл, если ширина строки не сильно меняется.
Первый параметр шаблона R
из radix_sort()
количество возможных значений для каждой цифры (буквы) на входе. Например. R = 128
означает, что возможные значения 0..127
, Это должно быть хорошо для вашего ввода. Я не пытался сделать что-нибудь умное в отношении кодов ASCII, но вы можете настроить функцию bin()
для этого.
На выходе bin()
, значение 0
зарезервировано, чтобы означать «мы прошли конец этой строки». Такие строки помещаются перед другими, которые все еще продолжаются.
Я попытался дать понятные имена переменным и функциям и использовать стандартные вызовы библиотеки для общих задач, где это возможно.
Код является общим, например, он может сортировать любой контейнер произвольного доступа, содержащий контейнеры произвольного доступа, а не только векторы строк.
Я использую возможности C ++ 11 здесь и там для удобства, но на самом деле ничего не нужно: можно легко сделать то же самое только с C ++ 03.
Очень похоже на iavr, но сортировка на месте (сравнена с решением iavr с g ++ -O3 и занимает около 2020 мс по сравнению с 1780 мс iavr), наслаждаясь обычный интерфейс и многократный код. Проблема с реализацией Iavr заключается в том, что его логика работает только с контейнерами строк и не может быть легко расширяема для других типов. Очевидно, что его специализированная версия более эффективна, но, возможно, стоило бы пожертвовать некоторой производительностью ради регулярности.
Вы можете найти остальную часть кода на реализация сортировки по основанию
General Radix сортировать:
template <typename T>
using Iter_value = std::iterator_traits<T>::value_type;
// intermediate struct to get partial template specialization
template<typename Iter, typename T, size_t range = 256>
struct rdx_impl {
static void rdx_sort(Iter begin, Iter end, int bits) {
// bits is # bits to consider up to if a max val is known ahead of time
// most efficent (theoretically) when digits are base n, having lg(n) bits
constexpr size_t digit_bits {8}; // # bits in digit, 8 works well for 32 and 64 bit vals
size_t d {0}; // current digit #
for (long long mask = (1 << digit_bits) - 1;
d * digit_bits < bits;) {// ex. 0x000000ff for setting lower 8 bits on 32 bit num
cnt_sort(begin, end, range, Digit_cmp<T>(mask, digit_bits*d));
++d;
}
}
};
// specialization of rdx_sort for strings
struct Shorter {
template <typename Seq>
bool operator()(const Seq& a, const Seq& b) { return a.size() < b.size(); }
};
template <typename Iter>
struct rdx_impl<Iter, std::string> { // enough to hold ASCII char range
static void rdx_sort(Iter begin, Iter end, int) {
// ignore additional int argument
int len_max = std::max_element(begin, end, Shorter())->size();
for (int d = len_max - 1; d >= 0; --d)
cnt_sort(begin, end, 128, Digit_cmp<std::string>(d));
}
};
// generic call interface for all iterators
template <typename Iter> // use intermediate struct for partial specialization
void rdx_sort(Iter begin, Iter end, int bits) {
rdx_impl<Iter, Iter_value<Iter>>::rdx_sort(begin, end, bits);
}
Подсчет сортировки для сортировки по каждой цифре (по месту):
template <typename Iter, typename Op>
void cnt_sort(Iter begin, Iter end, size_t range, Op op) {
using T = typename Iter::value_type;
std::vector<int> counts(range); // init to 0
for (auto i = begin; i != end; ++i) // count # elems == i
++counts[op(*i)];
for (size_t i = 1; i < range; ++i)
counts[i] += counts[i-1]; // turn into # elems <= i
std::vector<T> res(end - begin);
for (auto j = end;;) {
--j;
res[--counts[op(*j)]] = *j;
if (j == begin) break;
}
// ~18% of time is spent on copying
std::copy(res.begin(), res.end(), begin);
}
Извлечь значение цифры:
template <typename T> // overload digit_cmp for non-integral types top provide radix sort with digits
class Digit_cmp { // functor for comparing a "digit" (particular bits)
const long long mask; // 0..63 bitfield to test against
const size_t to_shift;
public:
Digit_cmp(long long m, size_t ts) : mask{m}, to_shift{ts} {}
// by default assumes integral, just shifts
size_t operator()(T n) const { // char assuming r = 8
return (n >> to_shift) & mask; // shift then mask for unit digit
}
};
// specialization for strings
template <>
class Digit_cmp<std::string> {
const size_t digit;
public:
Digit_cmp(size_t d) : digit{d} {}
size_t operator()(const std::string& str) {
// 0 indicates past the end of the string
return str.size() > digit ? str[digit] : 0;
}
};