一、算法介绍
1、 算法是什么
算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
2、时间复杂度
在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度(O是数量级的符号 ),简称时间复杂度。
常见时间复杂度单位:效率从上到下变低,
O(1) 简单的一次运算(常数阶)
O(n) 一次循环(线性阶)
O(n^2) 两个循环(平方阶)
O(logn) 循环减半
O(nlogn) 一个循环加一个循环减半
O(n^2logn)
O(n^3)
一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法
O(1) 常数阶 < O(logn) 对数阶 < O(n) 线性阶 < O(nlogn) < O(n^2) 平方阶 < O(n^3) < { O(2^n) < O(n!) < O(n^n)
大O推导法:
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数
比如:
这是一段C的代码 #include "stdio.h" int main() { int i, j, x = 0, sum = 0, n = 100; /* 执行1次 */ for( i = 1; i <= n; i++) { sum = sum + i; /* 执行n次 */ for( j = 1; j <= n; j++) { x++; /* 执行n*n次 */ sum = sum + x; /* 执行n*n此 */ } } printf("%d", sum); /* 执行1次 */ }
分析:
执行总次数 = 1 + n + n*n + n*n + 1 = 2n2 + n + 2
根据大O推导法:
1.用常数 1 取代运行时间中的所有加法常数:执行总次数为: 2n2 + n + 1
2.在修改后的运行次数函数中,只保留最高阶项,这里的最高阶是 n 的二次方: 执行总次数为: 2n2
3.如果最高阶项存在且不是 1 ,则去除与这个项相乘的常数,这里 n 的二次方不是 1 所以要去除这个项的相乘常数:执行总次数为: n2
因此最后我们得到上面那段代码的算法时间复杂度表示为: O(n2)
3、空间复杂度
空间复杂度是用来评估算法内存占用大小的单位
空间换时间:如果需要增快算法的速度,需要的空间会更大
二、python实现常见的排序算法
前三种比较LowB,后三种比较NB
前三种时间复杂度都是O(n^2),后三种时间复杂度都是O(nlog(n))
1、冒泡(交换)排序
原理:列表中两个相邻的数,如果前一个数比后一个数大,就做交换。一共需要遍历列表的次数是len(lst)-1
时间复杂度:O(n^2)
def bubble_sort(lst): for i in range(len(lst) - 1): # 这是需要循环遍历多少次 for j in range(len(lst) - 1 - i): # 每次数组中的无序区 if lst[j] > lst[j + 1]: lst[j], lst[j + 1] = lst[j + 1], lst[j] lst = [1, 2, 44, 3, 5] bubble_sort(lst) print(lst)
优化:如果在循环的时候,有一次没有进行交换,就表示数列中的数据已经是有序的
时间复杂度:最好情况是0(n),只遍历一次,一般情况和最坏情况都是O(n^2)
def bubble_sort(lst): for i in range(len(lst)-1): # 这是需要循环遍历多少次 change = False # 做一个标志变量 for j in range(len(lst)-1-i): # 每次数组中的无序区 if lst[j] >lst[j+1]: lst[j],lst[j+1] = lst[j+1],lst[j] change = True # 每次遍历,如果进来排序的话,就会改变change的值 if not change: # 如果change没有改变,那就表示当前的序列是有序的,直接跳出循环即可 return lst = [1, 2, 44, 3, 5] bubble_sort(lst) print(lst)
2、选择排序
原理:每次遍历找到当下数组最小的数,并把它放到第一个位置,下次遍历剩下的无序区,记录剩余列表中最小的数,继续放置
时间复杂度 O(n^2)
方法一:
def select_sort(lst): for i in range(len(lst) - 1): # 当前需遍历的次数 min_loc = i # 当前最小数的位置 for j in range(i + 1, len(lst)): # 无序区 if lst[j] < lst[min_loc]: # 如果有更小的数 lst[min_loc], lst[j] = lst[j], lst[min_loc] # 把最小的数交换到当前最小数的位置(索引) lst = [1, 2, 44, 3, 5] select_sort(lst) print(lst)
方法二:
def select_sort(lst): for i in range(len(lst) - 1): # 当前需遍历的次数 min_loc = i # 当前最小数的位置 for j in range(i + 1, len(lst)): # 无序区 if lst[j] < lst[min_loc]: # 如果有更小的数 min_loc = j # 最小数的位置改变 if min_loc != i: lst[i], lst[min_loc] = lst[min_loc], lst[i] # 把最小数和无序区第一个数交换 lst = [1, 2, 44, 3, 5] select_sort(lst) print(lst)
3、插入排序
原理:列表分为有序区和无序区,有序区是一个相对有序的序列,就是说在有序区内,是已经排序好了的,最初有序区只有一个元素,每次从无序区选择一个值,插入到有序区,直到无序区为空
时间复杂度:O(n^2)
原理图
def insert_sort(lst): for i in range(1, len(lst)): # 无序区从1开始向前跟有序区比较、插入 (有序区初始有一个值) for j in range(i, 0, -1): # 如果无序区的值小于前一个元素,交换位置 if lst[j] < lst[j - 1]: lst[j], lst[j - 1] = lst[j - 1], lst[j] else: break lst = [12, 15, 9, 20, 6, 31, 24] insert_sort(lst) print(lst)
4、快速排序
思路:取第一个元素,让它归位,就是放到一个位置,使它左边的都比它小,右边的都比它大,然后递归完成排序
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据, 然后将所有比它小的数都放到它左边,所有比它大的数都放到它右边,这个过程称为一趟快速排序。 值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。 一趟快速排序的算法是: 1)设置两个变量i、j,排序开始的时候:i=0,j=N-1 2)以第一个数组元素作为关键数据,赋值给key,即key=A[0] 3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换 4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换 5)重复第3、4步,直到i==j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值, 使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。