学习内容均来自由胡凡、曾磊主编的《算法笔记》,对其中的内容进行总结整理方便自己的复习和省察
日期处理问题一般分为两种,一种是给定起始日期和天数,要求计算指定天数后的日期是什么;另一种是给定头尾两个日期,计算它们之间的天数。这类问题主要要考虑到的有:平闰年问题和大小月问题,因此有些细节问题会比较复杂。其他的类似于制作一张万年历表,都是基于上述两种问题得来的。
可以用模拟的方法进行求解。就像我们日常生活中的计数模式一样,把天数一天天地加上去,如果遇到需要进位的地方就月份加一,日数置一或年份加一,月份置一。为了方便读取每个月的天数,直接给定二维数组用来存放每个月的天数(二维是为了表示闰年),规定第二维为0时是平年,1时是闰年。
为提升速度,可以将年份先加到与第二个日期的年份相差1为止(如果同年容易错误计算跨年情况,如18年1月与16年4月)。
为了数组的一维数的下标与实际的月份对应,共设立13个月,将第0个月置空。
//问题描述:
//有两个日期,求两个日期之间的天数,如果两个日期是连续的,
//则规定它们之间的天数为两天
//输入:20130101
//输入:20130105
//输出:5
#include
using namespace std;
int month[13][2]={
{
0,0},{
31,31},{
28,29},{
31,31},{
30,30},{
31,31},{
30,30},
{
31,31},{
31,31},{
30,30},{
31,31},{
30,30},{
31,31} };
bool isleap(int year){
return((year%4==0&&year%100!=0) || (year%400==0));
}
int main(){
int time1,y1,m1,d1;
int time2,y2,m2,d2;
while(cin>>time1>>time2){
if(time1>time2){
int temp = time1;
time1 = time2;
time2 = temp;
}
y1=time1/10000,m1=time1%10000/100,d1=time1%100;
y2=time2/10000,m2=time2%10000/100,d2=time2%100;
int ans=1;
while(y1<y2||m1<m2||d1<d2){
d1++;
if(d1==month[m1][isleap(y1)]+1){
m1++;
d1=1;
}
if(m1==13){
y1++;
m1=1;
}
ans++;
}
cout<<ans<<endl;
}
return 0;
}
散列(Hash)是一种常用的用空间换时间的做法。个人的理解是在两两列表之间产生某种对应的映射关系。比如数组就是其中的一种,它把存入的整数或字符映射成了数组的下标,供你进行快速地访问和判断。
比如以下为一道例题的求解过程。
给出N个正整数,再给出M个正整数。问这M个正整数是否分别在N个正整数中出现过。如N个正整数{2,3,4,5}和M个正整数{1,2},其中只有2在里面出现过。显然,它可以用循环遍历的方法求解,但是其时间复杂度会达到O(NM),因此散列将会是不错的办法。即另开一个数组记录N集合中的数字是否出现过,出现过的数字(2,3,4,5)作为新开数组的下标,并将其作标记,在M集合中的数字出现后,便可以通过M中的数字对应到新开数组下标所对应的标记,看看是否出现过,完成本题。
代码如下。
#include
using namespace std;
const int maxn = 10010;
bool hashTable[maxn] = {
false};
int main(){
int n,m,x;
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>x;
hashTable[x] = true;
}
for(int i=0;i<m;i++){
cin>>x;
if(hashTable[x]){
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
}
return 0;
}
通过新建一个简单的hashTable就完成了这道题,巧妙地利用空间提升了速度。但是,在这道题中,输入的是不大的数字,因此正好可以利用数组的坐标进行标记与判断。一旦输入的数字过大,或者是输入的为一段文字,此时的方法就不够“万能”。因此,我们可以通过一种方法,将输入的东西转换成数组的下标或是其他我们想标记的元素,这种方法往往被定义成一种函数,即为散列函数H。
将元素通过一个函数转换为整数,使得该整数可以尽量唯一地代表这个元素
常用的方法有
可以想见,常用的这几种方法都有很多不足之处如空间上的浪费。但最重要的是很难确保映射的唯一性。如最常用的除留余数法,产生的数最多是[0,mod)中的数,如果需要更多的对应关系则难以招架,这样的情况叫做“冲突”。因此,我们用其他的方法来解决这些冲突。
开放地址法 即为为产生冲突的地址求得一个地址序列(依照一定的次序探测判断)如:
线性探测再散列:原有的H(key)被占用了以后,通过不断地+1或者是-1或者是其他的常数+c/-c寻找没有被占用的H(key)
平方探测再散列:原有的H(key)被占用了以后,通过不断地+/-c^2寻找没有被占用的H(key)
随机探测再散列:同上,在寻找新地址的时候采用一个随机数
再哈希法 顾名思义,再使用一次哈希法,会增加计算的时间
链地址法 不计算新的hash值,而是把所有H(key)相同的Key值用链表连接成一条单链表,最后再按指示串联起来。
公共溢出区法 开辟一个溢出表,发生冲突的所有记录都填入溢出表,最后再进行计算
上述方法也是哈希查找的做法,将待查找的元素通过哈希函数映射成正整数,在查找的时候就可以十分方便地找到自己想要的结果。不过在一般情况下,可以使用STL中的map来直接使用hash的功能更为方便,不用自己写哈希表。
以书中的问题结尾:
给出N个字符串,再给出M个查询字符串,问每个查询字符串在N个字符串中出现的次数。
#include
const int maxn = 100;
char S[maxn][5],temp[5];
int hashTable[26*26*26+10];
int hashFunc(char s[],int len){
int id=0;
for(int i=0;i<len;i++){
id=id*26+(s[i]-'A');
}
return id;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>s[i];
int id=hashFunc(s[i],3); //将字符串转换为整数
cout<<"id="<<id<<endl;
hashTblechar[id]++; //该字符串的出现次数+1
}
for(int i=0;i<m;i++){
cin>>temp;
int id=hashFunc(temp,3); //将字符串temp转换为整数
cout<<hashTblechar[id]<<endl; //输出该字符串出现的次数
}
return 0;
}