字符串(1)——字符串排序

字符串排序

无数的重要领域都是基于字符串处理的。本章将会学习一些经典的字符串处理算法。

我们将学习两类不同的字符串排序算法。
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~N21小数组或是已经有序的数组
快速排序Nlog2NlogN适用于空间不足的情况
三向快速排序N~NlogNlogN大量重复键
低位优先NwN较短的定长字符串
高位优先N~NwN+WR随机字符串
三向字符串N~NwW+logN含有较长公共前缀
归并排序Nlog2NN稳定的通用排序


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值