# 那个函数增长的更快?NlogN,N1+ξ/logN(ξ>0)N\log N, N^{1+\xi /\sqrt{\log N}(\xi>0) }

假设N1+ξ/logN<NlogNN^{1+\xi /\sqrt{\log N} }<N\log N, 则有Nξ/logN<logNN^{\xi /\sqrt{\log N} }<\log N
两边取对数,有ξlogNlogN<loglogN\frac{\xi}{\sqrt{\log N}}\log N <\log^{\log N}
化简得ξlogN<loglogN\xi\sqrt{\log N}<\log\log N
N=logNN=\sqrt{\log N}, 则有ξ2N<log2N\xi^2N<\log^2N
f(N)=ξ2N,g(N)=log2Nf(N)=\xi^2N,g(N)=\log^2N
limNf(N)g(N)\lim_{N\to\infty}\frac{f(N)}{g(N)} 可得极限为\infty
所以g(N)=o(f(N))g(N)=o(f(N))
假设不成立,NlogNN\log N 增长更慢

# 证明对任意常数kk,logkN=o(N)\log^kN=o(N)

只考虑正整数,对k=0,1k=0,1,基准情况成立
k1<k2k_1<k_2, 有logk1N=o(logk2N)\log^{k1}N=o(\log^{k2}N)
对于kk, 仍有limNlogkNN=0\lim_{N\to\infty}\frac{\log^kN}{N}=0, 所以对任意常数kk, 有logkN=o(N)\log^kN=o(N)

# 求两个函数f(N)f(N)g(N)g(N),使得f(N)O(g(N))f(N)\ne O(g(N))g(N)O(f(N))g(N)\ne O(f(N))

只要limNf(N)g(N)\lim_{N\to\infty}\frac{f(N)}{g(N)} 的极限在00\infty 之间摆动即可
所以有f(N)={1NisevennumberNNisoddnumberf(N)=\begin{cases} 1 & N\;is\;even\;number \\ N & N\;is\;odd\;number \end{cases} g(N)={NNisevennumber1Nisoddnumberg(N)=\begin{cases} N & N\;is\;even\;number \\ 1 & N\;is\;odd\;number \end{cases}
类似摆动数列,只不过围绕着固定值摆动幅度加大

# 假设需要生成前NN 个自然数的一个随机置换。例如,{4,3,1,5,2} 和 {3,1,4,2,5} 就是合法的置换,但 {5,4,1,2,1} 却不是,因为数11 出现两次而数33 却没有。这个程序常常用于模拟一些算法。我们假设存在一个随机数生成器RandInt(i,j)RandInt(i,j),它以相同的概率生成iijj 之间的一个整数。下面是三个算法。

  • 1 如下填入从 A[0]A[0]A[N1]A[N-1] 的数组AA; 为了填入A[i]A[i], 生成随机数直到它不同于已经生成的 A[0],A[1],...,A[i1]A[0],A[1],...,A[i-1] 时,再将其填入A[i]A[i]
随机数的生成
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]);
        }
    }

忽略掉打印语句,从最内层的forfor 循环看起,每次至少执行ii
随机数产生的概率是等可能的,所以whilewhile 语句每次循环产生不同随机数概率是NiN\frac{N-i}{N}
i=0N1\sum_{i=0}^{N-1}NiNi\frac{Ni}{N-i}<<i=0N1\sum_{i=0}^{N-1}N2Ni\frac{N^2}{N-i}<<N2i=0N11NiN^2\sum_{i=0}^{N-1}\frac{1}{N-i}
假设以最坏情况运行,则可令N1=N,i=1N-1=N,i=1
所以N2i=0N11NiN^2\sum_{i=0}^{N-1}\frac{1}{N-i}<<N2j=1N1jN^2\sum_{j=1}^{N}\frac{1}{j}
由调和数HN=i=1N1ilogeNH_N = \sum_{i=1}^{N}\frac{1}{i}\approx\log_eN
N2j=1N1jN^2\sum_{j=1}^{N}\frac{1}{j} == O(N2logN)O(N^2\log N), 即该算法时间复杂度

  • 2. 同算法 (1),但是要保存一个附加的数组,称之为UsedUsed(用过的)数组。当一个随机数RanRan 最初被放入数组AA 的时候,置 Used[Ran]=1Used[Ran]=1,。这就是说,当用一个随 机数填入A[i]A[i] 时,可以进一步来测试该随机数是否已经被使用,而不是像第一个算法那样(可能)进行ii 步测试。
第二种算法
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);
    }

该算法whilewhile 语句中少了forfor 循环
所以有i=0N1i+i=0N1NNi\sum_{i=0}^{N-1}i+\sum_{i=0}^{N-1}\frac{N}{N-i}<<i=1Ni+Nj=1N1j\sum_{i=1}^{N}i+N\sum_{j=1}^{N}\frac{1}{j}==O(N)+O(NlogN)O(N)+O(N\log N)
即时间复杂度为O(NlogN)O(N\log N)

  • 3. 填写该数组使得 A[i]=i+1A[i]=i+1, 然后:
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;
    }

时间复杂度为线性O(N)O(N)

  • a 证明这三个算法都生成合法的置换,并且所有的置换都是等可能的。
  • b 对每一个算法给出你能够得到的尽可能准确的期望的运行时间分析 (用大 OO)。
  • c 分别写出程序来执行每个算法1010 次,得出一个好的平均值。
    N=250,500,1000,2000N=250,500,1000,2000 运行程序 11;
    N=2500,5000,10000,20000,40000,80000N=2500,5000,10000,20000,40000,80000 运行程序22;
    N=10000,20000,40000,80000,160000,320000,640000N=10000,20000,40000,80000,160000,320000,640000 运行程序 33

各个算法运行时间绝赞运行中
随着重装电脑没备份,我的大部分运行时间数据已逝去。

# 编写一个程序来确定正整数NN 是否是素数

  • 11 是素数,
  • 能被22 整除的偶数和能被奇数整除的合数都不是素数,
  • 凡是合数均有num=part1part2num=part1*part2, 且有且仅有一个partpart 小于 num\sqrt{num}
素数
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;
    }

ifif 语句只执行一次,可视为常量,忽略不计
一共执行N\sqrt{N} 次,所以时间复杂度为O(N)O(\sqrt{N})

# 不用递归,写出快速求幂的程序

383^8 为例,38=3813^8=3^8*1,8=(1000)28=(1000)_2
(1000)2(1000)_244 位,从第零位开始每右移一次,便开始循环平方一次,共四次
32=333^2=3*3
34=32323^4=3^2*3^2
38=34343^8=3^4*3^4
(1000)2(1000)_2 只有一个11, 所以只进行一次乘法,即38=3813^8=3^8*1
再以3153^{15} 为例,315=383432313^{15}=3^8*3^4*3^2*3*1,15=(1111)215=(1111)_2
这次(1111)2(1111)_24411, 所以进行四次乘法,有315=383432313^{15}=3^8*3^4*3^2*3*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;
    }

时间复杂度为o(N)o(N)

# 编写一个程序求解主要元素

主要元素是一个在大小为NN 的数组出现次数大于N/2N/2 的元素,有且仅有一个

思路:

  • 1 找出主要元素的候补元 (也许没)
  • 2 判断候补元是不是主要元素

有点类似最优起点算法,majormajor 记录候补元,countcount 记录候补元出现次数,若count<0count<0, 则重置majormajorcountcount, 遍历整个数组之后再判断是否是主要元素

摩尔投票算法参考链接
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;
}

时间复杂度为O(N)O(N)