2022-04-23 字符串排序法三则

经典排序如快排,堆排,插排等都是普世的排序,对于字符串这种特定的排序类型,还有一些比较讨巧的方法。

为啥还有讨巧的方法,快排已经优雅的不行不行的了。

因为string不是一个基本的原子式不可拆分的类型,他是由一串字符组成。

再优雅的基于比较的排序也要基于一个一个字符的比较。

基本的排序方法是一个一个的比较,但针对字符串排序的特有方法则是更接近于位图排序

我们知道,char类型的字符可以转换为0-255的数值,这个字符就可以映射到一个数组之中,我们第一步可以取所有字符串的第一个字符,映射到一个大小为 256 + 2 的数组中(多两位还有作用),每映射一次,数组对应位置的后边第二位 +1 ,这样,我们就统计出了每种字符有多少个,并且是自然排序的。

然后将此数组进行迭代加法,让第 n+1 位的数值等于 n 位数值 加上 n+1 位数值,此时的统计数组就自然变成某个字母在原字符串数组中排序位置的起始和最后。

例如:有这样一串字符串:

{"ba","ac","aa","ae"}

假如 a 对应 0,b 对应 1 ,其构成的统计数组就是:

{0, 0, 3, 4}

有 3 个 a ,1 个 b ,所以首字母为 a 的字符串应该在数组中的位置是 0 - 2, b 的位置是 3, 让我们看看是如何操作的:

我们设一个 buf 数组,大小同字符串数组,当我开始遍历原字符串数组时,第一个就是 ba,字母 b 映射 是1,取 b + 1 在统计数组的值,为 3 ,那么 ba 在 buf 数组中就是第 3 位

{"", "", "", "ba"}

同时统计数组自增为:

{0, 0, 4, 4}

然后我们遍历其余的 ac ,aa ,ae,它们的位置是 a + 1 在统计数组数值,也就是从0,自增 1,自增 2,自增 3

buf就变为

{"ac", "aa", "ae", "ba"}

同时统计数组自增为:

{0, 3, 4, 4}

此时统计数组又充当分组标签,我们将 ( 0 , 3 - 1), (3 ,4 - 1)对应的字符串子数组再次进行排序,此时取第二个字符作为定位字符,也就是 c a e,进行排序,ba由于只有一个,不必排序。

这种排序方法之所以讨巧,是因为没有比较一个多余的字母。

当然,也有缺陷,和快排一样,这种方法崇尚随机,如果所有的字符串都一样,反而消耗是最大的,此时,最好的方法不是这种特有排序,也不是快排,而是插排。

以下是源代码,注释里已经比较明确了。

#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

namespace STRST
{
//字符串索引排序,用于排序有索引的字符串组
struct StrKeySort
{
    explicit StrKeySort(std::istream &file)
    {
        read(file);
    }
    explicit StrKeySort(const std::string &filename)
    {
        std::ifstream file(filename);
        read(file);
    }

    void sort()
    {
        for (unsigned i = 0; i != size; ++i)
        {
            Count[strKey[i].second + 1]++;
        }
        for (unsigned r = 0; r != group + 1; ++r)
        {
            Count[r + 1] += Count[r];
        }
        std::vector<std::pair<std::string, unsigned>> bufStrVec(size);
        for (unsigned i = 0; i != size; ++i)
        {
            bufStrVec[Count[strKey[i].second]++] = strKey[i];
        }
        strKey = bufStrVec;
    }

  private:
    void read(std::istream &file)
    {
        std::string temp;
        unsigned num = 0;
        strKey.reserve(32);
        Count.reserve(8);
        while (file >> temp >> num)
        {
            strKey.emplace_back(temp, num);
            ++size;
            if (num > group)
            {
                group = num;
            }
        }
        Count = std::vector<unsigned>(group + 2, 0);
    }
    std::vector<std::pair<std::string, unsigned>> strKey;
    std::vector<unsigned> Count;
    unsigned size = 0;
    unsigned group = 0;
};

//同长度字符串排序,对于同等长度字符串排序,W是字符串的长度
void lowStrSort(std::vector<std::string> &str, unsigned W)
{
    unsigned vecSize = str.size();
    unsigned charSize = 256;
    std::vector<std::string> bufStrVec(vecSize);
    for (int chOrder = static_cast<int>(W) - 1; chOrder >= 0; chOrder--)
    {
        std::vector<unsigned> cntTable(charSize + 1, 0);
        for (int i = 0; i != vecSize; ++i)
        {
            cntTable[str[i].at(chOrder) + 1]++;
        }
        for (int r = 0; r != charSize; ++r)
        {
            cntTable[r + 1] += cntTable[r];
        }
        for (int i = 0; i != vecSize; ++i)
        {
            bufStrVec[cntTable[str[i].at(chOrder)]++] = str[i];
        }
        for (int i = 0; i != vecSize; ++i)
        {
            str[i] = bufStrVec[i];
        }
    }
}

//普通不等长字符串组排序,适合英文排序。
struct MSD
{
    static void sort(std::vector<std::string> &strVec)
    {
        int vecSize = static_cast<int>(strVec.size());
        bufStrVec = std::vector<std::string>(vecSize);
        if (vecSize > 1)
        {
            sort(strVec, 0, vecSize - 1, 0);
        }
    }

  private:
    static const int charSize = 256;
    static const int minSortSize = 15;
    static std::vector<std::string> bufStrVec;
    static auto charAt(const std::string &word, unsigned chOrder) -> int
    {
        if (chOrder < word.size())
        {
            return word.at(chOrder);
        }
        return -1;
    }
    static void sort(std::vector<std::string> &strVec, unsigned lo, int hi,
                     unsigned chOrder)
    {
        if (hi <= static_cast<int>(lo) + minSortSize)
        {
            std::sort(strVec.begin() + lo, strVec.begin() + hi + 1);
            return;
        }
        // 建立计数表 cntTable
        std::vector<unsigned> cntTable(charSize + 2);
        // 将 strVec 中第 i 个字符串中 chOrder 字符对应的数值(空则为-1)+ 2
        // 设以上值为 num
        // 将 cntTable 中以 num 为下标的值加1.
        // 如果 chOrder 字符为空, 则 cntTable[1] 的值 + 1.
        for (unsigned i = lo; i != hi + 1; ++i)
        {
            cntTable[charAt(strVec[i], chOrder) + 2]++;
        }
        // strVec 中所有 string 的 chOrder 位置字母为 alpha
        // cntTable 在 alpha 值 + 2 位置的值 cntTable[num] 代表 alpha 的个数
        // 空字符个数在 cntTable[1] 中
        // 循环累加
        // 使得 cntTable[alpha + 1] 对应 alpha 在 bufStrVec 中起始位置。
        for (unsigned r = 0; r != charSize + 1; ++r)
        {
            cntTable[r + 1] += cntTable[r];
        }
        // bufStrVec[num] 复制 strVec[i] 的 string
        // num 是 cntTable[alphaBegin] 的值
        // alphaBegin 是 strVec[i] 的 string 中 chOrder 位置字母 alpha + 1 的值
        // 复制后 num 自增 1,
        // 让后序相同 alpha 在 bufStrVec 中确定位置
        // 最后相同 alpha 拷贝完成,num 为 bufStrVec 中最后 alpha 位置索引 +1
        // 此时的 num 就是下一个 alpha 的起始位置
        for (unsigned i = lo; i != hi + 1; ++i)
        {
            bufStrVec[cntTable[charAt(strVec[i], chOrder) + 1]++] = strVec[i];
        }
        // 把根据 chOrder 位置字母排序的结果,拷贝回 strVec
        for (unsigned i = lo; i != hi + 1; ++i)
        {
            strVec[i] = bufStrVec[i - lo];
        }
        // 在 chOrder 处的字符已经排序完毕
        // 如果 chOrder 相同字符 alpha 有多个 string 则进行 chOrder + 1
        // 字符beta的排序 lo + cntTable[r] 是 alpha 在 strVec 起始位置 lo +
        // cntTable[r + 1] - 1 是 alpha 在 strVec 终止位置 if 的判断条件是 alpha
        // 的起始位置小于终止位置, 也就是相同 alpha 的 string 个数大于1
        for (unsigned r = 0; r != charSize; ++r)
        {
            if (static_cast<int>(lo + cntTable[r]) <
                static_cast<int>(lo + cntTable[r + 1]) - 1)
            {
                sort(strVec, lo + cntTable[r],
                     static_cast<int>(lo + cntTable[r + 1]) - 1, chOrder + 1);
            }
        }
    }
};

std::vector<std::string> MSD::bufStrVec;

} // namespace STRST

auto main(int /*argc*/, char * /*argv*/[]) -> int
{
    //  STRST::StrKeySort test("stu1.txt");
    //  test.sort();
    //  STRST::lowStrSort(test2, 3);
    std::vector<std::string> test2{
        "she",    "sells", "by",    "the", "sea",    "shore",    "tha",
        "shells", "she",   "sells", "are", "surely", "seashells"};
    std::vector<std::string> test3{"ab", "a"};
    STRST::MSD::sort(test3);
    for (const auto &i : test3)
    {
        std::cout << i << "\n";
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不停感叹的老林_<C 语言编程核心突破>

不打赏的人, 看完也学不会.

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值