数据结构和算法是计算机科学中非常重要的概念,它们是编写高效、优化代码的核心
什么是数据结构
常见的数据结构包括数组、链表、栈、队列、树和图等
什么是算法
很多人初学编程,都会产生一个疑问,算法是什么,为什么要学算法,我学了有什么用,只能应付面试吗?
其实不然,算法是一种清晰、精确、有效的解决问题的方式,它的作用就是指导计算机如何正确高效的解决问题.
比如你从A点到B点,直接走直线是最快的,但是A和B之间并不能直连, 你得走A-C-D-E-B,或者A-H-G-F
但是有一天,C-D这一段堵车了,你可能就得选择C-G-F-B,那么如果G-F也堵车了.
这时候你就需要算法帮你找出最佳的行车路线
常见的数据算法有下面这些.
- 排序算法:冒泡排序、选择排序、插入排序、快速排序、归并排序等。
- 查找算法:线性查找、二分查找等
- 字符串匹配算法:暴力匹配、KMP算法、BM算法等。
- 动态规划算法:最短路径、最长上升子序列、编辑距离等。
- 贪心算法:背包问题、活动选择问题、区间覆盖问题等。
- 图论算法:最短路径、最小生成树、拓扑排序等。
这些算法是程序员必须要熟练掌握的基础
数据结构与算法中还有一个很重要的概念,复杂度
什么是复杂度
复杂度是用来评估算法效率的一个指标,复杂度通常用两个指标 -时间复杂度和空间复杂度来衡量,时间复杂度一般用O来表示,常见的复杂度有下面几种
- O(1) 表示运行时间与输入规模n无关,是常量时间
比如:
a = 1
b =2
c = a+b
print(c)
只要你的代码和数据规模没有关系,那么就算有10万行代码,他的复杂度就是O(1)
- O(log n): 表示随着n的增加,运行时间与logn成正比
- O(n log n): 表示运行时间与nlogn成正比
对数级复杂度很好理解
a = 1
for i in range(1,n):
a += a
if i <=n: break
print(a)
当你打印a的时候,a的值会从1,2,4,8,16,32以指数级的方式增长
那么第x次的时候,a =2^(x-1),因为i<=n.所以2 ^(x-1)<=n
取对数得到x = 1+log2n
- O(n^2): 表示时间与n的平方成正比
这个案例我在下面介绍了
时间复杂度
时间复杂度本质就是评估这个算法执行用了多长时间
def bubble_sort(nums):
if not nums or len(nums) == 1:
return nums[:]
for i in range(len(nums)-1):
flag = True
for j in range(len(nums)-1-i):
if nums[j] > nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
flag = False
if flag:
break
return nums[:]
nums = [8,9,22,44,66,77,88,55,66]
res2 = time.time()
print(bubble_sort(nums))
假定每个语句执行的时候,需要一个单位时间,我用t来表示
上面代码是一个简单的冒泡排序
循环中,外循环执行一次,内循环需要执行n-1次,那么我们将所有的t加起来,那就是1t+1t+nt+(n-1)t+1t+1t+1t+1t+1t
总和就是8t+(nn)t-1t = 7t+(nn)t
因为7t为常数,对代码的执行影响微乎其微,所以我们忽略不计,那么这段代码的时间复杂度为O(n^2)
时间复杂度的分析主要是两点:
- 循环的次数
- 每次循环需要的工作量
通常来说
O(1)< O(logn)< O(n)< O(nlogn )< O(n2)<O(2^n)<O(n!)
最好时间复杂度 O(1)
最坏时间复杂度 O(n)
空间复杂度
空间复杂度是指算法在执行期间所需的内存空间大小
常见的空间复杂度:
- O(1):表示空间复杂度是常数级别,与输入规模无关。
- O(n):表示空间复杂度与输入规模n成线性关系。
- O(n^2):表示空间复杂度与 n 的平方成正比。
举个例子
for i in range(1,10): print(i)
这是个简单的求1-10的和,他的空间复杂度是O(1),只需要常数级别的空间
再比如上面排序算法的空间复杂度通常是 O(n),需要 O(n) 的存储空间保存输入数据。
动态规划算法的空间复杂度可能是 O(n^2),需要 2 维存储数组。这个后面会介绍
通过分析空间复杂度,我们可以发现算法的性能和优化空间占用,进行优化