排序算法(python)
算法:
算法是独立存在的一种解决问题的方法和思想
五大特性:
- 输入: 具有0个或多个输入
- 输出: 只扫有1个或者多个输出
- 有穷性: 在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤在可接受的时间内可以完成
- 确定性: 每一步都有确定的含义,不会出现二义性
- 可行性: 每一步都是可行的,也就是说每一步都能够执行有限的次数完成
“大O记法”:对于单调的整数函数f,如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)<=c*g(n),就说函数g是f的一个渐近函数(忽略常数),记为f(n)=O(g(n))。也就是说,在趋向无穷的极限意义下,函数f的增长速度受到函数g的约束,亦即函数f与函数g的特征相似。
时间复杂度:假设存在函数g,使得算法A处理规模为n的问题示例所用时间为T(n)=O(g(n)),则称O(g(n))为算法A的渐近时间复杂度,简称时间复杂度,记为T(n)
- 算法完成工作最少需要多少基本操作,即最优时间复杂度
- 算法完成工作最多需要多少基本操作,即最坏时间复杂度
- 算法完成工作平均需要多少基本操作,即平均时间复杂度
执行次数函数举例 | 阶 | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
3n2+2n+1 | O(n2) | 平方阶 |
5log2n+20 | O(logn) | 对数阶 |
2n+3nlog2n+19 | O(nlogn) | nlogn阶 |
6n3+2n2+3n+4 | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
注意: 经常将log2n(以2为底的对数)简写成logn
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
算法的时间复杂度和空间复杂度合称为算法的复杂度
timeit模块
def t1():
li = list()
for i in range(1000):
li.append(i)
import timeit
timer1 = timeit.TImer(stmt='t1()', setup='from file_name.py import t1', timer='默认计时器')
# 当前文件
timer1 = timeit.TImer(stmt='t1()', setup='from __main__ import t1', timer='默认计时器')
# 执行
timer1.timit(number=1000) # 返回执行1000次后平均值
列表相加获得列表效率较慢,其次insert()方法也较为慢
数据结构
静态的表现数据之间的关系类型
抽象数据类型:
数据类型和算法构成的程序
顺序表
顺序表(数组)
表头: 容量和已有元素个数
两种实现方式
- 一体式结构: 表头存储区+ 元素存储区
- 分离式结构: 表头+ 元素存储区引用 ++ 元素存储区
元素存储区扩充策略
- 每次扩充n个元素位置
- 每次扩充倍数级
链表
单向链表
每个节点包含两个域,一个信息域,一个链接域(指向后继节点)
双向链表
前驱节点,后置节点
单向循环链表
最后一个节点指向第一个节点
双向循环链表
排序
稳定性: 稳定排序算法会让原本有相等简直的记录维持相对次序(比如数列中相等数据维持排序之前的顺序)
冒泡排序
它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来
- 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
- 最坏时间复杂度:O(n2)
- 稳定性:稳定
def bubble_sort(alist): for j in range(len(alist)-1,0,-1): # j表示每次遍历需要比较的次数,是逐渐减小的 for i in range(j): if alist[i] > alist[i+1]: alist[i], alist[i+1] = alist[i+1], alist[i] li = [54,26,93,17,77,31,44,55,20] bubble_sort(li) print(li)
选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾,以此类推,知道所有元素均排序完毕
- 最优时间复杂度:O(n2)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定(考虑升序每次选择最大的情况)
def selection_sort(alist): n = len(alist) # 需要进行n-1次选择操作 for i in range(n-1): # 记录最小位置 min_index = i # 从i+1位置到末尾选择出最小数据 for j in range(i+1, n): if alist[j] < alist[min_index]: min_index = j # 如果选择出的数据不在正确位置,进行交换 if min_index != i: alist[i], alist[min_index] = alist[min_index], alist[i] alist = [54,226,93,17,77,31,44,55,20] selection_sort(alist) print(alist)
插入排序
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到对应位置并插入;插入排序在实现上,在从后向前扫描过程中,需要反复把已排序的元素逐步向后挪位,为新元素提供插入空间
- 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
- 最坏时间复杂度:O(n2)
- 稳定性:稳定
def insert_sort(alist): # 从第二个位置,即下标为1的元素开始向前插入 for i in range(1, len(alist)): # 从第i个元素开始向前比较,如果小于前一个元素,交换位置 for j in range(i, 0, -1): if alist[j] < alist[j-1]: alist[j], alist[j-1] = alist[j-1], alist[j] alist = [54,26,93,17,77,31,44,55,20] insert_sort(alist) print(alist)
希尔排序
希尔排序是非稳定排序;把记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
- 最优时间复杂度:根据步长序列的不同而不同
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定
def shell_sort(alist): n = len(alist) # 初始步长 gap = n // 2 while gap > 0: # 按步长进行插入排序 for i in range(gap, n): j = i # 插入排序 while j>=gap and alist[j-gap] > alist[j]: alist[j-gap], alist[j] = alist[j], alist[j-gap] j -= gap # 得到新的步长 gap //= 2 alist = [54,26,93,17,77,31,44,55,20] shell_sort(alist) print(alist)
快速排序
通过一趟排序将要排序的数据分割成独立的两个部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定
def quick_sort(alist, start, end): """快速排序""" # 递归的退出条件 if start >= end: return # 设定起始元素为要寻找位置的基准元素 mid = alist[start] # low为序列左边的由左向右移动的游标 low = start # high为序列右边的由右向左移动的游标 high = end while low < high: # 如果low与high未重合,high指向的元素不比基准元素小,则high向左移动 while low < high and alist[high] >= mid: high -= 1 # 将high指向的元素放到low的位置上 alist[low] = alist[high] # 如果low与high未重合,low指向的元素比基准元素小,则low向右移动 while low < high and alist[low] < mid: low += 1 # 将low指向的元素放到high的位置上 alist[high] = alist[low] # 退出循环后,low与high重合,此时所指位置为基准元素的正确位置 # 将基准元素放到该位置 alist[low] = mid # 对基准元素左边的子序列进行快速排序 quick_sort(alist, start, low-1) # 对基准元素右边的子序列进行快速排序 quick_sort(alist, low+1, end)
alist = [54,26,93,17,77,31,44,55,20]
quick_sort(alist,0,len(alist)-1)
print(alist)
“`归并排序
归并排序的思想就是先递归分解数组,再合并数组;将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数据的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位,然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(nlogn)
- 稳定性:稳定
“`python
def merge_sort(alist):
if len(alist) <= 1:
return alist
# 二分分解
num = len(alist)/2
left = merge_sort(alist[:num])
right = merge_sort(alist[num:])
# 合并
return merge(left,right)def merge(left, right):
”’合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组”’
#left与right的下标指针
l, r = 0, 0
result = []
while l