不看《算法4》都不知道字符串排序也能讲这么多,之前还以为就是简单的strcmp就完事了,不过看了发现没这么简单,没这么简单的话,就赶紧来学一学吧。
2.0 字符串排序思路
之前是不打算写这个的,直到我看到后面了之后,才发现需要写一个对比,才能更好的理解后面的字符排序法,所以就起了一个0的序号。
从排序说起,我们学过的排序有冒泡,快排等,但是仔细想了一下,我们之前用的排序都是直接用值比较,不过字符串我们也有函数,strcmp,但是这样的话效率就很低下,所以有必要来研究研究字符串排序的方法。
2.1 键索引计数法
这篇文章本来想看懂再写的,结果发现,这个建索引计数法,怎么都看不懂,还拖慢了进度,那就干脆一遍写一遍看,这样效率反而搞了点。
2.1.1 题目
我们就按《算法4》的题目自己实现一遍,说实话,我现在还没看懂,不过可以先实现、
老师在统计学生分数时会遇到以下数据处理问题。学生分为若干组,标号为1 、2 、3等。在某些情况下,我们希望全部同学按组分类。因为组的编号是较小的整数,使用键索引计数法来排序很合适。
假设数组stu[]中的每个元素都保存了一个名字和一个组号,其中组号在0-R-1之间,如下:
struct student
{
char name[50];
int groud;
};
student stu[] = {
{"Anderson", 2},
{"Brown", 3},
{"Davis", 3},
{"Carcia", 4},
{"Harris", 1},
{"Jackson", 3},
{"Johnson", 4},
{"Martin", 3},
{"Martinez", 2},
{"Miller", 2},
{"Moore", 1},
{"Robinson", 2},
{"Smith", 4},
{"Taylor", 3},
{"Thomas", 4},
{"Thompson", 4},
{"White", 2},
{"Williams", 3},
{"Wilson", 4},
};
//strsort的构造函数,就是把数组准备好
StrSort::StrSort(int count_size)
{
m_count = new int[count_size];
memset(m_count, 0, count_size*sizeof(int));
m_count_size = count_size;
}
2.1.2 频率统计
频率统计其实就是统计一共有多少个共同的key。
- 使用int数组count[]计算每个键出现的频率
- 对于数组中的每个元素,都使用它的键访问count[]中的相应元素并将其加一
代码如下:
/*
使用int数组count[]计算每个键出现的频率
对于数组中的每个元素,都使用它的键访问count[]中的相应元素并将其加一
*/
int *StrSort::FreqCount(student *stu, int stu_len)
{
for(int i = 0; i < stu_len; i++) {
int key = stu[i].groud;
m_count[key + 1]++;
printf("key %d count %d\n", key+1, m_count[key + 1]);
}
return m_count;
}
统计之后的图示:
2.1.3 将频率转换为索引
使用count[]来计算每个键在排序结果中的起始索引位置
例:第一组有3个人,第二组有5个人,因此第三组中的同学在排序结果数组中的起始位置为8。
白话:其实就是把,每个组的基数算出来,然后每个组就在这个基数上操作。
代码如下:
/*
使用count[]来计算每个键在排序结果中的起始索引位置
例:第一组有3个人,第二组有5个人,因此第三组中的同学在排序结果数组中的起始位置为8
*/
int StrSort::ConvIndex()
{
for(int r=0; r < R; r++) {
m_count[r+1] += m_count[r]; //把前面的个数一直叠加
printf("ConvIndex r+1=%d = %d r=%d =%d\n", r+1, m_count[r+1], r, m_count[r]);
}
return 0;
}
图示:
其实我很不理解,一个一维数组干啥用二维的方式表示,我之前的看的懵逼了,你们如果也懵逼的话,就要把这个图当做一维数组来看。
ConvIndex count[0]=0
ConvIndex count[1]=0
ConvIndex count[2]=3
ConvIndex count[3]=8
ConvIndex count[4]=14
这样画不香么
2.1.4 数据分类
在将count[]数组转换成一张索引表之后,将所有元素移动到一个辅助数组aux[]中进行排序。
每个元素在aux[]中的位置是由它的键对应的count[]值决定。
代码如下:
/*
在将count[]数组转换成一张索引表之后,将所有元素移动到一个辅助数组aux[]中进行排序。
每个元素在aux[]中的位置是由它的键对应的count[]值决定。
*/
int StrSort::Datafen(student *stu, int stu_len)
{
student *aux = new student[stu_len];
for(int i=0; i<stu_len; i++) {
int key = stu[i].groud; //找到key
int index = m_count[key]++; //然后通过key找到索引
aux[index] = stu[i]; //然后填进对应的坑位中
}
for (int i=0; i<stu_len; i++) //回写
{
stu[i] = aux[i];
}
for (int i=0; i<stu_len; i++)
{
printf("stu %s %d\n", stu[i].name, stu[i].groud);
}
delete[] aux;
return 0;
}
图示:
第一个图我也没看,没眼看,不知道为什么画的这么复杂,增加理解难度啊,真是的、
第二个还是比较容易理解的,就是通过索引,然后找到自己对应的坑位就可以了。
2.1.5 回写
这个就简单了,因为我们在排序的时候,是使用到了辅助数组,所以最后一步需要我们把排序后的结果赋值回去。
2.1.6 完整代码路径
代码已经写出来,不过感觉封装的太水了,目前先这样吧,路径:…/code/strSort.cpp
2.2 低位优先的字符串排序
好像我看着就是在从低位开始,一遍一遍的使用建索引计数法就行的排序。
下面直接上代码:
void LSD::sort(std::string *a, int a_len, int w)
{
//通过前w个字符将a[]排序
int N = a_len;
int R = 256;
std::string *aux = new std::string[N];
for (int d= w-1; d >=0; d--)
{
//根据第d个字符用键索引计数法排序
int *count = new int[R+1];
memset(count, 0, (R+1)*4);
for (int i = 0; i< N; i++) // 计算频率
{
count[a[i].at(d) + 1]++;
//printf("%c ", a[i].at(d));
}
for (int r =0; r < R; r++) // 将频率转换为索引
{
count[r+1] += count[r];
//printf("r %d %d\n", r, 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];
// printf("\n");
}
delete[] aux;
}
std::string temp[] = {
"4PGC938",
"2IYE230",
"3CIO720",
"1ICK750",
"10HV845",
"4JZY524",
"1ICK750",
"3CIO720",
"10HV845",
"10HV845",
"2RLA629",
"2RLA629",
"3ATW723"
};
#if 1
int main(int argc, char *argv[])
{
printf("aa\n");
LSD::sort(temp, 13, 7);
for (int i=0; i<13; i++)
{
printf("%d %s\n", i, temp[i].c_str());
}
return 0;
}
#endif
是不是有点奇怪,说从低位排起,按照我们的习惯应该是高位排起,不过仔细想想从低位排期会先把低位就绪,然后再排高位,很反过来排序的效果是一样的。
下一次就可以安排高位优先排序了。
完整代码路径:…/code/LSD.cpp