目录
(一)数据结构前言
(1)数据结构
数据结构(Data Structure) 是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
(2)算法
一系列的计算步骤,用来将输入数据转化成输出结果。
(二)算法效率
(1)来个题
直接上代码,这是一个旋转数组题https://leetcode.cn/problems/rotate-array/description/ :
cpp
void rotate(int* nums, int numsSize, int k)
{
int i = 0;
while (k--)
{
printf("k = %d:\n",k);
int end = nums[numsSize - 1];
for (i = numsSize - 1;i > 0;i--)
{
nums[i] = nums[i-1];
}
nums[0] = end;
for (i = 0;i < numsSize;i++)
{
printf("nums[%d] = %d\n", i, nums[i]);
}
}
}
提交代码为什么无法通过?算法效率太低了,详解在第(六)点。
(2) 复杂度的概念
一个算法的好坏,一般从 时间复杂度 和 空间复杂度 来衡量。
-
时间复杂度主要衡量算法的运行快慢。
-
空间复杂度主要衡量一个算法运行所需的额外空间。
(三)时间复杂度
在计算机科学中,算法的时间复杂度是一个函数式T(N),它定量描述了算法的运行时间。
为什么不直接计算程序的运行时间?
-
程序运行时间与 编译环境 和 运行机器的配置 都有关系。
-
同一个算法程序,用一个老低配置机器和新高配置机器,运行时间不同。
-
时间只能程序写好后测试,不能写程序前。
T(N)这个函数式到底是什么?
-
T(N)这个函数式计算了程序的执行次数。
-
算法程序被编译后生成二进制指令,程序运行,就是cpu在执行这些编译好的指令。
-
假设每句指令执行时间基本一样,那么执行次数和运行时间就是成正比例。
-
这样就脱离了具体的编译运行环境。
-
执行次数就可以表示时间效率的优劣。
在实际计算中
-
一般只需要计算程序能代表增长量级的大概执行次数。
-
复杂度的表示通常使用大O的渐进表示法。
(1)大O的渐进表示法
大O符号(Big O notation)是用于描述函数渐进行为的数学符号。
推导大O阶规则
-
时间复杂度函数式T(N)中,只保留最高阶项,去掉那些低阶项。因为当N不断变大的时候,低阶项对结果影响越来越小,当N无穷大的时候,就可以忽略不计了。
-
如果最高阶项存在且不是1,则去除这个项的常数系数。因为当N不断变大,这个系数对结果的影响越来越小,当N无穷大的时候,就可以忽略不计了。
-
T(N)中如果只有常数项,用1代替所有加法常数。
(2)时间复杂度计算
①Fun1
cpp
void Fun1(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;
}
}
以上算法程序时间复杂度为:O(N^2)
②Fun2
cpp
void Fun2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
以上算法程序时间复杂度为:O(N)
③Fun3
cpp
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);
}
以上算法程序时间复杂度为:O(N)
④Fun4
cpp
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
以上算法程序时间复杂度为:O(1)
⑤Fun5
cpp
const char* strchr( const char* str, int character)
{
const char* p_begin = str;
while (*p_begin != character)
{
if (*p_begin == '\0')
{
return NULL;
}
p_begin++;
}
return p_begin;
}
以上算法程序时间复杂度为:O(N)
⑥BubbleSort
cpp
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(N^2)
⑦Fun7
cpp
void Fun7(int n)
{
int cnt = 1;
while (cnt < n)
{
cnt *= 2;
}
}
以上算法程序时间复杂度为:O(logN)
⑧Fac
cpp
long long Fac(size_t N)
{
if (0 == N)
{
return 1;
}
return Fac(N - 1) * N;
}
以上算法程序时间复杂度为:O(N)
总结:
有些算法的时间复杂度存在最好、平均和最坏情况。
-
最坏情况(上界):任意输入规模的最大运行次数。
-
平均情况:任意输入规模的最大运行次数。
-
最好情况(下界):任意输入规模的最小运行次数。
(四)空间复杂度
空间复杂度 是对一个算法在运行过程中因为算法的需要 额外开辟的空间。
-
空间复杂度计算的是变量的个数。
-
空间复杂度使用大O渐进表示法。
函数运行所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间就已经确定好了,因此空间复杂度主要是计算函数在运行时候显式申请的(程序员主动写代码申请的)额外空间。
空间复杂度计算
①BubbleSort
cpp
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)
②Fac
cpp
long long Fac(size_t N)
{
if (0 == N)
{
return 1;
}
return Fac(N - 1) * N;
}
以上算法程序空间复杂度为:O(N)
(五)常见复杂度对比
|-------|-------------|----------------|----------|-------------|-------------|-------------|
| n | log2^n | n*log2^n | n^2 | n^3 | 2^n | n! |
| 4 | 2 | 8 | 16 | 64 | 16 | 24 |
| 8 | 3 | 24 | 64 | 512 | 256 | 80320 |
| 10 | 3.32 | 33.2 | 100 | 1000 | 1024 | 3628800 |
| 16 | 4 | 64 | 256 | 4096 | 65536 | 2.1*10^13 |
| 32 | 5 | 160 | 1024 | 32768 | 4.3*10^9 | 2.6*10^35 |
| 128 | 7 | 896 | 16384 | 2097152 | 3.4*10^38 | ∞ |
| 1024 | 10 | 10240 | 1048576 | 1.07*10^9 | ∞ | ∞ |

(六)复杂度算法题
旋转数组
算法1(暴力旋转)
思路:旋转k次,一次一次旋转数组。
cpp
void rotate1(int* nums, int numsSize, int k)
{
int i = 0;
while (k--)
{
printf("k = %d:\n",k);
int end = nums[numsSize - 1];
for (i = numsSize - 1;i > 0;i--)
{
nums[i] = nums[i-1];
}
nums[0] = end;
}
}
以上算法时间复杂度为O(N^2),空间复杂度为O(1)。
算法2(额外数组法)
创建新数组。
在新数组中实现旋转。
再拷贝到旧数组。
cpp
void rotate2(int* nums, int numsSize, int k)
{
int* tmpArr = malloc(numsSize*sizeof(int));
int i = 0;
for (i = 0;i < numsSize;++i)
{
tmpArr[i] = nums[(i + k) % numsSize];
}
for (i = 0;i < numsSize;++i)
{
nums[i] = tmpArr[i];
}
}
以上算法时间复杂度为O(N),空间复杂度为O(N)。
算法3(三次翻转法)
旋转前 numsSize - k 个元素。
再从下标numsSize - k旋转k个元素。
最后旋转整个数组。
reverse函数:
cpp
void reverse(int* nums,int begin,int end)
{
int tmp = 0;
while (begin < end)
{
tmp = nums[begin];
nums[begin] = nums[end];
nums[end] = tmp;
++begin;
--end;
}
}
rotate3函数:
cpp
void rotate3(int* nums, int numsSize, int k)
{
k = k % numsSize;
test014_4_rotate3_reverse(nums,0,numsSize - k - 1);
test014_4_rotate3_reverse(nums,numsSize - k,numsSize - 1);
test014_4_rotate3_reverse(nums,0,numsSize - 1);
}
以上算法时间复杂度为O(N),空间复杂度为O(1)。