1.算法效率
如何衡量⼀个算法的好坏呢?
案例:旋转数组 https://leetcode.cn/problems/rotate-array/description/
思路:循环K次将数组所有元素向后移动⼀位
void rotate(int* nums, int numsize, int k)
{
while (k--)
{
int end = nums[numsize - 1];//把最后一个先记住
for (int i = numsize - 1; i > 0; i--)
{
nums[i] = nums[i - 1];//从倒数第二个开始给倒数第一个
}
nums[0] = end;//第一个赋值为最后一个
}
}
但是未能通过全部案例,算法没问题,但是超出了时间限制。
2.复杂度
如何衡量算法的好坏呢,利用复杂度,复杂度有时间复杂度以及空间复杂度
复杂度是一个粗估的概念,并不是一个准确的值。
时间复杂度主要衡量⼀个算法的运⾏快慢,⽽空间复杂度主要衡量⼀个算法运⾏所需要的额外空间。在计算机发展的早期,计算机的存储容量很⼩。所以对空间复杂度很是在乎。但是经过计算机⾏业的迅速发展,计算机的存储容量已经达到了很⾼的程度。所以我们如今已经不需要再特别关注⼀个算法的空间复杂度
2.1时间复杂度
如何计算时间复杂度呢
// 请计算⼀下Func1中++count语句总共执⾏了多少
次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
++count;
}
}
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
}
所以时间复杂度为N^2+2N+10.
2.2⼤O的渐进表⽰法
由此可得,上面的Func1代码时间复杂度为O(N^2)
2.2.1
再来一题:
// 计算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);
}
2.2.2
T(N)=2N+10
由大O渐进表示法可知N前的常数项不要,低次项也不要,所以,为
T(N)=N
2.2.3
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);
}
M和N都是未知数
T(N)=O(M+N)
若M>>N,O(M)
若M<<N,O(N)
若M≈N,O(M+N)
2.2.4
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++k)
{
++count;
}
printf("%d\n", count);
}
由大O规则中的3可知O(100)==O(1)
O(1)中的1代表的是常数。
2.2.5
// 计算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;
}
这是一个查找字符的代码
若在一开始,O(1)
若在中间,O(N/2)
若在后面或者超过N,O(N)
2.2.6冒泡排序的时间复杂度
// 计算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;
}
}
外层循环一次,内层循环根据外层改变
根据等差公式得,T(N)=N(1+N)/2
根据大O渐进表示法,去掉低次项,去掉未知数前的系数,得T(N)=O(N^2)
2.2.7
void func5(int n)
{
int cnt = 1;
while (cnt < n)
{
cnt *= 2;
}
}
假设执行的次数为x,2^x=n,假设n=10
即2^x=10,若要跳出循环,x=4.
所以时间复杂度为O(log n)
键盘敲不出2,所以写logn也可
2.2.8
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
if (0 == N)
return 1;
return Fac(N - 1) * N;
}
一次函数栈帧执行的时间复杂度为O(1)
执行了N次,总的时间复杂度为O(N) .
2.3空间复杂度
跟时间复杂度类型,也采用大O渐进表示法
只需要计算函数运行时开辟的额外空间,在编译期间确定好的不用管
2.3.1
// 计算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;
}
}
空间复杂度O(1)
1表示的还是常数。
2.3.2
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
if (N == 0)
return 1;
return Fac(N - 1) * N;
}
递归了N次,创建了N个函数栈帧,所以空间复杂度为O(N)
2.3.3
这个空间复杂度也是O(N)
3.常见的复杂度对比
4.复杂度算法题
4.1翻转数组
4.1.1
开始的时候我们写了一个代码
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^2),时间太久了,我们可以学习下列的代码
4.1.2
void rotate(int* nums, int numsize, int k)
{
int numarr[numsize];//创建一个新数组
for (int i = 0; i < numsize; i++)
{
numarr[(i + k) % numsize] = nums[i];//
}
for (int i = 0; i < numsize; i++)
{
nums[i] = numarr[i];//赋值回来
}}
所以这里的时间复杂度为O(N)
空间复杂度为O(N)
这里是利用了空间换时间
4.1.3
思路
void reverse(int* nums, int left, int right)
{
while (left < right)
{
//left和right指向的下标开始交换
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
void rotate(int* nums, int numsize, int k)//数组 元素个数 移动几位
{
//前n-k个数据逆置
reverse(nums, 0, numsize - k - 1);//传的是交换数组的下标
//后k个数据逆置
reverse(nums, nums - k, numsize - 1);
//整体逆置
reverse(nums, 0, numsize - 1);
}
时间复杂度为O(N),空间复杂度为O(1)
这里发生了一个错误,假设元素个数numsize为1,移动位数k为3.则reverse传值会传负数,更改代码为
void reverse(int* nums, int left, int right)
{
while (left < right)
{
//left和right指向的下标开始交换
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
void rotate(int* nums, int numsize, int k)//数组 元素个数 移动几位
{
k = k % numsize;
//前n-k个数据逆置
reverse(nums, 0, numsize - k - 1);//传的是交换数组的下标
//后k个数据逆置
reverse(nums, nums - k, numsize - 1);
//整体逆置
reverse(nums, 0, numsize - 1);
}
更改处