字符串排序
无数的重要领域都是基于字符串处理的。本章将会学习一些经典的字符串处理算法。
我们将学习两类不同的字符串排序算法。
1)从右到左检查键中的字符。这种方法一般被称为低位优先,适用于键的长度都相同的字符串排序。
2)从左到右检查键中的字符,这种方法一般被称为高位优先,和快排类似,通过递归来快速切分。
键索引计数法
做为两种排序方法的基础,我们需要用到键索引计数法。
他的作用是通过键的出现频率构造索引,然后用过索引排序数组。
具体分为四部:
1)频率统计:count数组计算每个键出现的频率,存储到count[r+1]
2)将频率转换为索引:从前往后count[r+1]都加上count[r],构造出索引的位置
3)数据分类:将元素复制到辅助数组aux[]中进行排序
4)回写:将排序后的元素复制回原数组
代码:
int N = a.size();
vector<string> aux(N);
vector<int> count(R + 1,0);
for (int i = 0; i < N; i++)//计算出现频率
count[a[i].key() + 1]++;
for (int r = 0; r < R; r++)//将频率转换为索引
count[r + 1] += count[r];
for (int i = 0; i < N; i++)//将元素分类
aux[count[a[i].key()]++] = a[i];
for (int i = 0; i < N; i++)//回写
a[i] = aux[i];
低位优先的字符串排序
低位优先的字符串排序算法能够稳定地将定长字符串排序。
缺点就是字符串长度必须是固定的。
具体步骤就是,从右往左对字符串的每一个字符进行键索引计数法排序。
代码:
#ifndef LSD_H
#define LSD_H
#include<string>
using std::string;
#include<vector>
using std::vector;
class LSD
{
public:
void sort(vector<string> &a, int w)//w为字符串长度
{
int N = a.size();
int R = 256;//字符的数量
vector<string> aux(N);
for (int d = w - 1; d >= 0; d--)
{
vector<int> count(R + 1,0);
for (int i = 0; i < N; i++)//计算出现频率
count[a[i].at(d) + 1]++;
for (int r = 0; r < R; r++)//将频率转换为索引
count[r + 1] += count[r];
for (int i = 0; i < N; i++)//将元素分类
aux[count[a[i].at(d)]++] = a[i];
for (int i = 0; i < N; i++)//回写
a[i] = aux[i];
}
}
};
#endif
测试用例:
#include<iostream>
#include<fstream>
#include"LSD.h"
using namespace std;
int main()
{
vector<string> a;
string aa;
while (cin >> aa)
a.push_back(aa);
LSD s;
s.sort(a,a[0].size());
for (auto w : a)
cout << w << endl;
system("pause");
return 0;
}
高位优先的字符串排序
要实现一个通用字符串排序算法(字符串长度不一定相等),我们应该考虑从左向右遍历所有的字符。这种思想的一个很自然的方法就是一种递归算法,即高位优先的字符串排序。
具体方法是:将字符串从左往右开始对字符进行键索引计数法排序,然后递归对每个索引频率内的数据单独进行下一位字符排序。(类似于快排)
因为字符串长度是不同的,所以我们应该注意在计算频率时将长度小于所查字符的单独放一栏。所以我们需要对at()函数进行重构,当没有所查字符时返回-1,而键索引计数法也需要将查找的位置后移一位。
代码:
#ifndef MSD_H
#define MSD_H
#include<string>
using std::string;
#include<vector>
using std::vector;
class MSD
{
private:
int R = 256;//基数
int M = 15;//小数组的切换阙值
vector<string> aux;
int at(string s, int d){ if (d < s.size())return s.at(d); else return -1; }
public:
void sort(vector<string> &a)
{
int N = a.size();
aux=vector<string>(N);
sort(a, 0, N - 1, 0);
}
void sort(vector<string> &a, int lo, int hi, int d)
{//以d个字符为键将a[lo]至a[hi]排序
if (hi <= lo)return;
//if(hi<=lo+M){insertsort(a,lo,hi,d);return;}
vector<int> count(R + 2);
for (int i = lo; i <= hi; i++)//计算频率
count[at(a[i], d) + 2]++;
for (int r = 0; r < R + 1; r++)//将频率转换为索引
count[r + 1] += count[r];
for (int i = lo; i <= hi; i++)//数据分类
aux[count[at(a[i], d) + 1]++] = a[i];
for (int i = lo; i <= hi; i++)//回写
a[i] = aux[i - lo];
//递归的以每个字符为键进行排序
for (int r = 0; r < R; r++)
sort(a, lo + count[r], lo + count[r + 1] - 1, d + 1);
}
};
#endif
测试用例:
#include<iostream>
#include<fstream>
#include"MSD.h"
using namespace std;
int main()
{
vector<string> a;
string aa;
while (cin >> aa)
a.push_back(aa);
MSD s;
s.sort(a);
for (auto w : a)
cout << w << endl;
system("pause");
return 0;
}
三向字符串快速排序
与三向快排相类似,将字符串以首字符进行三切分快排,然后递归对切分出来的同类子集进行次一字符的快排,直到元素字符排空为止。
代码:
#ifndef QUICK3STRING_H
#define QUICK3STRING_H
#include<string>
using std::string;
#include<vector>
using std::vector;
class Quick3string
{
private:
int at(string s, int d){if (d < s.size())return s.at(d);else return -1; }
void eaxh(vector<string> &a, int i,int j)
{
string t = a[i];
a[i] = a[j];
a[j] = t;
}
public:
void sort(vector<string> &a){ sort(a, 0, a.size() - 1, 0); }
void sort(vector<string> &a, int lo, int hi, int d)
{
if (hi <= lo)return;
int lt = lo, gt = hi;//左切分指针和右切分指针
int v = at(a[lo], d);//提取切分的字符
int i = lo + 1;//遍历指针
while (i <= gt)
{
int t = at(a[i], d);//查看的字符
if (t < v)eaxh(a, lt++, i++);
else if (t > v)eaxh(a, i, gt--);
else i++;
}
sort(a, lo, lt - 1, d);
if (v >= 0)sort(a, lt, gt, d + 1);
sort(a, gt + 1, hi, d);
}
};
#endif
测试用例:
#include<iostream>
#include<fstream>
#include"Quick3string.h"
using namespace std;
int main()
{
vector<string> a;
string aa;
while (cin >> aa)
a.push_back(aa);
Quick3string s;
s.sort(a);
for (auto w : a)
cout << w << endl;
system("pause");
return 0;
}
算法性能对比
字符串排序算法的性能特点:
算法 | 稳定性 | 原地排序 | 运行时间 | 额外时间 | 优势邻域 |
插入排序 | 是 | 是 | N~N2 | 1 | 小数组或是已经有序的数组 |
快速排序 | 否 | 是 | Nlog2N | logN | 适用于空间不足的情况 |
三向快速排序 | 是 | 否 | N~NlogN | logN | 大量重复键 |
低位优先 | 是 | 否 | Nw | N | 较短的定长字符串 |
高位优先 | 是 | 否 | N~Nw | N+WR | 随机字符串 |
三向字符串 | 否 | 是 | N~Nw | W+logN | 含有较长公共前缀 |
归并排序 | 是 | 否 | Nlog2N | N | 稳定的通用排序 |