首先所谓的"字符串是否包含问题"的意思是指:字符串"abcde",是否包含字符串"abc"里的全部字符。
一、直观办法:挨个比较
对被匹配字符串"abc"的每个字符,在字符串"abcde"里判断是否存在。
很显然,时间复杂度是O(strlen(原字符串) * strlen(被匹配字符串)),在尤其原字符串长度较大时会比较慢
二、改进版:预先对原字符串进行排序,然后对被匹配字符串的每个字符在排序后的原字符串中二分查找判断是否存在:
void swap (std::string &raw, int i, int j) {
if (i == j) {
return;
}
char tmp = raw[i];
raw[i] = raw[j];
raw[j] = tmp;
}
void qsort (std::string &longstr, int start, int end) {
if (start >= end) {
return;
}
char lastchar = longstr[end];
int i = start, j = start;
for (; i < end; i++) {
char curchar = longstr[i];
if (curchar <= lastchar) {
swap(longstr, i, j);
++j;
}
}
swap(longstr, j, end);
qsort(longstr, start, j - 1);
qsort(longstr, j + 1, end);
}
排序后的查找:
bool binarysearch (const std::string raw, const char ch) {
int start = 0, end = raw.size() - 1;
while (start < end) {
char curchar = raw[(start + end)/2];
if (ch > curchar) {
start = (start + end)/2 + 1;
} else if (ch < curchar) {
end = (start + end)/2 - 1;
} else {
return true;
}
}
return false;
}
bool qsortscan (const std::string shortstr, const std::string longstr) {
bool flag = true;
for (int i = 0; i < shortstr.size(); i++) {
if (false == binarysearch(longstr, shortstr[i])) {
std::cout << "qsortscan found not " << shortstr[i] << std::endl;
flag = false;
}
}
return flag;
}
平均时间复杂度:O(strlen(原字符串) * log(strlen(原字符串))) + O(log(strlen(原字符串)) * strlen(被匹配字符串)),第一部分是排序的时间,第二部分是查找的时间
在原字符串长度较大时,这种方法的排序时间将导致更加严峻。
三、hash:最快
因为需要查找的是字符是否存在,所以假定原字符串和被匹配字符串的字符都是ASCII字符,那么建一个256大小的hash表即可,建hash表后然后判断就很快了:
void hashinit (const std::string longstr, int *hashtable) {
for (size_t i = 0; i < longstr.size(); i++) {
int idx = longstr[i] - '\0';
hashtable[idx] = 1;
}
}
bool hashscan (const std::string shortstr, int *hashtable) {
bool flag = true;
for (size_t i = 0; i < shortstr.size(); i++) {
int idx = shortstr[i] - '\0';
if (hashtable[idx] == 0) {
std::cout << "hashscan found not " << shortstr[i] << std::endl;
flag = false;
}
}
return flag;
}
hash方式的时间复杂度:O(strlen(原字符串)) + O(1 * strlen(被匹配字符串)),大幅度降低时间复杂度。
附测试程序:
int main () {
//方法1: 最快的方法是一个最简单的hash, 因为需要查看是否包含的是字符, 所以假定原字符串和被匹配字符串里都是ASCII字符, 那么建一个256大小的hash表即可
//建hash表的时间复杂度是O(strlen(原字符串)), 空间复杂度是256, 判断是否包含的时间复杂度是O(1 * strlen(被匹配字符串)), 这里是O(5)
std::string longstr = "987319874387387874111432rerewqjfdsabjkfdsadsaewqjfdvcgr17659875987zzzaa";
for (int i = 0; i < 10; i++) {
longstr += longstr;
}
std::string shortstr = "acegi";
std::cout << longstr.size() << std::endl;
int hashtable[256] = {0};
hashinit(longstr, hashtable);
std::cout << "hash result: " << hashscan(shortstr, hashtable) << std::endl;
//方法2: 先排序, 快排吧, 然后再对被匹配字符串的每个字符依次进行二分查找
//快排时间复杂度O(strlen(原字符串) * log(strlen(原字符串))), 判断是否包含的时间复杂度是O(log(strlen(原字符串)) * strlen(被匹配字符串))
//总的时间复杂度是: O(strlen(原字符串) * log(strlen(原字符串)) + O(log(strlen(原字符串)) * strlen(被匹配字符串)), 和hash方式没法比
//当然还有最直观的方法3: 被匹配字符串每个字符挨个顺序比较...时间复杂度是O(strlen(原字符串) * strlen(被匹配字符串))
//当原字符串长度非常长的时候就方法2和方法3的时间复杂度就很可怕了
qsort(longstr, 0, longstr.size() - 1);
std::cout << qsortscan(shortstr, longstr) << std::endl;
return 0;
}
可以发现这字符串较大时快排是方法2的瓶颈。