一、什么是算法
算法(Algorithm):一个计算过程,解决问题的方法
二、时间复杂度、空间复杂度
Ⅰ、时间复杂度
时间复杂度是一个函数,它定量描述该算法的运行时间,时间复杂度常用“O”表示,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的情况。呈现时间频度的变化规律,记为T(n)=O(f(n)) 指数时间:一个问题求解所需的执行时间m(n),依输入数据n呈指数倍成长(即 求解所需的执行时间呈指数倍成长)
时间复杂度是用来估计算法运行时间的一个式子(单位),一般来说,时间复杂度高的算法比复杂度低的算法慢
print('Hello world') # O(1)
# O(1)
print('Hello World')
print('Hello Python')
print('Hello Algorithm')
for i in range(n): # O(n)
print('Hello world')
for i in range(n): # O(n^2)
for j in range(n):
print('Hello world')
for i in range(n): # O(n^2)
print('Hello World')
for j in range(n):
print('Hello World')
for i in range(n): # O(n^2)
for j in range(i):
print('Hello World')
for i in range(n):
for j in range(n):
for k in range(n):
print('Hello World') # O(n^3)
Ⅱ、空间复杂度
空间复杂度:用来评估算法内存占用大小的一个式子
三、列表排序
列表排序:将无序列表变为有序列表
1、冒泡排序
所谓冒泡,就是将元素两两之间进行比较,谁大就往后移动,直到将最大的元素排到最后面,接着再循环一趟,从头开始进行两两比较,而上一趟已经排好的那个元素就不用进行比较了。(图中排好序的元素标记为黄色柱子)
时间复杂度:O(n2)
基础写法:
def bubble_sort(li):
for i in range(len(li) - 1):
for j in range(len(li) - 1 - i):
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
return li
list = [3, 1, 5.78, 8, 63, 96, 21, 0, 2]
print(bubble_sort(list))
冒泡优化1
# 设定一个变量为False,如果元素之间交换了位置,将变量重新赋值为True,最后再判断,#在一次循环结束后,变量如果还是为False,则brak退出循环,结束排序。
#如果冒泡排序中执行一趟而没有交换,则列表已经是有序状态,可以直接结束算法。
def bubble_sort(li):
for i in range(len(li) - 1):
flag = False
for j in range(len(li) - 1 - i):
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
flag = True
print("冒泡第%s次" % i)
if not flag:
print("yes")
break
return li
# list = [3, 1, 5.78, 8, 63, 96, 21, 0, 2]
list = [1, 2, 3, 4, 5, 8, 6, 9, 7]
print(bubble_sort(list))
冒泡优化三
双向排序提高效率,即当一次从左向右的排序比较结束后,立马从右向左来一次排序比较。
# 双向冒泡
@cal_time
def bubble_sort2(li):
for i in range(len(li) - 1):
flag = False
for j in range(len(li) - 1 - i):
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
flag = True
if flag:
for j in range(len(li) - 1 - i, 0, -1):
if li[j - 1] > li[j]:
li[j - 1], li[j] = li[j], li[j - 1]
flag = True
if not flag:
break
return li
三种冒泡执行时间对比
import time, sys
sys.setrecursionlimit(1000000)
# 自定义计时装饰器
def cal_time(func):
def waraper(*args, **kwargs):
t1 = time.time()
res = func(*args, **kwargs)
t2 = time.time()
print('%s消耗的时间是:%s' % (func.__name__, t2 - t1))
return res
return waraper
# 随机1-10000生成列表
li = list(range(1, 10000))
random.shuffle(li)
bubble_sort(li)
bubble_sort1(li)
bubble_sort2(li)
---------------------------------------------------------------------
# 时间:
bubble_sort消耗的时间是:9.232983350753784
bubble_sort1消耗的时间是:0.0010197162628173828
bubble_sort2消耗的时间是:0.0009989738464355469
2、选择排序
核心算法:固定位置,选择元素,即:先从序列中,找到最小的元素,放在第一个位置,之后找到第二小的元素,放在第二个元素,以此类推,就可以完成整个排序工作了。
# 时间复杂度: O(n^2) 空间复杂度: O(1)
@cal_time
def select_sort(li):
for i in range(len(li) - 1):
min_li = i
for j in range(i + 1, len(li)):
if li[j] < li[min_li]:
min_li = j
if min_li != i:
li[i], li[min_li] = li[min_li], li[i]
li = list(range(1, 10000))
random.shuffle(li)
select_sort(li)
3、插入排序
# 列表被分为有序区和无序区两个部分。最初有序区只有一个元素。
# 每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空。
@cal_time
def insert_sort(li):
for i in range(1, len(li)):
tmp = li[i]
# 设置当前值其哪一个元素标识
j = i - 1
while j >= 0 and tmp < li[j]:
li[j + 1] = li[j]
j = j - 1
li[j + 1] = tmp
li = list(range(1, 100))
random.shuffle(li)
insert_sort(li)
print(li)
排序low组
- 冒泡排序 插入排序 选择排序
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
4、快速排序
快速排序的思想:首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序
快速排序的算法是:
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-完成的时候,此时令循环结束)。
# 时间复杂度: O(nlogn) 空间复杂度: O(1)
def partition(data, left, right):
tmp = data[left]
while left < right:
while left < right and data[right] >= tmp:
right -= 1
data[left] = data[right]
while left < right and data[left] <= tmp:
left += 1
data[right] = data[left]
data[left] = tmp
return tmp
def _quick_sort(data, left, right):
if left < right:
mid = partition(data, left, right)
_quick_sort(data, left, mid - 1)
_quick_sort(data, mid + 1, right)
@cal_time
def quick_sort(li, left, right):
_quick_sort(li, left, right)
4、归并排序
**归并排序**仍然是利用完全二叉树实现,它是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列。
**基本过程**:假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列,再两两归并,最终得到一个长度为n的有序序列为止,这称为2路归并排序。
![](<http://118.25.51.117:8080 /images/gbsf1.png>)
图中主要表明了实例代码中的两部分,分别为原始序列的拆分和合并两部分
def merge(li, low, mid, high):
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if li[i] < li[j]:
ltmp.append(li[i])
i = i + 1
else:
ltmp.append(li[j])
j = j + 1
while i <= mid:
ltmp.append(li[i])
i = i + 1
while j <= high:
ltmp.append(li[j])
j = j + 1
li[low:high + 1] = ltmp
def _mergesort(li, low, high):
if low < high:
mid = (low + high) // 2
_mergesort(li, low, mid)
_mergesort(li, mid + 1, high)
merge(li, low, mid, high)
@cal_time
def mergesort(li, low, high):
_mergesort(li, low, high)
多种排序算法在同一条件下的执行情况
li = list(range(1, 10000))
random.shuffle(li)
mergesort(li, 0, len(li) - 1)
li = list(range(1, 10000))
random.shuffle(li)
bubble_sort1(li)
li = list(range(1, 10000))
random.shuffle(li)
bubble_sort2(li)
li = list(range(1, 10000))
random.shuffle(li)
select_sort(li)
li = list(range(1, 10000))
random.shuffle(li)
insert_sort(li)
li = list(range(1, 10000))
random.shuffle(li)
quick_sort(li, 0, len(li) - 1)
---------------------------------------------------------------------mergesort消耗的时间是:0.03596305847167969
bubble_sort1消耗的时间是:12.642452716827393
bubble_sort2消耗的时间是:10.469623804092407
select_sort消耗的时间是:4.044940233230591
insert_sort消耗的时间是:5.539592027664185
quick_sort消耗的时间是:0.032910823822021484
5、希尔排序
希尔排序是一种分组插入排序算法。
首先取一个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序;
取第二个整数d2=d1/2,重复上述分组排序过程,直到di=1,即所有元素在同一组内进行直接插入排序。
希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。
# 希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。
# 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
# 希尔排序是基于插入排序的以下两点性质而提出改进方法的:
# 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
# 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
def shell_sort(li):
gap = len(li) // 2
while gap > 0:
for i in range(gap, len(li)):
tmp = li[i]
j = i - gap
while j >= 0 and tmp < li[j]:
li[j + gap] = li[j]
j -= gap
li[j + gap] = tmp
gap /= 2
小结:
排序方法 | 时间复杂度 | 稳定性 | 代码复杂度 | ||
最坏情况 | 平均情况 | 最好情况 | |||
冒泡排序 | O(n2) | O(n2) | O(n) | 稳定 | 简单 |
直接选择排序 | O(n2) | O(n2) | O(n2) | 不稳定 | 简单 |
直接插入排序 | O(n2) | O(n2) | O(n2) | 稳定 | 简单 |
快速排序 | O(n2) | O(nlogn) | O(nlogn) | 不稳定 | 较复杂 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | 不稳定 | 复杂 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | 稳定 | 较复杂 |
希尔排序 | O(1.3n) | 不稳定 | 较复杂 |
走在从入门到放弃,最终删库跑路的路上