目录
一、数据结构与算法
1. 什么是数据结构?
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的 数据元素的集合。数据在内存中的本质就是 增删查改
2. 什么是算法?
算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
3.数据结构与算法的关系
数据结构为算法提供了基础。算法是用来解决特定问题的一系列操作,而数据结构则是用来存储和组织数据的方式。算法需要在特定的数据结构上进行操作,因此选择合适的数据结构对于设计高效的算法至关重要。
例如,我们要对一组数据进行排序,那么我们可以选择使用数组或链表这两种不同的数据结构来存储这些数据。如果我们选择使用数组,那么我们可以使用快速排序或归并排序这两种高效的排序算法;而如果我们选择使用链表,那么我们可以使用插入排序或归并排序这两种排序算法。不同的数据结构会影响算法的效率,因此选择合适的数据结构对于设计高效的算法至关重要。
二、算法的复杂度
1.算法效率
1.1什么是算法效率
算法效率是指算法完成任务所需的资源(如时间和空间)的多少。一个高效的算法能够在较短的时间内完成任务,同时占用较少的内存空间
1.2 算法的好坏
假如我们要在一个长度为 n 的数组中查找一个特定的元素
//顺序查找
int search(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
//二分查找
int search(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
第一种算法是顺序查找,需要依次检查每个元素,所以需要检查n个元素;
第二种算法是二分查找,首先检查中间的,然后每次检查都会将搜索范围缩小一半。
两者的区别就在于检查元素的个数不同,顺序查找需要检查的多,那么相较于二分查找的运行速度就会慢,二分查找效率更高
1.3算法的复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。如上面查找元素的代码,二分查找的 时间复杂度 明显高于 顺序查找
2.时间复杂度
2.1什么是时间复杂度
时间复杂度 是指 算法完成任务所需的时间。它用来衡量算法的执行效率,也就是算法运行所需的时间。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。在计算时,我们计算大概的时间,因为具体的时间与电脑的配置有关。
2.2大O的渐进表示法
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶
大O渐进表示法 只提供了一种粗略的估计方法,并不能精确地表示算法执行时间或空间。它只能告诉我们当输入数据规模 n 增长时,算法执行时间或空间会以什么样的方式增长
而在计算大O时,我们通常关注的是算法的最坏运行情况,所以如上面 顺序查找元素 中,搜索数据时间复杂度为O(N)
2.3实例
// 计算Func1的时间复杂度?
void Func1(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
for循环 执行了100次,通过推导大O阶方法,时间复杂度为 O(1)。
即使是 1* 10^8 次,也是一个常数,它的时间复杂度也是O(1)。
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
在这段代码中,共递归了N次,所以时间复杂度是 O(N)
// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
在这个计算斐波那契数的算法中,递归了 2^N 次,所以时间复杂度是 O(2^N)
//二分查找
int search(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
在二分查找中,每次都是减少一般查找范围,最坏查找 O(logN) 次,所以时间复杂度为 O(logN)
int main()
{
int n, m, x, y;
scanf("%d %d", &n, &m);
int arr[n][m];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
scanf("%d", &arr[i][j]);
}
}
scanf("%d %d", &x, &y);
printf("%d", arr[x - 1][y - 1]);
return 0;
}
在这段代码中,查找指定的元素,最坏的情况则是查找了N*M次,所以时间复杂度是O(N*M)
时间复杂度为 O(N^2) 同样是这个形式,两个循环都是从0到N-1
3.空间复杂度
3.1什么是空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,而是计算变量的个数。 空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显示申请的额外空间来确定
3.2实例
//计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
if(N == 0)
return 1;
return Fac(N-1)*N;
}
函数递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)
// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
在计算斐波那契数时,应注意它是重复调用,并不是开辟新的空间,所以空间复杂度为 O(N)
def my_function(n):
x = n * 2
y = x + 1
return y
这段代码中,我们创建了两个变量,所以空间复杂度为 O(1)