每日算法之5
1.之重温查找算法
//二分查找:针对于有序序列
int B_search(int a[],int length,int e){
if(a==NULL || length<1)
return -1;
int low,high,mid;
low=0;
high=length-1;
while(low<=high){
mid=(low+high)/2;
if(a[mid]==e)
return mid;
else if(a[mid]>e)
high=mid-1;
else
low=mid+1;
}
return -1;
}
//递归改编:
int B_search(int a[],int low,int high,int e){
if(a==NULL)
return -1;
int mid=(low+high)/2;
if(low<=high){
if(a[mid]==e)
return mid;
else if(a[mid]>e)
return B_search(a,low,mid-1,e);
else
return B_search(a,mid+1,high,e);
}else
return -1;
}
//测试用例
int main(){
int a[]={2,4,6,6,9,10};
int e=6;
cout<<e<<"的下标位置为:"<<B_search(a,6,6);
}
//时间复杂度为:基本操作为mid取值,循环结束的关键在于mid=1,假设循环k次结束,此时mid=n/2^k
//n/2^k+a=1,a为补偿,化简可得时间复杂度为O(logn);;
2.之重温排序算法
//问题描述:将一个公司的几万员工按照年龄进行排序,要求时间复杂度为O(n),空间复杂度不超过O(n)
//算法分析:由于问题规模就是n,要求时间复杂度为O(n),也就是说,对每个数据的操作,只能一次;只能够扫描一遍,就能排好序列;;考虑使用桶排序,分配0~99个桶,只需要扫描一一遍员工的年龄数组,对应的桶加一;
//最后将所有桶的次数直接输出即可
//算法实现:
void bucketSortAge(int ages[],int length){
if(ages==NULL || length<1)
throw "empty error!";
const int ageMAX=100;
int countAge[ageMAX];//定义0~99的存储年龄次数的数组
//对数组进行初始化
for(int i=0;i<ageMAX;++i)
countAge[i]=0;
//遍历一次,将相应的年龄次数加一
for(int i=0;i<length;++i){
int age=ages[i];
if(age<0 || age>ageMAX-1)
throw "age error!";
++countAge[age];
}
//将桶中的年龄次数数据重新还原存入ages数组中
int j=0;//表示ages的下标
for(int countAgeIndex=0;countAgeIndex<ageMAX;++countAgeIndex){
for(int i=0;i<countAge[countAgeIndex];++i){
ages[j++]=countAgeIndex;
}
}
}//注:不要养成思维误区,看到双重循环,并不代表时间复杂度就是平方阶,要学会抓住时间复杂度的本质
//是执行基本操作的次数,循环只是累加这个次数,而关键在于循环到什么时候开始结束!!
//测试样例
void generateRandom(int a[],int length,int Max){
srand((unsigned)time(NULL));
for(int i=0;i<length;++i){
a[i]=rand()%Max;
}
}
int main(){
int ages[30000];
generateRandom(ages,30000,100);
bucketSortAge(ages,30000);
for(int i=0;i<30000;++i)
cout<<ages[i]<<" ";
cout<<endl;
}
//算法分析:该算法只使用了100个int数组的辅助空间,却完成了O(n)的时间复杂度
3.使用二分查找寻找旋转数组的最小的数字
//输入一个递增数组的前若干个数的一个旋转,要求找出其中最小的数字
//如:数组{3,4,5,1,2}为{1,2,3,4,5}一个旋转,其最小值为1
//算法分析:如果直接使用顺序查找,时间复杂度为O(n),显然没有利用旋转数字的特性
//注意到,在旋转前k个数字后,数组分为前一半有序,后一半也有序,中间数字5为最大的数,
//其后一位数即为最小的数,使用二分查找,如果中间值mid大于前一个数,直接往上半区查找,直到
//找到一个mid小于前一个的数,即为最小值
//时间复杂度为:O(logn)
//算法实现:
int findMin(int rotate[],int length){
if(rotate==NULL || length<1)
throw "empty error!";
int low,mid,high;
low=0;
high=length-1;
while(low<=high){
mid=(low+high)/2;
if(rotate[mid]>rotate[mid-1])
low=mid+1;
else
return rotate[mid];
}
return rotate[0];//如果没有找到,说明旋转数组旋转了0个数,最小值即为第一个数
}
//测试用例:
int main(){
int a[]={1,2,3,4,5,-2,0};
cout<<"最小值为:"<<findMin(a,7);
int b[]={1,2,3,4,5}
cout<<"最小值为:"<<findMin(b,5);
}
/*输出结果:
最小值为:-2
最小值为:1
*/
//问题变种:如果输入的是一个非递减的数组,即有可能含有相同的数字,又当如何?
//如:{1,1,0,0,0,0}
//问题分析:二分查找的关键在于剔除掉不会含有所求值的区间,那么怎样确定这个区间是否含有所求值,需要什
//么条件?
//分析问题的基本思路:先针对一般的情况分析,如果一般情况复杂,则进行分解为多个简单的情况;;一般情况分析完毕,再分析特殊的情况,对代码进行完善!!可以在一个函数里调用另一个函数,轻松完善代码的不足之处!
//算法实现:
int findMin_2(int re[],int length){
if(re==NULL || length<1)
throw "empty error!";
int low,mid,high;
low=0;
high=length-1;
while(re[low]>=re[high]){
if(high-low==1){
mid=high;
break;
}
mid=(low+high)/2;
//直接使用顺序查找
if(re[low]==re[high]&&re[low]==re[mid])
return findMin_order(re,low,high);
//否则使用二分查找
if(re[mid]>=re[low])
low=mid;
else if(re[mid]<=re[high])
high=mid;
}
return re[mid];
}
int findMin_order(int re[],int low,int high){
int res=re[low];
for(int index=low+1;index<=high;++index){
if(res>re[index])
res=re[index];
}
return res;
}
//思考总结:二分查找的核心逻辑三大要点
/*
1.什么样的区间才会含有所求值,而什么样的区间不会含有所求值
2.缩小区间到什么时候就能找到所求值
3.满足什么条件退出查找
4.其他的特殊情况!!必须做特殊的处理
*/
4.之计算2的N次方[涉及大数计算及进位原理]
//问题分析:如果N=10000,显然即使unsigned long long有心而长度不足,这个使用必须使用字符数组+
//进位的原理进行解决,即每次按照位数,每次乘以2时逢十进一
void calulator(int n){
if(n<0)
throw "n error!";
int MAX=10000;
char result[MAX];//定义存入结果的字符数组
int multiNum;//表示乘以2的次数
int flag;//表示进位数
int resNum;//表示字符数组当前的存入字符个数
int temp;//表示当前位的临时数字
int bit;//表示位数
//进行初始化
for(int i=0;i<MAX;++i)
result[i]='0';
resNum=1;
result[0]='1';
for(multiNum=0;multiNum<n;++multiNum){
flag=0;
for(bit=0;bit<resNum;++bit){
//循环将计算结果从第一位字符数字取出进行计算,逢十进一,存储之后,再取出下一高位!!
temp=result[bit]-'0';//转为数字计算!!
temp=temp*2+flag;
flag=0;//必须进行清0,保证是最后一位flag>0时,后面的if才会执行
if(temp>=10){
flag=temp/10;
temp=temp%10;
}
result[bit]=temp+'0';//转换为字符
}
if(flag>0){
result[bit]=flag+'0';
++resNum;//表示最后一位flag不为0时进位,存储,并字符数组个数加一
}
}
if(resNum>MAX)
throw "Max error!";
for(int i=resNum-1;i>=0;--i){//必须反向输出!!
cout<<result[i];
}
cout<<endl;
cout<<"字符个数为:"<<resNum<<endl;
}
//测试用例:
int main(){
while(1){//使用循环测试
cout<<"请输入n=:";
int n;
cin>>n;
try{
calulator(n);
}catch(const char *e){
cout<<e;
}
}
}
//输入33333
//输出:MAX error!
//表示最大能够计算33333次方!!
//算法时间复杂度分析:假设每乘以4次进一位,则乘以n次,基本操作数为n^2/16+3n+8,
//即时间复杂度为O(n^2)