数据结构学习之算法复杂度
C语言学习到一定阶段之后,接下来我们就进入到了数据结构的部分内容。
目录
数据结构
数据结构的前言
算法
数据结构和算法的重要性
如何学好数据结构和算法?
数据结构书籍推荐
算法效率
算法复杂度
时间复杂度
大O的渐进表示法
空间复杂度
数据结构
数据结构的前言
数据结构是计算机存储和组织数据的方式,指相互之间存在一种或多种特定关系数据元素的集合。没有任何一种单一的数据结构对所有的用途都有用。所以我们要学习所有的数据结构:线性表、树、图、哈希表
算法
算法:就是定义良好的计算过程,取一个或者一组为输入,并产生一个或者一组作为输出。简单来说就是一系列的计算步骤,用来将输入数据转化为输出结果。
数据结构和算法是不分家的
数据结构和算法的重要性
校招和招聘必考
如何学好数据结构和算法?
1死磕代码!!
2.画图思考
数据结构书籍推荐
《数据结构(C语言版)》
《数据结构(C++版)》
《大话数据结构》
《算法导论》:内容严谨、内容比较晦涩。
算法效率
数据结构和算法是不分家的,针对同一个预期效果有无数个算法去实现,那么我们该如何评估算法的好坏呢?这就涉及到一个算法复杂度的概念了。
这是一道力扣平台上的例题:
针对这道题我们的算法逻辑是这样的。
轮换一次将最后以为传递给临时变量tmp其余元素向后一位,tmp的数值赋给数组的第一位(下标0)
有
void SWAP(int* arr, int num, int k)
{while(k--){int tmp = arr[num - 1];for (int i = num - 2;i >= 0;i--){arr[i + 1] = arr[i];//arr[1]=arr[0]}arr[0] = tmp;}
}int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};SWAP(arr,10,3);for (int i = 0;i < 10;i++){printf("%d ", arr[i]);}return 0;
}
逻辑上没有问题,但是如果你提交到平台上,就会提示你超出时间限制。
这就是因为算法设计不好。
而这就涉及到算法复杂度的东西了。
算法复杂度
一个算法的好坏要从时间和空间两大角度去衡量,即时间复杂度和空间复杂度。时间复杂度主要衡量一个算法运行快慢,而空间复杂度则是衡量一个算法运行所需要的二外的空间。
时间复杂度
定义:在计算机科学中,算法的时间复杂度是一个函数式T(N),它定量地描述了该算法的运行时间。时间复杂度是衡量程序的时间效率。为什么不去计算程序的运行时间呢?
1.程序运行时间和编译器环境和运行机器的配置都有关系。
2.时间只能在程序写好后测试,不能写程序前通过理论思想计算评估
T(N)的数值越小,算法效率越高。
实际上我们计算时间复杂度的时候计算的也不是程序精准的执行次数,计算起来很麻烦而且意义不大。因为我们计算时间复杂度只是想比较算法程序的增长量及,也就是当N不断变大的时候T(N) 的差别
void func(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;for (int l = 0;l < M;l++){++count;}
}
T(N)=N^2+2*N+10
我们可以观察到,当N不断变大的时候常数项和低阶项影响很小。所以只需要计算程序能代表增长量级的大概执行次数,复杂度的表示通常用大O的渐进表示法。
大O的渐进表示法
·大O符号:描述函数渐进性为的数学符号
推导大) 阶的规则
1.T(N)只保留最高项,去掉低阶项。当N不断变大的时候,低阶项的影响就可以忽略不记了。
2.如果最高项系数存在且不为1,则取出这个项目的常熟系数,因为当N不断变大的时候,这个系数对结果的影响越来越小,N无穷大的时候就忽略不计。
3.T(N)没有N相关项目只有常数项,则用常数1取代所有加法常数。
所以以上func的复杂度O为O(N^2)
因此我们会发现有些算法时间复杂度存在最好、平均、最坏情况
最好:任意输入规模的最大运行次数(上界)
平均:任意输入规模的期望运行次数
最坏:任意输入规模的最小运行次数(下界)
而大O渐进表示法关注的是算法的上界,也就是最坏的情况。 、
示例:计算如下冒泡排序的时间复杂度
void bubbledsort(int* a, int n)
{assert(a);for (size_t i = 0;i < n - 1;i++){int flag = 0;for (size_t j = 0;j < n - i - 1;j++){if (a[j] > a[j + 1]){SWAP(&a[j], &a[j + 1]);flag = 1;}}if (flag == 0){break;}}
}
当外层循环走到第一次的时候,内存循环要走n-1次数;当外层循环走到第二次的时候,内层循环走到n-2次;第三次循环的时候,内层循环要走n-3次。以此类推,第n次循环的时候 ,内层循环走1次。所以T(N)为n^2/2+2*n。
因此时间复杂度:O(N^2)
一个特殊情况
void func(int n)
{int cut = 1;while (cut < n){cut *= 2;}
}
时间复杂度为O(log n)
很显然,log n这个式子在数学里是绝对错误的 ,但是计算机中log n底数敲不出来,而且在计算机中随着程序的运行,底数的大小对于结果影响不大,统一记作log n。
递归算法的时间复杂度:单次递归的时间复杂度*递归次数
int func(int n)
{if (n == 0){return 1;}else{return n* func(n-1) ;}}
O=1
空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中因为算法的需要额外开辟的空间。
空间复杂度计算的主要是变量的个数。
空间复杂度的计算也是大O的渐进表示法。
注意:函数运行时候所需要的栈空间(储存参数,局部变量,一些寄存器信息)在编译期间已经确定好了因此空间复杂度主要通过函数运行时显式申请的额外空间确定。
void bubbledsort(int* a, int n)
{assert(a);for (size_t i = 0;i < n - 1;i++){int flag = 0;for (size_t j = 0;j < n - i - 1;j++){if (a[j] > a[j + 1]){SWAP(&a[j], &a[j + 1]);flag = 1;}}if (flag == 0){break;}}
}
因此针对之前轮换数组那道题,我们可以做出如下的更改
void SWAP(int* arr, int num, int k)
{while(k--){int tmp = arr[num - 1];for (int i = num - 2;i >= 0;i--){arr[i + 1] = arr[i];//arr[1]=arr[0]}arr[0] = tmp;}
}int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};SWAP(arr,10,3);for (int i = 0;i < 10;i++){printf("%d ", arr[i]);}return 0;
}
经计算可知,时间复杂度O(N^2),空间复杂度O(1)
循环嵌套的复杂度过高,因此我们有新思路:创建一个同样大小的新数组,将后K个直接放到新数组之前,其余的正常顺序摆放。
原理图:
void SWAP(int *arr, int nums, int k)
{int tem[nums];//向右轮转n次for (int i = 0;i < nums;i++){tem[(i+k)%nums]=arr[i];}//将tem中的元素复制回arrfor (int i = 0;i < nums;i++){arr[i]=tem[i];}}
(这段代码在VS上无法运行,因为VS上不支持变长数组,需要动态内存开辟)
思路三:三次逆置
时间复杂度O(n)
空间复杂度O(1)
原理图:
//逆置
void resver(int *arr, int left, int right)
{while (left < right){int tem = arr[left];arr[left] = arr[right];arr[right] = tem;left++;right--;}
}
void SWAP(int* arr, int nums, int k)
{//nums为数组总长度,k为逆置的长度// 可能存在k>=nums的情况,此时不需要逆置k = k % nums;//前n-k个元素逆置resver(arr, 0, nums-k- 1);//后k个元素逆置resver(arr ,nums - k, k - 1);//整体逆置resver(arr, 0, k - 1);
}
这里需要加上
k=k%nums;
的原因是k在某些情况下可能比nums还大导致运行出错。
本篇博客就到这里了,感谢读者大大的阅读,求一个赞,谢谢