文章目录
- 1.什么是数据结构
- 2.什么是算法
- 3.如何学好数据结构呢
- 3.1写代码
- 3.2 多去动手画图
- 4.算法效率
- 4.1如何评判一个算法的好与坏呢
- 4.2算法的复杂度
- 5.时间复杂度
- 5.1 概念
- 5.2大O渐进法
- 6常见的时间复杂度
- 6.1常数阶
- 6.2线性阶
- 6.3 对数阶
- 6.4平方阶
- 6.5函数调用
- 6.5.1普通调用
- 6.5.2递归调用
- 6.6不确定循环次数
- 6.7时间复杂度的比较
- 7.空间复杂度
先给大家一段话:
If you give someone a program , you will frustrate them for a day ; if you teach them how to program , you will frustrate them for a lifetime
如果你交给某人一个程序,你将折磨他一整天;如果你教某人如何编程,你将折磨他一辈子!!!
今天我们来看看数据结构!!!!
1.什么是数据结构
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的
数据元素的集合。
从这幅图我们也看出数据结构的重要性了吧。
2.什么是算法
算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为
输出。简单来说算法就是一系列的计算步骤,将数据转换成输出结果
3.如何学好数据结构呢
3.1写代码
在学习数据结构时,你觉得听起来挺简单,但是如果你一动手就不会了,所以我们要多用手去敲代码,把同一个结构敲上十几次
敲到这种程度就好😄😄
3.2 多去动手画图
在遇到比较难理解的结构与算法,我们要去拿纸去画一画它是如何实现的。
眼过千遍不如手过一遍
4.算法效率
4.1如何评判一个算法的好与坏呢
求前100个整数相加的和
小王:我写了一个代码,很好。
#include<stdio.h>
int main()
{
int i = 0;
int sum = 0;
int n = 100;
for (i = 0; i < n; i++)
{
sum = sum + i;
}
printf("%d\n", sum);
return 0;
}
小田:我也写了一个,比你的好
#include<stdio.h>
int main()
{
int n = 100;
int sum = n * (n - 1) / 2;
printf("%d\n", sum);
return 0;
}
那么他们两个到底谁的方法好呢?如何评判的呢?
当然是有评判标准的!!!!
4.2算法的复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般
是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算
机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计
算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
5.时间复杂度
5.1 概念
时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数此函数并非C语音中的函数,而是数学中的函数,它定量描述了该算法的运行时间。
一 个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。
但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。
一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法 的时间复杂度。
切记:时间复杂度是基本语句执行的次数多少,而不是运行的时间。
如果用运行时间来判断,由于机器性能的不同,相同代码跑出来的时间是不同的。所以不用运行时间来比较。
先看个例子吧
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;
}
printf("%d\n", count);
}
求一下++count语句一共执行了多少次。
答: N*N + 2*N+ 10
这就是他的基本语句执行的次数,也就是它的时间复杂度
Func1 执行的基本操作次数 :
N*N + 2*N+ 10
N = 10 F(N) = 130
N = 100 F(N) = 10210
N = 1000 F(N) = 1002010
N*N
N = 10 F(N) = 100
N = 100 F(N) =10000
N = 1000 F(N) = 1000000
我们可以看出,随着N的增大,只有N*N对这个结果影响比较大,而2*N+10随着N的增大,影响基本没有。
所以,实际上我们计算时间复杂度是,并不需要像我们上面那样精确的计算,而只需要计算大概的执行次数。
什么是大概的执行次数呢?
有一个大O渐进法为我们规定了。
5.2大O渐进法
推导大O阶:
1、用常数1取代时间中的所有加法常数
2、在修改后的运行次数函数中,只保留最高阶项
3、如果最高级项存在且其系数不是1,则去除与这个项相乘的系数
用这个三个规则得到的就是大O阶
6常见的时间复杂度
6.1常数阶
#include<stdio.h>
int main()
{
int sum = 0; //执行一次
int n = 100; //执行一次
sum = (1 + n) * n / 2; //执行一次
printf("%d", sum); //执行一次
return 0; //执行一次
}
上面代码一共执行5次
时间复杂度是O(5)吗?
不是,根据大O渐进法 用常数1取代时间中的所有加法常数
只要是常数相加,就看作1,你哪怕是相加为1000000,也看作1。
所以上面代码时间复杂度:O(1)
6.2线性阶
#include<stdio.h>
int main()
{
int i = 0; //执行一次
int n = 10; //执行一次
for (i = 0; i < n; i++) //执行N次
{
//时间复杂度为O(1)的程序步骤
}
return 0; //执行一次
}
这个时间复杂度为:O(N)
如果细算的话 F(N) = N+3
根据大O渐进法,第二条在修改后的运行次数函数中,只保留最高阶项
最高阶为: N
6.3 对数阶
₂₂₂#include<stdio.h>
int main()
{
int count = 1;
int n = 128;
while (count < n)
{
count = count * 2;
}
return 0;
}
由于每次count乘以2,就离N越来越近了
2^x = N;
x = log₂N
但是习惯上将logN 与 log₂N等价
所以这个程序的时间复杂度:O(logN)
二分法的时间复杂度就是:O(logN)
6.4平方阶
#include<stdio.h>
int main()
{
int n = 100;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
//时间复杂度为O(1)的程序
}
}
return 0;
}
这是一个循环嵌套
外层每循环1次,内层循环N次
外层循环N次,内层循环N次
一共执行多少条语句呢?
F(N) = N + N +…+N N个N相加
则时间复杂度:O(N*N)
在看一个程序
#include<stdio.h>
int main()
{
int n = 100;
for (int i = 0; i < n; i++)
{
for (int j = i; j < n; j++) //注意:j = i
{
//时间复杂度为O(1)的程序
}
}
return 0;
}
当i = 0时,内层循环执行了n次
当i= 1时,内层循环执行了n-1次
.
.
当i= n-1时,内层循环执行了1次
一共执行:F(N) = N+(N-1) +(N-2) +…+1 = N*N/2+N/2
取最高阶 N*N/2
在根据大O渐进法,第三条:如果最高级项存在且其系数不是1,则去除与这个项相乘的系数
将1/2变成1
则时间复杂度为:O(N*N)
6.5函数调用
6.5.1普通调用
#include<stdio.h>
void fundtion(int n)
{
printf("%d ", n);
}
int main()
{
int i = 0;
int j = 0;
int n = 10;
for (i = 0; i < n; i++)
{
function(i);
}
return 0;
}
for循环内,调用了函数,这个函数内的时间复杂度是1。
则这个程序的时间复杂度就是:O(N) 由for循环决定
#include<stdio.h>
void fundtion(int n)
{
int j = 0;
for (j = 0; j < n; j++)
{
//时间复杂度为O(1)的程序步骤
}
}
int main()
{
int i = 0;
int j = 0;
int n = 10;
for (i = 0; i < n; i++)
{
function(i);
}
return 0;
}
时间复杂度为:O(N*N)
==我们可以将函数中的实现,带到for循环中,==这个和上面刚刚说的是一样的,你们可以算算。
6.5.2递归调用
long long Fac(size_t N)
{
if (0 == N)
return 1;
return Fac(N - 1) * N;
}
这是一个递归,它的时间复杂度又是多少呢?
在这个函数中,基本语句是两条。它一共递归了N次
则总共执行:F(N) = 2*N
则时间复杂度是:O(N)
6.6不确定循环次数
二分查找
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n - 1;
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;
}
这种我们不确定它需要循环错少次才可以找到,那么时间复杂度是多少呢?
假设有N个数字,我们需要在这N个数字中找一个数字
如果循环一次就找到了,那时间复杂度:O(1)
如果在最后一次找到了,那么时间复杂度:O(logN)
每次查找,范围缩小一半,如果最后一次才找到
1*2*2*2…*2 == N
2^x = N (x为最大循环的次数)
x = logN
最坏的结果就是,循环了logN次,最后一次找到
那么这种情况下,我们如何确定这个程序的时间复杂度呢?
举个例子:
如果有一天,你要和你女盆友去吃饭,但是,你在当天有点事情,这个事情最早是下午四点就完了,最晚是下午五点完成,你该给你女盆友说几点去吃饭呢?
你说四点,万一你没有到,你女盆友等了你一个小时,你终于来了,她也不想听你解释了
你女盆友说:你自己去吃吧,咱两分手吧
这不就很难受,就因为一句话
你如果你说五点,结果你早早就结束了,你等了她一个小时
你女盆友说:哇,你真好,真有心
最终你们就走在了一起
就算最坏的结果你五点才到,那也没什么说的本来就是五点,你们还是可以开开心心吃饭。
一样的,我们一般计算不确定循环次数的时间复杂度,按最坏的打算算。
所以二分查找的时间复杂度:O(logN)最坏的打算
6.7时间复杂度的比较
从下向上时间复杂度以此增大。
7.空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。
空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法**。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
注:
空间复杂度,用的也是大O渐进法
空间复杂度是看变量的个数,只要是在程序运行起来需要的额外空间
第一个例子
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;
}
}
看看这个函数的空间复杂度,我们一般将形式参数,不算在这个函数的空间复杂度中。
size_t end 是一个变量 size_t = unsigned int
int exchange 是一个变量
size_t i 是一个变量
所以说这个函数一共有三个变量,根据大O渐进法
空间复杂度:O(1)
第二个例子
long long* Fibonacci(size_t n)
{
if (n == 0)
return NULL;
long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
}
return fibArray;
}
long long* fibArray 开辟了N+1个空间,相当于N+1个变量
int i 是一个变量
根据大O渐进法,空间复杂度:O(N)
第三个例子
long long Fac(size_t N)
{
if (N == 0)
return 1;
return Fac(N - 1) * N;
}
这个递归函数的空间复杂度又是多少呢?
有人说:这里面没有变量,是不是O(0)
其实不是的
函数调用,会在栈上开辟栈帧空间(想成函数调用会开辟空间就好)
把开辟的这个空间就当成一个变量,即一个函数的空间O(1)
那么一共调用了N次,一共开辟了,N个栈帧空间
NULL;
long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
}
return fibArray;
}
> long long* fibArray 开辟了N+1个空间,相当于N+1个变量
>
> int i 是一个变量
根据大O渐进法,空间复杂度:O(N)
==第三个例子==
```c
long long Fac(size_t N)
{
if (N == 0)
return 1;
return Fac(N - 1) * N;
}
这个递归函数的空间复杂度又是多少呢?
有人说:这里面没有变量,是不是O(0)
其实不是的
函数调用,会在栈上开辟栈帧空间(想成函数调用会开辟空间就好)
把开辟的这个空间就当成一个变量,即一个函数的空间O(1)
那么一共调用了N次,一共开辟了,N个栈帧空间
则空间复杂度:O(N)