一、判断字符串a中是否包含字符串b(或者字符串a中包含字符串b的字母个数)
1。使用最笨的方法,依次扫描
//时间复杂度为O(lenLong*lenShort) int isContain(char* longString,char* shortString){ int lenLong=strlen(longString); int lenShort=strlen(shortString); if(lenLong<lenShort){ return 0; } int j; for(int i=0;i<lenShort;i++){ for(j=0;j<lenLong;j++){ if(longString[j]==shortString[i]){ break; } } if(j==lenLong){ cout<<"false"<<endl; return 0; } } cout<<"true"<<endl; return 1; }
2,排序之后再扫描(其中采用快速排序的复杂度为O(mlogm+nlogn)+o(m+n)),采用计数排序的复杂度为O(m+n)
//方法二,先对两个字符串中的字母进行排序,然后对两个字符串进行轮循 //两个字符串排序,复杂度为(mlogm+nlogn),之后扫描需要o(m+n),因此一共的复杂度为o(mlogm)+o(nlogn)+o(m+n) ///首先,实现对于字符的快速排序算法 int partition(char* str,int low,int high){ int i=low; int j=high; int pivot=str[i]; while(i<j){ while(i<j&&str[j]>=pivot){ j--; } if(i<j){ str[i]=str[j]; i++; } while(i<j&&str[i]<=pivot){ i++; } if(i<j){ str[j]=str[i]; j--; } } str[i]=pivot; return i; } void quickSort(char *arr,int start,int end){ int i; if(start<end){ i=partition(arr,start,end); quickSort(arr,start,i-1); quickSort(arr,i+1,end); } } ///比较排序之后的两个字符串,进行线性扫描,时间复杂度为O(m+n) int isContainByMethod2(char* strLong,char* strShort){ int nLong=strlen(strLong); int nShort=strlen(strShort); ///如果有重复的字母,排序是什么一种情况 int i,j; for(i=0;i<=nLong-nShort;i++){ if(strLong[i]==strShort[0]){ for(j=0;j<=nShort-1;j++){ if(strLong[i+j]!=strShort[j]){ break; } } if(j==nShort){ cout<<"true"<<endl; return 1; } } } cout<<"flase"<<endl; return 0; } /******************方法三,同方法二,只不过排序采用的是线性排序法,这里采用计数排序的方法,排序O(n+m),线性扫描o(m+n);一共o(m+n)+o(m+n)=o(m+n)********/ void countSort(char* str){ int help[26]={0}; int len=strlen(str); char* help_str=new char[len]; int i,n,pos; for(i=0;i<len;i++){ n=str[i]-'a'; help[n]++; } for(i=1;i<26;i++){ help[i]+=help[i-1]; } //将每个元素放在合适的位置 for(i=len;i>=0;i--){ n=str[i]-'a'; pos=help[n]-1; help_str[pos]=str[i]; //这个是考虑原来数组可能有相同元素 help[n]--; } for(i=0;i<len;i++){ str[i]=help_str[i]; } delete[] help_str; }
3,采用hash的方法(如果引入大素数,倒确实是一种有趣的思路,其中牵涉到大数除法,这里暂略)
void isContain(string str_long,string str_short){ int hash[26]={0}; int len_short=str_short.length(); int len_long=str_long.length(); for(int i=0;i<len_short;i++){ int index=str_short[i]-'a'; hash[index]=1; } for(int i=0;i<len_long;i++){ int index=str_long[i]-'a'; if(hash[index]==1){ hash[index]=0; } } int i=0; for(i=0;i<26;i++){ if(hash[i]==1){ break; } } if(i==26){ cout<<"true"<<endl; }else{ cout<<"false"<<endl; } }
4,采用hash结合bitmap的方法
采用位的思想来构建辅助数组,可以大大降低控件复杂度,这中思想在解决内存有限的去重、查找、排序问题中很常见
void isContainByHashAndBitmap(string str_long,string str_short){ int len_short=str_short.length(); int len_long=str_long.length(); //char 在内存中占据一个字节的存储空间 //这里需要借助4个字节,不过这里不再借助char[4]数组了,而是采用一个int类型 //char *hash=new char[4]; int dictionary=0; for(int i=0;i<len_long;i++){ dictionary|=getBit(str_long[i]); } int i; for(i=0;i<len_short;i++){ if(dictionary!=(dictionary|getBit(str_short[i]))){ break; } } if(i==len_short){ cout<<"true"<<endl; }else{ cout<<"false"<<endl; } }
5,判断两个字符串是否匹配,采用hash的方法
//判断字符串是否匹配,即两个字符串含有的字符个数相同,只是顺序不同 //这里对于字符串包含重复字符的情况也考虑在内了 bool is_match(const char* strOne,const char* strTwo){ int lenOfOne=strlen(strOne); int lenOfTwo=strlen(strTwo); if(lenOfOne!=lenOfTwo){ return false; } int hash[26]={0}; for(int i=0;i<lenOfOne;i++){ int index=strOne[i]-'a'; hash[index]++; } for(int i=0;i<lenOfTwo;i++){ int index=strTwo[i]-'a'; if(hash[index]!=0){ hash[index]--; }else{ return false; } } return true; }
6,查找一个字符串中子字符串的位置
#include<iostream> #include<cstring> using namespace std; int main(){ string str_long="abcdef"; string str_short="cd"; int findsubstring(string,string); int result=findsubstring(str_long,str_short); cout<<result<<endl; return 0; } //查找子字符串的位置,还可以考虑排序之后再查找,或者通过KMP算法,其复杂度为O(m+n) int findsubstring(string str_long,string str_short){ int len_long=str_long.length(); int len_short=str_short.length(); for(int i=0;i<len_long-len_short;i++){ if(str_long[i]==str_short[0]){ int j; for( j=0;j<len_short;j++){ if(str_long[i+j]!=str_short[j]){ break; } } if(j==len_short){ cout<<"true"<<endl; return i; } } } cout<<"false"<<endl; return 0; }
7,找出一个字符串中第一次只出现一次的字符
同样利用hash的方法,
#include<iostream> #include<cstring> using namespace std; int main(){ string str="eeooidjia"; char find_first_unique_char(string); cout<<find_first_unique_char(str)<<endl; return 0; } char find_first_unique_char(string str){ int hash[26]={0}; int len=str.length(); for(int i=0;i<len;i++){ int index=str[i]-'a'; hash[index]++; } for(int i=0;i<len;i++){ int index=str[i]-'a'; if(hash[index]==1){ return str[i]; } } return '/0'; }
8,将字符串转换为对应的数字输出,如“-12635”转换为-12635输出
(1) char转换为int的方法 -'0'
(2) '+'、'-'的符号考虑
(3) 大型数字溢出的考虑
#include<iostream> #include<cstring> #include<assert.h> using namespace std; //实现功能,将字符串转换为对应的数字,如“123”输出数字123 //需要注意的问题 //1,如果使用的指针,需要判断指针是否为空 //2,如果输入的字符串中包含有不是数字的字符,那么碰到这些非法字符的是偶,给出出错信息,判断数字是否在‘0’到‘9’之间 //3,数字可能还包含“+”,“-”号,利用一个符号位来记录该信息 //4,如果输入的是字符串形式的数字,可能存在溢出的可能,判断溢出的标志是在处理符号之前判断是否>0 int main(){ string str="-23876"; int str_2_int(string); cout<<str_2_int(str)<<endl; return 0; } int str_2_int(string str){ assert(str.length()>0); int pos=0; int sys=1; if(str[pos]=='+'){ sys=1; pos++; }else if(str[pos]=='-'){ sys=-1; pos++; } int num=0; for(pos;pos<str.length();pos++){ assert(str[pos]>='0'); assert(str[pos]<='9'); num=num*10+(str[pos]-'0'); //处理溢出 assert(num>=0); } num*=sys; return num; }
9,字符串的复制操作,主要看考虑是否周全
#include<iostream> #include<cstring> #include<assert.h> using namespace std; //实现功能,将字符串转换为对应的数字,如“123”输出数字123 //需要注意的问题 //1,如果使用的指针,需要判断指针是否为空 //2,如果输入的字符串中包含有不是数字的字符,那么碰到这些非法字符的是偶,给出出错信息,判断数字是否在‘0’到‘9’之间 //3,数字可能还包含“+”,“-”号,利用一个符号位来记录该信息 //4,如果输入的是字符串形式的数字,可能存在溢出的可能,判断溢出的标志是在处理符号之前判断是否>0 int main(){ string str="-23876"; int str_2_int(string); cout<<str_2_int(str)<<endl; return 0; } int str_2_int(string str){ assert(str.length()>0); int pos=0; int sys=1; if(str[pos]=='+'){ sys=1; pos++; }else if(str[pos]=='-'){ sys=-1; pos++; } int num=0; for(pos;pos<str.length();pos++){ assert(str[pos]>='0'); assert(str[pos]<='9'); num=num*10+(str[pos]-'0'); //处理溢出 assert(num>=0); } num*=sys; return num; }
10,找出N(可以是个海量数据)个数中最小的K个数
思路:取出N个数中开始的k个数字构成一个初始的最大堆
遍历k到N中间的数字,和heap[0]比较,如果比heap[0]小,那么就替换heap[0],重新调整这个最大堆
在文档上还有快速排序达到O(n)复杂度的讨论,没怎么看明白,有时间再研究吧
#include<iostream> #include<cstring> using namespace std; void swap(int* a,int* b){ if(*a!=*b){ *a=*a^*b; *b=*a^*b; *a=*a^*b; } } int leftChildPos(int pos){ return (pos<<1)+1;//注意位移的优先级小于+,因此这里的括号一定不能省 } int rightChildPos(int pos){ return (pos+1)<<1; } int parent(int pos){ return (pos-1)>>1; } bool isLeaf(int pos,int MAXLEN){ return pos>=(MAXLEN>>1)&&pos<MAXLEN; } void adjustHeap(int array[],int i,int len){ int largeIndex=i; int left=leftChildPos(i); int right=rightChildPos(i); if(left<len&&array[largeIndex]<array[left]){ largeIndex=left; } if(right<len&&array[largeIndex]<array[right]){ largeIndex=right; } if(largeIndex!=i){ swap(array[i],array[largeIndex]); adjustHeap(array,largeIndex,len); } } void buildHeap(int array[],int len){ if(array==NULL){return;} int i; for(i=len/2-1;i>=0;i--){ adjustHeap(array,i,len); } } int main(){ int MAXLEN=10000; int data[10000]={0}; int i=0; for(i=0;i<MAXLEN;i++){ data[i]=MAXLEN-i; } int k=10; int heap[10]={0}; for(int i=0;i<k;i++){ heap[i]=data[i]; } cout<<"排序之前的数组为:"<<endl; for(int i=0;i<10;i++){ cout<<heap[i]<<" "; } cout<<endl; buildHeap(heap,k); cout<<"堆初始化之后的数组:"<<endl; for(int i=0;i<10;i++){ cout<<heap[i]<<" "; } cout<<endl; int newData=-1; for(int i=10;i<MAXLEN;i++){ newData=data[i]; if(newData<heap[0]){ heap[0]=newData; adjustHeap(heap,0,k); } } cout<<"建立起来的最大堆为:"<<endl; for(int i=0;i<k;i++){ cout<<heap[i]<<" "; } cout<<endl; //备注:建立起来最大堆,得到了最小的k个数,如果想要顺序排列的话,采用堆排序即可 return 0; }
11.Top K问题
问题10的对立面,在实际中非常有用,即寻找最大的k个数字的问题,可以采用的算法有,这在自己的论文个性化搜索中可以体现,
比如,统计搜索引擎中的热门查询词汇
(这一点可以运用到论文的个性化搜索模块,如何将大众总体的热门搜索和用户的搜索历史融合起来,提供个性化服务,这一点值得考虑,也可以作为自己对后台的贡献部分)
所谓hashTable,就是把关键码值(key value)直接进行访问的方法,把关键码值映射到表中一个位置的方法叫做散列,存放记录的数组就叫做散列表。
哈希表hashtable(key,value) 的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位(文章第二、三部分,会针对Hash表详细阐述)。在下面从数量庞大的字符串数组中,判断某个特定的字符串是否存在的问题中,会用到这一点
因此对于这个问题:
第一步:query统计
维护一个key为query字符串,value为该字符串出现次数的hashtable(利用java里面内置的hashtable数据结构的话,相当于字符串映射为地址码的这一步,有系统帮助完成了)
第二步:top10问题解决
维护一个大小为10的最小堆,和解决找出最小的k个值的思路是一样的。
12,和上面一道题目的关系是同样涉及了hashtable的概念,判断一个字符串时候存在于一个很大的字符串数组中
#include<iostream> using namespace std; //问题描述,有一个庞大的字符串数组,给一个单独的字符串,让你从这个字符串数组中判断是否有这个字符串并找到它 //思路:第一步:通过某种算法,将以个字符串“压缩”成一个整数,当然,无论如何,一个32位的整数是无法对应回一个字符串的(,这就是one-way hash算法的思想,这也说明funf的加密方法是不可逆的?),不过在程序中,两个字符串计算出hash值相等的可能性非常小 //第二步:通常的想法会是,逐个比较字符串的hash数值,但这种解决方案远远不够,要想得到最快的算法,就不能进行逐个的比较,通常是构造一个哈希表来解决问题,哈西表的大小根据程序进行定义,例如1024,每一个hash值通过取模运算对应到数组中的一个位置,这样,只要比较这个字符串的的哈希值对应的位置有没有占用,就可以得到最后的结果,这样事件复杂度就是O(1)<不过,最初大数组生成hash,确定对应位置是否占用,不是依旧需要遍历一次么?> //可能的问题是“两个字符串在哈希表中对应的位置相同怎么办”,解决方法很多,一种方法就是“链表” //但是一个更绝的方法是,其基本原理是,在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串,一个用于哈西表的下标,另外两个用来验证 //总上,算法的流程为: //1,计算出字符串的三个哈希值 //2,察看哈希表中的这个位置 //3,哈希表中该位置是否为空,如果为空,那么该字符串不存在,返回-1 //4,如果存在,那么检查其他两个哈希值是否也匹配,如果匹配,表示找到了该字符串,返回该hash值 //5,移到下一个位置,如果已经移动到了表的末尾,就反转到表的开头继续查询 //6,看看是不是回到原来位置,如果是,就返回没有找到 //7,回到3 //下面的代码仅是测试:准备hash表,并将一个字符串映射为一个数值 #include <stdio.h> #include <ctype.h> //多谢citylove指正。 //crytTable[]里面保存的是HashString函数里面将会用到的一些数据,在prepareCryptTable //函数里面初始化 unsigned long cryptTable[0x500]; //以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500] void prepareCryptTable() { unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; for( index1 = 0; index1 < 0x100; index1++ ) { for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 ) { unsigned long temp1, temp2; seed = (seed * 125 + 3) % 0x2AAAAB; temp1 = (seed & 0xFFFF) << 0x10; seed = (seed * 125 + 3) % 0x2AAAAB; temp2 = (seed & 0xFFFF); cryptTable[index2] = ( temp1 | temp2 ); } } } //以下函数计算lpszFileName 字符串的hash值,其中dwHashType 为hash的类型, //在下面GetHashTablePos函数里面调用本函数,其可以取的值为0、1、2;该函数 //返回lpszFileName 字符串的hash值; unsigned long HashString( char *lpszFileName, unsigned long dwHashType ) { unsigned char *key = (unsigned char *)lpszFileName; unsigned long seed1 = 0x7FED7FED; unsigned long seed2 = 0xEEEEEEEE; int ch; while( *key != 0 ) { ch = toupper(*key++); seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2); seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3; } return seed1; } //在main中测试argv[1]的三个hash值: //./hash "arr/units.dat" //./hash "unit/neutral/acritter.grp" int main( int argc, char **argv ) { unsigned long ulHashValue; int i = 0; if ( argc != 2 ) { printf("please input two arguments/n"); return -1; } /*初始化数组:crytTable[0x500]*/ prepareCryptTable(); /*打印数组crytTable[0x500]里面的值*/ for ( ; i < 0x500; i++ ) { if ( i % 10 == 0 ) { printf(" "); } printf("%-12X", cryptTable[i] ); } ulHashValue = HashString( argv[1], 0 ); printf(" ----%X ---- ", ulHashValue ); ulHashValue = HashString( argv[1], 1 ); printf("----%X ---- ", ulHashValue ); ulHashValue = HashString( argv[1], 2 ); printf("----%X ---- ", ulHashValue ); return 0; } //如果要完整实现上面的代码: typedef struct { int nHashA; int nHashB; char bExists; ...... } SOMESTRUCTRUE; int GetHashTablePos( har *lpszString, SOMESTRUCTURE *lpTable ) //lpszString要在Hash表中查找的字符串,lpTable为存储字符串Hash值的Hash表。 { int nHash = HashString(lpszString); //调用上述函数二,返回要查找字符串lpszString的Hash值。 int nHashPos = nHash % nTableSize; if ( lpTable[nHashPos].bExists && !strcmp( lpTable[nHashPos].pString, lpszString ) ) { //如果找到的Hash值在表中存在,且要查找的字符串与表中对应位置的字符串相同, return nHashPos; //则返回上述调用函数二后,找到的Hash值 } else { return -1; } }
13,libFunction
#include<stdio.h> #include<stdlib.h> #include<assert.h> //copy the first n char to a destStr char *strncpy(char* strDes,const char *strSrc,unsigned int count){ assert(strDes!=NULL&&strSrc!=NULL); char *address=strDes; while(count--&&*strSrc!='