正文
前几天参加一家公司的实习招聘,笔试中有一道题,是让对单词按字母进行排序。
单词结构体类似如下,其中word中的字符均在小写字母a~z范围内:
struct words{
string word;
word*next;
...
};
这是链表结构。
对链表的排序,如果是原址排序,一般复杂度都是
O(n2)
对于链表中的关键字——string类型的str,如果进行比较排序,每次都调用比较操作运算(如果是C语言,则是使用库函数strcmp进行比较),平均字符比较次数与单词长度相关(单词长度对时间复杂度的影响在这里未作深究)。
如果要取得 nlog(n) 的时间复杂度,必须先花费 O(n) 的时间将链表的每个元素复制到一个数组,再使用快排、堆排序等方法。
这里我使用一种类似基数排序的方法,可以使整个排序达到 O(n) 的时间复杂度。大致思路如下:
构造一个容器,一共26个槽,对应a~z的26个字母。对于链表中的每个单词,依据第i个字母:如果存在,放入对应的槽中;不存在,则增加屏蔽计数。然后对槽中数目超过1的递归上述过程。
c++代码如下:
void rescursort(vector<words*>&pwords, int idx = 0){
//pwords是存进vector的链表指针向量 idx 表示字母下标数
vector<vector<words*>> ps(26);
vector<words*> temp;
int count = 0;
for each (auto var in pwords)
{
if (var->word[idx] - 'a' >= 0)
ps[var->word[idx] - 'a'].push_back(var);
else{
count++;
temp.push_back(var);
}
}
for (int i = 0; i < count; i++){
pwords[i] = temp[i];
}
for (int i = 0; i < 26; i++){
if (ps[i].size()>1){
rescursort(ps[i], idx + 1);//
for (int k = 0; k < ps[i].size(); k++){
pwords[count++] = ps[i][k];
}
}
else if (ps[i].size()){
pwords[count++] = ps[i][0];
}
}
}
void sortwords(words*p){
int count[26];
vector<words*> ps;
for (words*pc = p->next; pc; pc = pc->next){
ps.push_back(pc);
}
rescursort(ps);
words*prev = p;
for (int k = 0; k<ps.size(); k++){
prev->next = ps[k];
prev = ps[k];
}
prev->next = NULL;
}
存取链表用时
O(2n)
递归共用时间
O(L∗n)
,L是单词最大长度。
一共
O((2+L)n)
总结
这种方法对于大量长度较短且较均匀的字符序列或者多维数进行排序比较合适,但由于二维向量的使用,对于长度不确定或是比较长的关键字还是使用比较排序节省空间。