一些复杂度的实例
n | 对数阶 log2n log 2 n | 线性阶n | 平方阶 n2 n 2 | 指数阶 2n 2 n |
---|---|---|---|---|
100 | 7 | 100 | 10000 | 超标 |
1000 | 10 | 1000 | 1000000 | 超标 |
1000000 | 20 | 1000000 | 1000000000000 | 严重超标 |
搜索算法
搜索最小值
def indexOfMin(lyst):
'''返回最小项的索引'''
minIndex=0
currentIndex = 1
while currentIndex<len(lyst):
if lyst[currentIndex]<lyst[minIndex]:
minIndex = currentIndex
currentIndex +=1
return minIndex
这个算法的复杂度为O(n)
顺序搜索一个列表
def sequentialSearch(target,lyst):
'''如果找到目标返回他在列表中的索引否则返回-1'''
position = 0
while position<len(lyst):
if target == lyst[position]:
return position
position += 1
return -1
顺序搜索的分析要考虑如下这三种情况:
在最坏的情况下,目标位于列表的末尾,或者根本不在列表中。那么,算法必须访问每一项,并且对大小为n的列表要执行n次迭代。因此顺序搜索的最坏情况的复杂度为O(n)
在最好的情况下,算法只进行了一次迭代就在第一个位置找到目标项,复杂度为O(1)
要确定平均情况,把在每一个可能的位置找到目标项所需要的迭代次数相加,并用总和除以n。因此算法执行了(n+1)/2次迭代。复杂度仍为O(n).
有序列表的二叉树搜索
# 假设列表升序排列
def binarySearch(target, sortedLyst):
left = 0
right = len(sortedLyst) - 1
while left <= right:
midpoint = (left + right) // 2
if target == sortedLyst[midpoint]:
return midpoint
elif target < sortedLyst[midpoint]:
right = midpoint - 1
else:
left = midpoint + 1
return -1
二叉搜索的最坏情况的复杂度为O( log2n log 2 n )
基本排序算法
选择排序
工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
def selectionSort(lyst):
i = 0
while i < len(lyst) - 1:
minIndex = i
j = i + 1
while j < len(lyst): # 寻找第i项以后的最小项与第i项交换
if lyst[j] < lyst[minIndex]:
minIndex = j
j += 1
if minIndex != i:
lyst[i], lyst[minIndex] = lyst[minIndex], lyst[i]
i += 1
选择排序的复杂度的为O( n2 n 2 )
冒泡排序
冒泡排序算法的原理如下:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubbleSort(lyst):
n = len(lyst)
while n > 1:
swapped = False #记录每轮循环是否一次都没有交换过数据(即已经排好序了)以便尽早退出循环。
i = 1
while i < n:
if lyst[i] < lyst[i - 1]: # 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
lyst[i], lyst[i - 1] = lyst[i - 1], lyst[i]
swapped = True
i +=1
if not swapped:
return None
n -=1
冒泡排序复杂度为O( n2 n 2 )
插入排序
类似于人们排列手中扑克牌的顺序。也就是说,如果你按照顺序排 好了前i-1张牌,抓取第i张牌并且与手中的这些牌进行比较,直到找到合适的位置
假设有一组无序序列 R0,R1,...,Rn−1 R 0 , R 1 , . . . , R n − 1 。
- 我们先将这个序列中下标为 0 的元素视为元素个数为 1 的有序序列。
- 然后,我们要依次把 R1,R2,...,Rn−1 R 1 , R 2 , . . . , R n − 1 插入到这个有序序列中。所以,我们需要一个外部循环,从下标 1 扫描到 n-1 。
- 接下来描述插入过程。假设这是要将 Ri R i 插入到前面有序的序列中。由前面所述,我们可知,插入 Ri R i 时,前 i-1 个数肯定已经是有序了。
所以我们需要将 Ri R i 和 R0 R 0 ~ Ri−1 R i − 1 进行比较,确定要插入的合适位置。这就需要一个内部循环,我们一般是从后往前比较,即从下标 i-1 开始向 0 进行扫描。
def insertionSort(lyst):
i = 1
while i < len(lyst):
itemToInsert = lyst[i]
j = i - 1
while j >= 0:
if lyst[j] > itemToInsert:
lyst[j + 1] = lyst[j]
j -= 1
else:
break
lyst[j + 1] = itemToInsert
i += 1
更快的排序
快速排序
快速排序所使用的策略可以概括如下:
首先,从列表的中点位置选取一项。在这一项叫做基准点。
将列表中的项分区,以便小于基准点的所有项都移动到基准点左边,而剩下的项都移动到基准点的右边。根据相关的实际项,基准点自身的最终位置也是变化的。例如,如果基准点自身是最大的项,它会位于列表的最右边,如果基准点是最小值,它会位于最左边。但是,不管基准点最终位于何处,这个位置都是它在 完全排序的列表中的最终位置。
分而治之。对于基准点分割列表而形成的子列表,递归地重复该过程。一个子列表包含了基准点左边所有的项(现在是较小的项),另一个子列表包含了基准点右边的所有的项(现在是较大的项)。
每次遇到少于两个项的一个子列表,就介绍这个过程。
步骤 | 列表 |
---|---|
假设子列表是由数字组成的,其中包含一个基准点14 | 12 19 17 18 14 11 15 13 16 |
将基准点和最后一项交换 | 12 19 17 18 16 11 15 13 14 |
在第一项之前建立一个边界 | * 12 19 17 18 16 11 15 13 14 |
扫描小于基准点的第一项 | * 12 19 17 18 16 11 15 13 14 |
将这一项和边界之后的第一项交换。在这个例子中, 该项是与自身交换 | * 12 19 17 18 16 11 15 13 14 |
将边界向后移动 | 12 * 19 17 18 16 11 15 13 14 |
扫描小于基准点的第一项 | 12 * 19 17 18 16 11 15 13 14 |
将这一项和边界之后的第一项交换 | 12 * 11 17 18 16 19 15 13 14 |
将边界向后移动 | 12 11 * 17 18 16 19 15 13 14 |
扫描小于基准点的第一项 | 12 11 * 17 18 16 19 15 13 14 |
将这一项和边界之后的第一项交换 | 12 11 * 13 18 16 19 15 17 14 |
将边界向后移动 | 12 11 13 * 18 16 19 15 17 14 |
扫描小于基准点的第一项;然而这次没有这样的一项 | 12 11 13 * 18 16 19 15 17 14 |
将这一项和边界之后的第一项交换。此时小于基准 项的所有项都在基准项的左边;剩余的项在基准点 的右边 | 12 11 13 * 14 16 19 15 17 18 |
import random
def quicksort(lyst):
quicksortHelper(lyst, 0, len(lyst) - 1)
def quicksortHelper(lyst, left, right):
if left < right:
pivolocation = partition(lyst, left, right)
quicksortHelper(lyst, left, pivolocation - 1)
quicksortHelper(lyst, pivolocation + 1, right)
def partition(lyst, left, right):
# 找到基准点与最后一项交换
middle = (left + right) // 2
pivot = lyst[middle]
lyst[middle], lyst[right] = lyst[right], lyst[middle]
# 在第一项前建立一个边界
boundary = left
# 将小于基准点的第一项与边界后的第一项交换
for index in range(left, right):
if lyst[index] < pivot:
lyst[index], lyst[boundary] = lyst[boundary], lyst[index]
boundary += 1
# 全部扫描完后 将基准点与边界后的那一项交换
lyst[right], lyst[boundary] = lyst[boundary], lyst[right]
return boundary
def main(size=20, sort=quicksort):
lyst = []
for count in range(size):
lyst.append(random.randint(1, size + 1))
print(lyst)
sort(lyst)
print(lyst)
if __name__ == '__main__':
main()
合并排序(归并排序)
非正式的概述;
- 计算一个列表的中间位置,并且递归地排序其左边和右边的子列表(分而治之).
- 将两个排好序的子列表重新合并为单个的排好序的列表
- 当子列表不再能够划分的时候,停止这个过程
from TestClass import Array
def mergeSort(lyst):
'''
合并的过程中使用了和列表相同大小的一个数组(这个数组是在根目录下定义的)。这个数组名为copyBuffer。为了避免每次调用merge的时候为copyBuffer分配和释放内存的开销。只在mergeSort中分配一次该缓冲区,并且在后续将其作为一个参数传递给mergeSortHelper和merge。每次调用mergeSortHelper的时候,都需要知道他所操作的子列表的边界。这些边界通过另外的两个参数low,high来提供。
'''
copyBuffer = Array(len(lyst))
mergeSortHelper(lyst, copyBuffer, 0, len(lyst) - 1)
def mergeSortHelper(lyst, copyBuffer, low, high):
if low < high:
middle = (low + high) // 2
mergeSortHelper(lyst, copyBuffer, low, middle)
mergeSortHelper(lyst, copyBuffer, middle + 1, high)
merage(lyst, copyBuffer, low, middle, high)
def merage(lyst, copyBuffer, low, middle, high):
'''
:param low: 第一个子列表的第一索引
:param middle: 第一个子列表的最后一索引
middle+1:第二个子列表的第一索引
:param high: 第二个子列表的最后一索引
这个函数将两个排好序的子列表合并到一个大的拍好序的子列表中。
'''
i1 = low # 将索引指针设置为每个子列表的第一索引
i2 = middle + 1
for i in range(low, high + 1): # 从每个子列表的第一项开始重复地比较各项。将较小的项从其子列表中复制到复制缓存即copyBuffer中
if i1 > middle: # 并继续处理子列表中的下一项。重复这个过程,直到两个子列表中的所有的项都已经复制过。如果先到达其中一个子列表的末尾,通过从另一个子列表复制剩余的项,从而结束这个步骤。
copyBuffer[i] = lyst[i2]
i2 += 1
elif i2 > high:
copyBuffer[i] = lyst[i1]
i1 += 1
elif lyst[i1] < lyst[i2]:
copyBuffer[i] = lyst[i1]
i1 += 1
else:
copyBuffer[i] = lyst[i2]
i2 += 1
for i in range(low, high + 1): # 将copyBuffer中low 和high之间的部分,复制回lyst中对应的位置。
lyst[i] = copyBuffer[i]
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
---|---|---|---|---|---|
冒泡 | O( n2 n 2 ) | O( n2 n 2 ) | 稳定 | O(1) | n小时较好 |
选择 | O( n2 n 2 ) | O( n2 n 2 ) | 不稳定 | O(1) | n小时较好 |
插入 | O( n2 n 2 ) | O( n2 n 2 ) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O( logRB l o g R B ) | O( logRB l o g R B ) | 稳定 | O(n) | B是真数(0-9), R是基数(个十百) |
Shell | O( nlogn n l o g n ) | O( ns n s ) (1 |