目录
1 最好、最坏情况时间复杂度
2 平均时间复杂度
3 均摊时间复杂度
正文
1 最好、最坏时间复杂度
// n 为 数组arr的长度
int fun1(int arr[], int n, int x)
{
int i = 0;
int pos = -1;
for( ; i<n; i++)
{
if(arr[i] == x)
{
pos = i;
}
}
return pos;
}
上面这段代码的意思是:在无序的数组arr[]中,查找变量x出现的位置,如果没有找到就返回-1。
这段代码的复杂度为O(n)
,n为数组长度。
这段代码的问题:如果我们查找的数据就在第一个位置,就不需要遍历后面的了。
// 对上边代码的优化
int fun2(int arr[], int n, int x)
{
int i = 0;
int pos = -1;
for( ; i<n; i++)
{
if(arr[i] == x)
{
pos = i;
break; // 这里
}
}
return pos;
}
这个时间复杂度怎么分析?
最好情况时间复杂度:O(1)
最坏情况时间复杂度:O(n)
2 平均时间复杂度
对于fun2()
来说:要查找的变量x在数组中的位置有n+1种情况,在数组0~n-1位置中和不在数组中。
把每种情况下,查找所需要遍历的元素个数累加起来,然后再除以n+1,就可以得到需要遍历的元素个数平均值。
(1+2+3+...+n+n)/(n+1) = (n(n+3))/2(n+1)
但是,要查找的变量x,要么在数组里,要么不在数组里。这两种情况对应的概率统计起来很麻烦,假设这两种情况的概率都为1/2。要查找的数据在0~n-1这n个位置的概率也是一样的,为1/n。所以根据概率乘法法则,要查找的数据出现在0~n-1中任意位置的概率为1/2n。
把每种情况发生的概率也考虑上:
1*1/2n + 2*1/2n + 3*1/2n + ... + n*1/2n + n*1/2 =
(3n+1)/4
这个值就是概率论中的加权平均值,也是期望值。
所以平均时间复杂度的全称为加权平均时间复杂度或期望时间复杂度。
所以fun2()的复杂度为O(n)
。
3 均摊时间复杂度
int arr[SIZE] = {0};
int n = sizeof(arr)/sizeof(arr[0]);
int count = 0;
int fun3(int val)
{
if(count == n)
{
int sum = 0;
for(int i=0; i<n; i++)
{
sum = sum + arr[i];
}
arr[0] = sum;
count = 1;
}
arr[count] = val;
++count;
}
这段代码的意思是:往数组中插入数据,当数组满后count == n
,用for循环遍历数组求和,并清空数组。求和之后的sum值当道数组的第一个位置,然后再进行插入,如果数组一开始就有空闲空间,直接插入。
最好情况时间复杂度:O(1)
最坏情况时间复杂度:O(n)
平均时间复杂度:假设数组的长度是n,根据数据插入位置的不同,我们可以分为n种情况,每种情况的时间复杂度为O(1)。除此外,还有一种情况,就是当数组没有空闲空间时,这时的时间复杂度为O(n)。而且,这n+1种情况发生的概率一样,都是1/(n+1)。所以有:
1*1/(n+1) + 1*1/(n+1) + ... + 1*1/(n+1) + n*1/(n+1) =
O(1)
。
对比fun2()
和fun3()
:
- fun2函数在极端情况下,复杂度才为O(1)。但是fun3在大部分情况下,时间复杂度都为O(1),只有个别情况下,复杂度才比较高。
- 对于fun3来说,O(1)时间复杂度的插入和O(n)时间复杂度的插入的出现是有规律的,而且有一定的前后时序关系,一般是O(n)插入后,紧跟着n-1个O(1)的插入操作,循环往复。
针对这种场景,有一种更加简单的方法:摊还分析法,通过摊还分析得到的时间复杂度成为均摊时间复杂度。
对于每一次的O(n)操作,将时间均摊给n-1个O(1)的操作,这样均摊下来,总的时间复杂度为O(1)。