只调用一次的递归叫做尾递归
基数排序
算法思想
基数排序需要经历d次,d为所要排序数列中位数最多的数的位数,其过程是首先根据数列中数的个位的数值将所有数入09这10个队列,然后从09将元素依次出队,然后再根据十位元素的数值再次入队,然后出队,以此类推重复d次,最终即可完成排序。
时间空间复杂度及稳定性
- T(n)=O(d*n) d为排序数中最大数的位数
- S(n)=O(n)
- 稳定
代码
void radixSort(vector<int> v) {
int d = GetMaxBit(v);
int *count = new int[10];
queue<int> q[10];
int radix = 1;
for (int i = 0; i < d; ++i) {
for (int j = 0; j < v.size(); ++j) {
int t;
t = (v[j] / radix) % 10;
q[t].push(v[j]);
}
int p = 0;
for (int k = 0; k < 10; ++k) {
while (!q[k].empty()) {
v[p++] = q[k].front();
q[k].pop();
}
}
radix *= 10;
}
show(v);
}
注意
对于任何的基数都可以归纳出算法,而不仅仅是以10做基数。比如可以把二进制的每四位作为一个数字,也就是用16作为基数,表的数目将和基数相等但是要保证从低位开始将数分配到表中。
整数幂
场景介绍
很多时候我们需要求实数x的n次方即xn,按照常规做法一般会对x进行n次自乘以得到xn,但是这是非常低效的,因为它需要senta(n)次乘法,按照输入的大小来说它是指数级的。
归纳法思路
一个比较高效的归纳算法是令m=int(n/2),假设已经知道如何计算xm,那么根据xm次方来计算x^n次方就有两种情况:
- n为偶数 则xn=(xm)^2
- n为奇数 则xn=x(xm)^2
归纳法实现代码(Exprec)
int power(int x,int n){
if (n==0){
return 1;
}
int m=n/2;
int y;
y=power(x,n/2);
y=y*y;
if (n%2!=0){//如果n是奇数
y=y*x;
}
return y;
}
迭代法实现思路
上述归纳法实现求xn的关键部分在于采用递归不断判断n/2的奇偶性,所以我们可以采用迭代的办法,因为一个数除以2的k次方后的奇偶性由其化为二进制数的第k低位决定的(因为除法除以2就相当于二进制的左移操作),所以我们可以将n化为二进制数字d_k,d_(k-1)……d_0,从y=1开始,从左到右扫描二进制数字,如果当前二进制数字为0,则对应递归情况下的偶数情况即应该y=y2,否则即为y=y(ym)2
迭代法实现代码(Exp)
int Exp(int x,int n){
int d[10];//假设n化为2进制数字后存在d数组里面
int y=1;
for (int i = len(d); i >=0 ; --i) {
y=y*y;
if(d[i]==1){
y=y*x;
}
}
}
总结
假设每次乘法的时间是常数,那么这两种方法所需的运行时间都是 senta(lohn) ,他们对于输入大小来说都是 线性 的。
多项式求值(Horner规则)
场景介绍
假设有n+2个数a_0,a_1,……,a_n和x序列,要对多项式 P_n(x)=a_nxn+a_(n-1)*x(n-1)+…+a_1x
求值,传统的办法是分别对每一个子项求值,然后再对整个式子求值,但是这种方法很低效,因为它需要n+(n-1)+(n-2)+……+1=n(n+1)/2次乘法。
归纳法解决思路
首先我们发现原式可进行如下化简:
化简之后我们可以发现如果我们假设已知P_(n-1)(x),那么P_n(x)=x*P_(n-1)(x)+a_0,所以就有了算法HORNER。
HORNER算法代码实现
int Horner(int *A,int n,int x){//数组A的长度为n+2,从A[0]到A[n+1]代表了a_0到a_(n+1)
int p=A[n+1];//p=a_(n+1)
for (int i = 1; i <=n ; ++i) {
p=p*x+A[n+1-i];//p=p*x+a_(n-i)
}
}
寻找多数元素
场景描述
令A[1…n]是一个整数序列,如果该序列中的某一个数x在该序列中出现的次数多余int(n/2),则称x为该序列的 多数元素 。
解决方法
- 蛮力法 将每个元素与其他因素进行比较,并且对每一个元素计数,如果某个元素的计数大于int(n/2),就可以断言它是多数元素。但是这种方法的比较次数是n(n-1)/2=senta(n^2),代价过于昂贵。
- 利用排序 先将原序列进行排序,在最坏情况下,排序这一步需要oumiga(nlogn)次比较。
- 寻找中间元素 因为多数元素排序后一定是中间元素,可以找到该序列的中间元素后扫描整个序列该中间元素的出现次数来验证该元素是否为多数元素,由于中间元素可以再senta(n)时间内找到,这个方法要花费senta(n)时间。
- MAJORITY算法 首先我们需要知道,去掉一个序列中的两个不同的数后该序列原来的多数元素现在依然是新序列的多数元素,所以我们……我们能怎么样呢,这不好描述啊,还是看代码吧……
代码实现
int majority(int *A, int n) {
int c = candidate(A, 1, n);
int count = 0;
for (int i = 1; i <= n; ++i) {
if (A[i] == c) {
count++;
}
}
if (count > (int) n / 2) {
return c;
} else {
return NULL;
}
}
int candidate(int *A, int m, int n) {
int j = m;
int c = A[m];
int count = 1;
while (j < n && count > 0) {
j++;
if (A[j] == c) {
count++;
} else {
count--;
}
}
if (j == n) {
return c;
} else {
return candidate(A, j + 1, n);
}
}