【问题标题】:Radix Sort on an Array of Strings?对字符串数组进行基数排序?
【发布时间】:2014-04-13 02:59:02
【问题描述】:

我一直在研究,虽然我已经弄清楚了使用基数排序来按字母顺序排列字符串数组的总体思路,但我知道我走错了方向。

这是我目前所拥有的:

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();
        }
    }
}

到目前为止,我已经按第一个字符对所有字符串进行了排序,并且我知道我必须遍历并制作剩余字符的子数组并对它们进行排序,但是我怎样才能有效地实现它呢?字符串大小可变,可以包含空格,例如:

  • 细分
  • 主要街道
  • 裤子
  • 刺穿非殖民化
  • 泥质
  • 轴向满意度
  • 脾气暴躁
  • 过敏
  • 熊
  • 头发宽度
  • 奶油激增
  • 不劳而获
  • 更骚
  • 最糟糕的
  • 毛里塔尼亚人
  • 发射器
  • 好评
  • zouaves 盘子
  • 旅行
  • 唯心主义
  • 报酬
  • 增溶
  • 轮廓分明
  • 颈静脉
  • 软软的
  • 烤面包机
  • 波特
  • 后缀
  • 无能为力的消息
  • 异化
  • 喘气
  • 调情
  • 嗯

这是我发现似乎有用的东西: http://algs4.cs.princeton.edu/lectures/51DemoKeyIndexedCounting.pdf

【问题讨论】:

  • 基数排序不太适合不同大小的元素。
  • 是的,很遗憾,这是我项目的最后一部分,我需要实现不同的排序算法并比较它们的执行时间。我当然希望我可以使用不同的方法!
  • 我有 3 条建议。 1:允许超过 27 个可能的输入。例如,您在某些要排序的项目中有空格。 2:使用递归。 3:识别出只有一个或更少元素的输入已经排序。

标签: c++ arrays sorting radix-sort


【解决方案1】:

您找到的幻灯片很棒!但是这些队列在您的代码中是从哪里来的呢?

不管怎样,你来了 (live example):

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() 包含在实时示例中并生成问题中给出的字符串。

我不想在这里解释这是如何工作的,我假设您可以弄清楚,因为您正在解决这个问题。但是有几个 cmets 是有序的。

  • 我们既没有对输入序列进行就地排序,也没有返回排序后的副本;我们只是返回排序后的输入元素的位置序列。

  • 我们正在从右到左处理字符串。

  • 复杂度为O(lw),其中l 是输入长度(输入字符串的数量),w 是最大输入宽度(所有输入字符串的最大长度)。所以如果字符串宽度变化不大,这个算法是有意义的。

  • radix_sort() 的第一个模板参数R 是输入中每个数字(字母)的可能值的数量。例如。 R = 128 表示可能的值为0..127。这对您的输入应该没问题。对于 ASCII 码,我没有尝试过任何聪明的做法,但您可以为此自定义函数 bin()。

  • 在bin() 的输出中,值0 保留表示“我们已超出此字符串的末尾”。此类字符串置于其他仍在继续的字符串之前。

  • 我已尝试为变量和函数提供一目了然的名称,并尽可能使用标准库调用来完成常见任务。

  • 代码是通用的,例如它可以对任何包含随机访问容器的随机访问容器进行排序,而不仅仅是字符串向量。

  • 为了方便起见,我到处使用 C++11 的特性,但实际上并没有什么必要:只需使用 C++03 就可以轻松做到这一点。

【讨论】:

  • 我明白你在做什么!我在另一个问题中问过,所以如果你在那里回答我会看到的,但你介意告诉我“使用”关键字的作用吗?非常感谢您的帮助!
  • @Kavix0 这是一个type alias,和typedef一样。
  • @iavr 因此,在了解了一些关于 typedef 之类的东西之后,我已经在 C++03 中实现了您的解决方案,并且效果很好!非常感谢!
  • @iavr 是否也可以仅使用 MSD 的位置进行排序?如果可以,可以给我一些链接吗?
【解决方案2】:

与 iavr 非常相似,但排序到位(以 iavr 的 g++ -O3 解决方案为基准,与 iavr 的 1780 毫秒相比,耗时约 2020 毫秒),享受常规界面和可重复使用的代码。 Iavr 实现的问题在于它的逻辑只适用于字符串容器,并且不容易扩展到其他类型。显然,他的专用版本更有效,但为了规律性而牺牲一些性能可能是值得的。 你可以在radix sort implementation找到其余的代码

一般基数排序:

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;
    }
};

【讨论】:

    猜你喜欢
    • 2016-07-18
    • 2017-08-01
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多