不看《算法4》都不知道字符串排序也能讲这么多,之前还以为就是简单的strcmp就完事了,不过看了发现没这么简单,没这么简单的话,就赶紧来学一学吧。
之前是不打算写这个的,直到我看到后面了之后,才发现需要写一个对比,才能更好的理解后面的字符排序法,所以就起了一个0的序号。
从排序说起,我们学过的排序有冒泡,快排等,但是仔细想了一下,我们之前用的排序都是直接用值比较,不过字符串我们也有函数,strcmp,但是这样的话效率就很低下,所以有必要来研究研究字符串排序的方法。
这篇文章本来想看懂再写的,结果发现,这个建索引计数法,怎么都看不懂,还拖慢了进度,那就干脆一遍写一遍看,这样效率反而搞了点。
我们就按《算法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; }
频率统计其实就是统计一共有多少个共同的key。
代码如下:
/* 使用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; }
统计之后的图示:
使用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
这样画不香么
在将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; }
图示:
第一个图我也没看,没眼看,不知道为什么画的这么复杂,增加理解难度啊,真是的、
第二个还是比较容易理解的,就是通过索引,然后找到自己对应的坑位就可以了。
这个就简单了,因为我们在排序的时候,是使用到了辅助数组,所以最后一步需要我们把排序后的结果赋值回去。
代码已经写出来,不过感觉封装的太水了,目前先这样吧,路径:…/code/strSort.cpp
好像我看着就是在从低位开始,一遍一遍的使用建索引计数法就行的排序。
下面直接上代码:
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