一、查找
1、顺序查找
查找最简单的方法是顺序查找(又叫线性查找),也就是从头到尾一个一个去查找,平均时间复杂度是O(n),空间复杂度是O(1)(没有使用额外的空间)
number_list = [0, 1, 2, 3, 4, 5, 6, 7]
def linear_search(value, iterable):
for index, val in enumerate(iterable):
if val == value:
return index
return -1
linear_search(5, number_list)
2、二分查找(数组是有序的)
我们主要介绍二分查找。二分查找需要列表是有序的,无序的列表可以使用python的sorted()函数先进行排序,再进行二分查找
def binary_search(list,item):
low = 0
high = len(list) - 1
while low <= high:
mid = (low + high) / 2
guess = list[mid]
if guess == item:
return mid
if guess > item:
high = mid - 1
else:
low = mid + 1
return None
二、排序
1.冒泡排序O(n^2)
bubble sort 可以说是最简单的一种排序算法了,它的思想如下。对一个数组进行 n-1 轮迭代,每次比较相邻两个元素, 如果相邻的元素前者大于后者,就交换它们。因为直接在元素上操作而不是返回新的数组,所以是一个 inplace 的操作。 这里冒泡的意思其实就是每一轮冒泡一个最大的元素就会通过不断比较和交换相邻元素使它转移到最右边。你可以想象假如有 10 个小盆友从左到右站成一排,个头不等。老师想让他们按照个头从低到高站好,于是他开始喊口号。 每喊一次,从第一个小盆友开始,相邻的小朋友如果身高不是正序就会两两调换,就这样第一轮个头最高的排到了最右边。(冒泡到最右边) 第二轮依次这么来,从第一个小朋友开始两两交换,这样次高的小盆友又排到了倒数第二个位置。依次类推
mport random
def bubble_sort(seq): # O(n^2), n(n-1)/2 = 1/2(n^2 + n)
n = len(seq)
for i in range(n-1):
print(seq) # 我打印出来让你看清楚每一轮最高、次高、次次高...的小朋友会冒泡到右边
for j in range(n-1-i): # 这里之所以 n-1 还需要 减去 i 是因为每一轮冒泡最大的元素都会冒泡到最后,无需再比较
if seq[j] > seq[j+1]:
seq[j], seq[j+1] = seq[j+1], seq[j]
print(seq)
def test_bubble_sort():
seq = list(range(10)) # 注意 python3 返回迭代器,所以我都用 list 强转了,python2 range 返回的就是 list
random.shuffle(seq) # shuffle inplace 操作,打乱数组
bubble_sort(seq)
assert seq == sorted(seq) # 注意呦,内置的 sorted 就不是 inplace 的,它返回一个新的数组,不影响传入的参数
""" 我打印出来让你看到每次从最高到次高的小盆友就这么排好序了,因为是随机数,你第一个没有排序的数组应该和我的不一样
[3, 4, 5, 0, 9, 1, 7, 8, 6, 2]
[3, 4, 0, 5, 1, 7, 8, 6, 2, 9]
[3, 0, 4, 1, 5, 7, 6, 2, 8, 9]
[0, 3, 1, 4, 5, 6, 2, 7, 8, 9]
[0, 1, 3, 4, 5, 2, 6, 7, 8, 9]
[0, 1, 3, 4, 2, 5, 6, 7, 8, 9]
[0, 1, 3, 2, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
"""
2.选择排序O(n^2)
刚才看到冒泡是每轮迭代中,如果相邻的两个元素前者大于后者了就交换两个相邻元素(假设正序排序)。其实还有一种思路就是, 每次我们找到最小的元素插入迭代的起始位置,这样每个位置从它自己的位置开始它就是最小的了,一圈下来数组就有序了。 选择可以理解为 一个 0 到 n-1 的迭代,每次向后查找选择一个最小的元素。
# 先编写一个找出数组中最小元素
def find_smallest(arr):
smallest = arr[0]
smallest_index = 0
for i in range(1,len(arr)):
if arr[i] < samllest:
smallest = arr[i]
samllest_index = i
return smallest_index
# 选择排序
def selection_sort(arr):
new_arr = []
for i in range(len(arr)):
smallest = find_smallest(arr)
new_arr.append(arr.pop(smallest))
return new_arr
3.快速排序-分治算法(O(nlogn),最差情况O(n^2))
def quick_sort(array):
if len(array) < 2:
return array # 基线条件:为空或只有一个元素的数组是“有序”的
else:
pivot = array[0] # 递归条件
less = [i for i in array[1:] if i <= pivot]
greater = [i for i in array[1:] if i > pivot]
return quick_sort(less) + [pivot] + quick_sort(greater)
4.归并排序
归并排序的思想就是先递归分解数组,再合并数组。
将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
由于和堆排序类似,都是利用完全二叉树的相关性质,所以它在时间复杂度方面,最好、最坏和平均的时间复杂度都是O(nlogn);但是,在空间复杂度方面,由于它开辟了一块新的内存空间用来存放left和right子序列,而两个子序列的总大小其实和lst是一样的,为n,所以它的空间复杂度为O(n),这是归并排序的主要弱点,牺牲了空间复杂度来换取时间复杂度的减少。稳定性方面,只要在关键码相同时采用左序列元素先行的原则,就能保证算法的稳定性,另一方面,归并排序算法没有适应性,无论对于什么样的序列它都要做logn遍的递归。
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[]合并成一个大的有序数组
l,r = 0,0 # left和right的下标指针
result = []
while l<len(left) and r<len(right):
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result += left[l:]
result += right[r:]
python有一个模块,专门提供了归并排序的方法,叫做“heapq”模块,因此我们只要将分解后的结果导入该方法即可。例如:
from heapq import merge
def merge_sort(lst):
if len(lst) <= 1:
return lst # 从递归中返回长度为1的序列
middle = len(lst) / 2
left = merge_sort(lst[:middle]) # 通过不断递归,将原始序列拆分成n个小序列
right = merge_sort(lst[middle:])
return list(merge(left, right))
知乎发不了动图,排序的动图可以参考
必学十大经典排序算法,看这篇就够了(附完整代码/动图/优质文章)(修订版)mp.weixin.qq.com