什么是数据结构?
数据结构(Data structure)是计算机存储,
组织数据的方式,指相互之间存在一种或多
种特定关系的数据元素的集合
什么是算法?
算法(Algorithm):就是定义良好的计算过程
他取一个或一组的值为输入,并产生一个或一
组的值作为输出,简单来说算法就是一系列的
计算步骤,用来将输入数据转换成输出的结果。
算法效率
算法在编写成可执行程序后,运行时需要耗费
时间资源和空间(内存)资源 。因此衡量一个
算法的好坏,一般是从时间和空间两个维度来
衡量的,即时间复杂度和空间复杂度。但是因
为当今科技的发展,计算机的内存容量也在快
速发展,所以我们现在判断一个算法的好坏一
般是看其时间复杂度的大小
时间复杂度
时间复杂度的定义:在计算机科学中,算法的
时间复杂度是一个函数,它定量描述了该算法
的运行时间,但是因为外部因素如编译机器的
不同,不同编译机器下的编译时间也存在差异
因此为了统一,我们不再计算一个算法的运行
时间,而是计算一个算法中基本语句的执行次
数,而一个算法所花费的时间与其中语句的执
行次数成正比例,所以算法中的基本操作的执
行次数,为算法的时间复杂度。
如何计算一个算法的执行时间
一个算法的执行时间大致上等于其所有语句执
行时间的总和,而语句的执行时间则为该语句
重复执行的次数和执行一次所需的时间
举例一
void Func1(int n){
for(i=1;i<=n;i++)
for(j=1;j<=n;j++){
c[i][j]=0;
for(k=1;k<=n;k++)
c[i][j]=c[i][j]+a[i][k]*b[k][j]
}
}
该算法中所有语句频度之和,是矩阵阶数n的函
数,用f(n)表示之。
所以 f(n)=2n^3+3n^2+2n+1
对于举例一这种比较简单的算法,可以直接计
算出算法中所有语句的频度,但是对于困难的
算法则通常是比较困难的。实际我们在计算时
间复杂度的时候,可以只用算法中的"**基本
语句**"的执行次数来计算算法的工作量"**
基本语句**"指的是算法中重复执行次数和算
法的执行时间成正比的语句(也称为基本操作)
,如循环语句.....同时我们也引进计算时间
复杂度的一个方法:大O渐进法。
大O渐近表示法
大O符号(Big O notation):是用于描述
函数渐进行为的数学符号。
推导大O阶法
1、用常数1取代运行时间中的所有加法常数。
且这个数为常数,那么就用1代替,表示为
O(1)
2、在修改后的运行次数函数中,只保留最高
阶项。
表示为O(n^2)
3、如果最高阶项存在且不是1,则去除与这个
项目相乘的常数。得到的结果就是大
O阶。
使用大O的渐进表示法以后,Func1(举例一)
的时间复杂度为:
O(n^3)
N = 10 F(N) = 1000
N = 100 F(N) = 1000000
N = 1000 F(N) = 1000000000
通过上面我们发现大O表示法去掉了那些对结
果影响不大的项,简洁明了的表示了
执行次数
另外有些算法的时间复杂度存在最好、平均和
最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情
况,所以数组中搜索数据时间复杂度为O(N)
举例二(时间复杂度的计算)
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);
}
Func2的基本操作次数为2n+10 ,根据大O渐
近法,这个算法的时间复杂度为O(n).
举例三(冒泡排序时间的时间复杂度)
void BubbleSort(int arr[], int len){
int i, j, temp;
for (j = 0; j < len - 1; j++){
for (i = 0; i < len - 1 - j; i++){
if(arr[i] > arr[i + 1]){
temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
}
}
冒泡排序的时间复杂度应该从思想上进行分析,
最好的情况下只排序一趟也就是n-1.最坏的情
况下整个数组每个数都得进行排序,那么n个
数就得排序(n-1)+(n-2).....+1=n*(n+1)
/2次,时间复杂度就为O(n).
举例四(二分查找算法的时间复杂度)
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;
else
return mid;
}
return -1;
}
二分查找算法的时间复杂度也应从思想上进行
分析,假设要查找x次,最好的情况第一次就
找到。那么最坏情况就应该是最后一次找到
或者没找到。根据二分算法,每次的查找以
后都得折半。所以1*2*2*2....(x个2)=数组
长度n,所以x就应该等于log2n
举例五(递归算法的时间复杂度)
long long Fac(size_t N)
{
if (0 == N)
return 1;
return Fac(N - 1) * N;
}
递归算法:递归次数*递归调用的次数
举例五的基本操作执行了n次,所以时间复杂度
为O(n);
空间复杂度
空间复杂度也是一个数学表达式,是对一个算
法在运行过程中临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,
因为这个也没太大意义,所以空间
复杂度算的是变量的个数。
空间复杂度计算规则基本跟实践复杂度类似,
也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(**存储
参数,局部变量,一些寄存器信息等**)
在编译期间已经确定好了,此空间复杂度主
要通过**函数在运行**时候显式申请的额外空
间**来确定。
举例六(冒泡排序的空间复杂度)
void vBubbleSort(int arr[], int len){
int i, j, temp;
for (j = 0; j < len - 1; j++){
for (i = 0; i < len - 1 - j; i++){
if(arr[i] > arr[i + 1]){
temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
}
}
冒泡排序使用了常数个额外空间,所以空间复
杂度为1.
举例八(递归数列的空间复杂度)
long long Fac(size_t N)
{
if (N == 0)
return 1;
return Fac(N - 1) * N;
}
Fac被调用了n次,创建了n个栈帧,每个栈帧
开辟了常数个空间,所以空间复杂度为O(n)
常见复杂度对比
|