# 那个函数增长的更快?
假设, 则有
两边取对数,有
化简得
令, 则有
令
由 可得极限为
所以
假设不成立, 增长更慢
# 证明对任意常数,
只考虑正整数,对,基准情况成立
当, 有
对于, 仍有, 所以对任意常数, 有
# 求两个函数 和,使得 且
只要 的极限在 和 之间摆动即可
所以有
类似摆动数列,只不过围绕着固定值摆动幅度加大
# 假设需要生成前 个自然数的一个随机置换。例如,{4,3,1,5,2} 和 {3,1,4,2,5} 就是合法的置换,但 {5,4,1,2,1} 却不是,因为数 出现两次而数 却没有。这个程序常常用于模拟一些算法。我们假设存在一个随机数生成器,它以相同的概率生成 和 之间的一个整数。下面是三个算法。
- 1 如下填入从 到 的数组; 为了填入, 生成随机数直到它不同于已经生成的 时,再将其填入。
int RandInt(int lower, int upper) | |
{ | |
srand((unsigned)time(NULL)); | |
Sleep(500);// 滞后 0.5S | |
return rand() % upper+lower; | |
} |
此即该题万恶之源,硬件生成等概率随机数贼慢
void Random(int *arr,int lower, int upper) | |
{ | |
int i = 0; | |
while (i < upper) | |
{ | |
int temp = RandInt(lower, upper); | |
BOOL flag = 1; | |
for (int j = 0; j <=i; j++) | |
{ | |
if(arr[j]==temp) | |
{ | |
flag = 0; | |
} | |
} | |
if(flag) | |
{ | |
arr[i] = temp; | |
i++; | |
} | |
} | |
for ( i = 0; i < upper; i++) | |
{ | |
printf("%d ", arr[i]); | |
} | |
} |
忽略掉打印语句,从最内层的 循环看起,每次至少执行 次
随机数产生的概率是等可能的,所以 语句每次循环产生不同随机数概率是
即
假设以最坏情况运行,则可令
所以
由调和数 得
, 即该算法时间复杂度
- 2. 同算法 (1),但是要保存一个附加的数组,称之为(用过的)数组。当一个随机数 最初被放入数组 的时候,置 ,。这就是说,当用一个随 机数填入 时,可以进一步来测试该随机数是否已经被使用,而不是像第一个算法那样(可能)进行 步测试。
void Random1(int *arr,int lower, int upper) | |
{ | |
int *used = (int*)malloc( upper*sizeof(int) ); | |
for ( int i = 0; i < upper; i++ ) | |
{ | |
used[i] = 0; | |
} | |
int i = 0; | |
while (i < upper) | |
{ | |
int temp = RandInt(lower, upper); | |
if(used[temp-1]==0) | |
{ | |
used[temp-1] =1; | |
arr[i] = temp; | |
i++; | |
} | |
} | |
for ( i = 0; i < upper; i++) | |
{ | |
printf("%d ", arr[i]); | |
} | |
free(used); | |
} |
该算法 语句中少了 循环
所以有
即时间复杂度为
- 3. 填写该数组使得 , 然后:
for(i=1;i<N;i++) | |
Swap(&A[i],&A[RandInt(0,i)]); |
void Random2(int *arr,int lower, int upper) | |
{ | |
// 装数 | |
for ( int i = 0; i < upper; i++) | |
{ | |
arr[i] = i+1; | |
} | |
// 随机换数 | |
for ( int i = 1; i < upper; i++) | |
{ | |
Swap(&arr[i], &arr[RandInt(0,i)]); | |
} | |
// 打印数 | |
for (int i = 0; i < upper; i++) | |
{ | |
printf("%d ", arr[i]); | |
} | |
} | |
void Swap(int *a,int *b) | |
{ | |
int temp = *a; | |
*a = *b; | |
*b = temp; | |
} |
时间复杂度为线性
- a 证明这三个算法都生成合法的置换,并且所有的置换都是等可能的。
- b 对每一个算法给出你能够得到的尽可能准确的期望的运行时间分析 (用大 )。
- c 分别写出程序来执行每个算法 次,得出一个好的平均值。
对 运行程序 ;
对 运行程序;
对 运行程序 。
各个算法运行时间绝赞运行中
随着重装电脑没备份,我的大部分运行时间数据已逝去。
# 编写一个程序来确定正整数 是否是素数
- 是素数,
- 能被 整除的偶数和能被奇数整除的合数都不是素数,
- 凡是合数均有, 且有且仅有一个 小于 。
int isPrime(unsigned int N) | |
{ | |
if (N == 1) | |
return 0; | |
if (N % 2 == 0) | |
return 0; | |
if (N == 2) | |
return 0; | |
for (int i = 3; i*i <= N; i += 2) | |
{ | |
if (N % i == 0) | |
return 0; | |
} | |
return 1; | |
} |
语句只执行一次,可视为常量,忽略不计
一共执行 次,所以时间复杂度为
# 不用递归,写出快速求幂的程序
以 为例,,
有 位,从第零位开始每右移一次,便开始循环平方一次,共四次
,
,
,
只有一个, 所以只进行一次乘法,即
再以 为例,,
这次 有 个, 所以进行四次乘法,有
double Pow(double x, int n) | |
{ | |
double result = 1; | |
while (n) | |
{ | |
if (n & 1) // 等价于判断 n 的末位是不是 1 | |
result *= x; | |
n >>= 1; // 将 n 右移一位,即遍历原 n 的二进制的每一位 | |
x *= x; //n 右移了一位,x 补上 | |
} | |
return result; | |
} |
时间复杂度为
# 编写一个程序求解主要元素
主要元素是一个在大小为 的数组出现次数大于 的元素,有且仅有一个
思路:
- 1 找出主要元素的候补元 (也许没)
- 2 判断候补元是不是主要元素
有点类似最优起点算法, 记录候补元, 记录候补元出现次数,若, 则重置 和, 遍历整个数组之后再判断是否是主要元素
int MainElement(int*arr,int len) | |
{ | |
// 边界条件判断,如果数组为空就返回 - 1 | |
if(arr==NULL||len==0) | |
return -1; | |
// 先找出主要元素 | |
int major = arr[0]; | |
int count = 1; | |
for(int i=1;i<len;i++) | |
{ | |
if(arr[i]==major) | |
// 如果当前元素等于 major,count 就加 1 | |
count++; | |
else | |
// 否则 count 就减 1, | |
count--; | |
if(count<0) | |
{ | |
// 如果 count 小于 0,说明前面的 | |
// 全部抵消了,这里在重新赋值 | |
major = arr[i]; | |
count = 1; | |
} | |
} | |
// 下面是判断主要元素是否存在 | |
count = 0; | |
for(int i=0;i<len;i++) | |
{ | |
if(major==arr[i]) | |
{ | |
count++; | |
if(count>(len>>1)) | |
return major; | |
} | |
} | |
return -1; | |
} |
时间复杂度为