目录
一.算法时间复杂度的定义
定义:在计算机科学中,算法的时间复杂度是一个函数式T(N),它定量描述了该算法的运行时间。时间复杂度是衡量程序的时间效率,那么为什么不去计算程序的运行时间呢?
1. 因为程序运行时间和编译环境和运行机器的配置都有关系,比如同⼀个算法程序,用一个老编译器进行编译和新编译器编译,在同样机器下运行时间不同。
2. 同⼀个算法程序,用⼀个老低配置机器和新高配置机器,运行时间也不同。
3. 并且时间只能程序写好后测试,不能写程序前通过理论思想计算评估。
那么算法的时间复杂度是⼀个函数式T(N)到底是什么呢?这个T(N)函数式计算了程序的执行次数。假设每句指令执行时间基本⼀样(实际中有差别,但是微乎其微),那么执行次数和运行时间就是等比正相关, 这样也脱离了具体的编译运行环境。执行次数就可以代表程序时间效率的优劣。比如解决⼀个问题的算法a程序T(N)=N,算法b程序T(N)=N^2,那么算法a的效率一定优于算法b。
总结:一个算法所花费的时间与其中语句的总执行次数T(n)成正比例,算法中的基本操作的执行次数,为算法的时间复杂度.
二.大O阶渐进表示法
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导规则:
1.时间复杂度函数式T(N)中,只保留最高阶项,去掉那些低阶项,因为当N不断变大时, 低阶项对结果影响越来越小,当N无穷大时,就可以忽略不计了。
2. 如果最高阶项存在且不是1,则去除这个项目的常数系数,因为当N不断变大,这个系数对结果影响越来越小,当N无穷大时,就可以忽略不计了。
3. T(N)中如果没有N相关的项目,只有常数项,用常数1取代所有加法常数。
简而言之,就是:
1.在修改后的函数中,只保留最高阶项.
2.用1取代函数中的所有加法常数..
3.如果最高阶项存在且不是1,则去除与这个项目相乘的常数.
三.常见的时间复杂度
1.
// 计算Func1的时间复杂度?
void Func1(int N)
{
int count = 0; //1
for (int k = 0; k < 100; ++ k)
{
++count; //100
}
printf("%d\n", count); //1
}
可以看出这个函数的运行次数是101次。
根据大O阶渐进表示法:.用1取代函数中的所有加法常数,而且它并没有最高阶项,所以时间复杂度是O(1).
只要这串代码中没有N,就是常数阶,时间复杂度就是O(1).
2.
// 计算Func2的时间复杂度?
void Func2(int N)
{
int count = 0; //1
for (int k = 0; k < 2 * N ; ++ k)
{
++count; //2*n
}
int M = 10; //1
while (M--)
{
++count; //10
}
printf("%d\n", count); //1
}
可以得出:T(N)=2N+10
根据大O阶渐进表示法,在修改后的函数中,只保留最高阶项.如果最高阶项存在且不是1,则去除与这个项目相乘的常数.
得到,O(N).
3.
// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++ k)
{
++count;
}
for (int k = 0; k < N ; ++k)
{
++count;
}
printf("%d\n", count);
}
可以得出,T (N) = M + N,由于M,N都是未知数,都不知道具体大小,那我们都看为N,所以T(N)=2N
根据大O阶渐进表示法,在修改后的函数中,只保留最高阶项.如果最高阶项存在且不是1,则去除与这个项目相乘的常数.
得到,O(N)
4.
// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character)
{
const char* p_begin = s;
while (*p_begin != character)
{
if (*p_begin == '\0')
return NULL;
p_begin++;
}
return p_begin;
}
strchr执行的基本操作次数:若要查找的字符在字符串第一个位置,则: T (N) = 1;
若要查找的字符在字符串最后的一个位置, 则:T (N) = N ;
若要查找的字符在字符串中间位置,则:T (N) = N / 2 ;
因此:strchr的时间复杂度分为:最好情况:O(1) 最坏情况:O(N) 平均情况:O(N)
通过上面我们会发现,有些算法的时间复杂度存在最好、平均和最坏情况。 最坏情况:任意输入规模的最大运行次数(上界); 平均情况:任意输入规模的期望运行次数;最好情况:任意输入规模的最小运行次数(下界)。 大O的渐进表示法在实际中一般情况关注的是算法的上界,也就是最坏运行情况。
5.
// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
这个运行次数不太好看出,我们可以列一个表格辅助做题:
| i的值 | 内循环的次数 |
| i=0 | n |
| i=1 | n-1 |
| i=2 | n-2 |
| ... | ... |
| i=n-2 | 2 |
| i=n-1 | 1 |
将i从0到n-1的每个内循环执行的次数加起来可得总的执行次数为:n+(n-1)+(n-2)+...+2+1=n*(n+1)/2=n^2/2+n/2.(等差数列求和)
根据大O阶推导方法可得,这段代码的时间复杂度为O(n^2).
6.
void func5(int n)
{
int cnt = 1;
while (cnt < n)
{
cnt *= 2;
}
}
因为每次cnt乘以2之后,就距离n更近了一分.cnt与多少个2相乘后大于n就会退出循环.
设循环内基本操作的循环次数为x,可得2^x=n,即.
因此这个循环的时间复杂度为O(logn)
当n接近无穷大时,底数的大小对结果影响不大。因此,一般情况下不管底数是多少都可以省略不写,即可以表示为log n不同书籍的表示方式不同,我们建议使用log n

四.轮转数组
https://leetcode.cn/problems/rotate-array/description/
思路一:
时间复杂度:O(n )
循环K次将数组所以元素向后移动一位(代码不通过)
void rotate(int* nums, int numsSize, int k) {
while(k--)
{
int end = nums[numsSize-1];
for(int i = numsSize - 1;i > 0 ;i--)
{
nums[i] = nums[i-1];
}
nums[0] = end;
}
}

思路二:
空间复杂度O(n):申请新数组空间
先将后k个数据放到新数组中,再将剩下的数据挪到新数组中
void rotate(int* nums, int numsSize, int k)
{
int newArr[numsSize];
for (int i = 0; i < numsSize; ++i)
{
newArr[(i + k) % numsSize] = nums[i];
}
for (int i = 0; i < numsSize; ++i)
{
nums[i] = newArr[i];
}
}

思路三:
空间复杂度O(1)
• 前n-k个逆置:4321567
• 后k个逆置:4321765
• 整体逆置:5671234
void reverse(int* nums,int left,int right)
{
while(left<right){
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
void rotate(int* nums, int numsSize, int k)
{
k = k%numsSize;
reverse(nums,0,numsSize-k-1);
reverse(nums,numsSize-k,numsSize-1);
reverse(nums,0,numsSize-1);
}


2万+

被折叠的 条评论
为什么被折叠?



