赞
踩
目录
一、算法的复杂度
二、时间复杂度
三、时间复杂度习题
例1、2、3、4:普通循环的时间复杂度
例5:二分查找法的时间复杂度
例6:冒泡排序的时间复杂度
例7:普通递归的时间复杂度
例8:求斐波那契数的时间复杂度
附加题:逆置数组
C程序在实现某种特定的功能的时候,会有多种代码去实现,为了提高程序的效率,需要考虑代码执行的时间和代码存储需要的空间。而在当今存储设备的进步,人们不是很在意存储代码所花空间的大小,会更在意代码运行的时间。
我们熟知的斐波那契数列,用递归的方式去实现的时候,代码十分简洁,但是这简洁的代码能说明它的效率高吗?
- long long Fib(int N)
- {
-
- if(N < 3)
- return 1;
- return Fib(N-1) + Fib(N-2);
- }
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般 是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算 机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计 算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。
最坏情况
|
任意输入规模的最大运行次数
(
上界
)
|
平均情况
|
任意输入规模的期望运行次数
|
最好情况
|
任意输入规模的最小运行次数
(
下界
)
|
- // 请计算一下Func1中++count语句总共执行了多少次?
- void Func1(int N)
- {
- int count = 0;
- for (int i = 0; i < N; ++i) //N
- {
- for (int j = 0; j < N; ++j) //N
- {
- ++count;
- }
- }
- //共N*N次
- for (int k = 0; k < 2 * N; ++k) //2*N
- {
- ++count;
- }
- int M = 10;
- while (M--) //10
- {
- ++count;
- }
-
- printf("%d\n", count);
- }

经过计算count一共计算F(N)= N^2+2*N+10次
N=10 F(N)=130
N=100 F(N)=10210
N=1000 F(N)=1002010
- // 计算Func2的时间复杂度?
- void Func2(int N)
- {
-
- int count = 0;
- for (int k = 0; k < 2 * N; ++k)
- {
- ++count;
- }
-
- int M = 10;
- while (M--)
- {
- ++count;
- }
-
- printf("%d\n", count);
- }

经过计算一共计算F(N)= 2*N+10次,则时间复杂度为O(N)
- // 计算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);
- }

经过计算一共计算F(N)= M+N次,则时间复杂度为O(M+N),若M>>N,则时间复杂度为O(M),反之则为O(N)。
例4
- void Func4(int N)
- {
- int count = 0;
- for (int k = 0; k < 100; ++k)
- {
- ++count;
- }
-
- printf("%d\n", count);
- }
程序总共计算了100次,次数为常数,所以时间复杂度为O(1)
例5
- int BinarySearch(int* a, int n, int x)
- {
- assert(a);
- int begin = 0;
- int end = n - 1;
- // [begin, end]:begin和end是左闭右闭区间,因此有=号
- while (begin <= end)
- {
- int mid = begin + ((end - begin) >> 1); //求中间值
- if (a[mid] < x)
- begin = mid + 1;
- else if (a[mid] > x)
- end = mid - 1;
- else
- return mid;
- }
- return -1;
- }

二分查找法,最好的情况是第一次就找到了,或者几次就找到了,这样时间复杂度为O(1)
最坏的情况是最终找不到需要找到的值。
例6
- 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;
- }
- }

执行次数为:(N-1)+(N-2)+(N-3)+.....+2+1=(N^N-N)/2,故时间复杂度为O(N^N)若数据本来就是有序的,则时间复杂度为O(N)。
例7
- // 计算阶乘递归Fac的时间复杂度?
- long long Fac(size_t N)
- {
- if(0 == N)
- return 1;
- return Fac(N-1)*N;
-
- }
该递归调用时,N的值变化情况有:N、N-1、N-2、N-3......3、2、1,总共有N次变化情况,所以该递归的时间复杂度为O(N)。
例8
- // 计算斐波那契递归Fib的时间复杂度?
- long long Fib(size_t N)
- {
- if(N < 3)
- return 1;
-
- return Fib(N-1) + Fib(N-2);
- }
递归数为:2^0+2^1+2^2+......+2^(N-2)+2^(N-1)=-2^0+2^N=2^N+1
则斐波那契数的递归的时间复杂度为O(2^N)
附加题:轮转数组——只考虑时间复杂度
方法一:
- int main()
- {
- int arr[] = { 1,2,3,4,5,6,7 };
- int sz = sizeof(arr) / sizeof(arr[0]);
- int k;
- scanf("%d", &k);
- while (k--)
- {
- int n = arr[sz-1];
- for (int i = sz-1; i > 0; --i)
- {
- arr[i] = arr[i - 1];
- }
- arr[0] = n;
- }
-
- for (int j = 0; j < sz; j++)
- printf("%d ", arr[j]);
- return 0;
-
- }

打印只是为了观察交换的结果,我们忽略打印的步骤,可以计算出。
- k=1,时间复杂度为O(N)
-
- 未知k等于多少,时间复杂度为O(kN)
-
- 没说明k的值,时间复杂度为O(N^N)
方法二:牺牲空间,提高算法效率
- void rotate(int* nums, int numsSize, int k)
- {
- int i = 0;
- int j = 0;
- if (k > numsSize)
- k = k % numsSize;
- if (k == numsSize)
- {
- for (i = 0; i < numsSize; ++i)
- {
- printf("%d", nums[i]);
- }
- }
- else
- {
- int left = numsSize - k;
- int right = k;
- int* arr1 = (int*)malloc(sizeof(int) * left);
- int* arr2 = (int*)malloc(sizeof(int) * right);
- if(arr1==NULL || arr2==NULL)
- {
- printf("malloc fail");
- return;
- }
-
- for (i = 0; i < numsSize; ++i)
- {
- if (i < left)
- arr1[i] = nums[i];
- else
- {
- arr2[j] = nums[i];
- j++;
- }
-
- }
- for (i = 0, j = 0; i < numsSize; ++i)
- {
- if (i < right)
- nums[i] = arr2[i];
- else
- {
- nums[i] = arr1[j];
- j++;
- }
-
- }
- free(arr1);
- free(arr2);
- }
- }

该方法为:将需要倒叙的数组元素分别放在新创建两个数组中,最终再将两个新创建的数组按照需要的顺序放入原数组中,放入新数组执行了N次,再放入元素组执行力N次,一共执行了2N次,所以时间复杂度为O(2N)=O(N),该方法需要浪费一定的空间。
- 例如:k=3
-
- 原数组:int nums[ ]={1,2,3,4,5,6,7}
-
- 新数组:int arr1[ ]={1,2,3,4}
-
- 新数组:int arr2[ ]={5,6,7}
-
- 调整后的原数组:int nums[ ]={5,6,7,1,2,3,4}
方法三:最优解
- void reverse(int* p, int left, int right)
- {
- while (right > left)
- {
- int tmp = p[left];
- p[left] = p[right];
- p[right] = tmp;
- right--;
- left++;
- }
- }
- void rotate(int* p ,int sz, int k)
- {
- if (k > sz)
- k %= sz;
-
- reverse(p, 0, sz - k - 1);
- reverse(p, sz - k , sz - 1);
- reverse(p, 0, sz - 1);
- }

该方法为先将左边部分逆序,再将右边部分逆序,然后再整体逆序。
- 例如:k=3
-
- 原数组:int nums[ ]={1,2,3,4,5,6,7}
-
- 前n-k个逆置:int nums[ ]={4,3,2,1,5,6,7}
-
- 后k个逆置:int nums[ ]={4,3,2,1,7,6,5}
-
- 整体逆置:int nums[ ]={5,6,7,1,2,3,4}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。