每日算法之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)