目录
第五章---必会两三个
排序算法的稳定性
排序算法:把无序序列排列成有序序列的算法。
稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
(4, 1) (3, 1) (3, 7) (5, 6)
(3, 1) (3, 7) (4, 1) (5, 6) (维持次序)
(3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)
冒泡排序(交换)
第一次冒泡(找到最大的,排在最后一位):
需要进行n-1次冒泡过程,每次冒泡过程比较次数如下:
最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
最坏时间复杂度:O(n2)
稳定性:稳定(相等的数不交换)
# coding:utf-8
def bubble_sort(alist):
"""冒泡排序"""
n = len(alist)
for j in range(n-1):
count = 0
for i in range(0,n-1-j):
# 班长从头走到尾
if alist[i] > alist[i+1]:
alist[i],alist[i+1] = alist[i+1],alist[i]
count += 1
if 0 == count: # 如果没有交换,本来就是有序的,则退出
return
li = [54,26,93,17,77,31,44,55,20]
bubble_sort(li)
print(li)
选择排序
选出最大的放在最后面,后面是有序序列,前面是无序序列,从无序序列中选出,放到后面的有序序列中。
最优时间复杂度:O(n2)
最坏时间复杂度:O(n2)
稳定性:不稳定(考虑升序每次选择最大的情况)如果后面再来一个26,可能排在前面的26之前
alist = (26,1),16,17,15,(26,2),11,10,9
升序一 = 9,16,17,15,11,10,(26,2),(26,1)
alist = 54,26,93,17,77,31,44,55,20 len = 9
j = 0, 1, 2, 3, 4, 5, 6, 7
min = 1, 2, 3, 4, 5, 6, 7, 8
j = 0
min = 0 , 0+1
alist[0],alist[3] = alist[3],alist[0]
j = 1
min = 1 , 1+1
alist[1],alist[8] = alist[8],alist[1]
def select_sort(alist):
"""选择排序,从小到大排"""
n = len(alist)
for j in range(n-1): # j:0 ~ n-2
min_index = j
for i in range(j+1,n): #结尾到下标n-1
if alist[min_index] > alist[i]:
min_index = i
alist[j],alist[min_index] = alist[min_index],alist[j]
alist = [54,226,93,17,77,31,44,55,20]
select_sort(alist)
print(alist)
插入排序(交换)
从前面无序序列中按顺序插入到后面有序序列的相应位置上。
alist = [54, 226,93,17,77,31,44,55,20]
alist = [54,226, 93,17,77,31,44,55,20]
alist = [54,93,226, 17,77,31,44,55,20]
alist = [17,54,93,226, 77,31,44,55,20]
alist = [17,54,77,93,226, 31,44,55,20]
alist = [17,31,54,77,93,226, 44,55,20]
alist = [17,31,44,54,77,93,226, 55,20]
alist = [17,31,44,54,55,77,93,226, 20]
alist = [17,20,31,44,54,55,77,93,226]
最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
最坏时间复杂度:O(n2)
稳定性:稳定
# coding:utf-8
def insert_sort(alist):
"""插入排序"""
n = len(alist)
# 从右边的无序序列中取出多少个元素执行这样的过程
for j in range(1,n):
# j = [1,2,3,...,n-1]
i = j # i代表内层循环的起始值
# 执行从右边的无序序列中取出第一个元素,即i元素的位置,然后将其放入正确的位置中
while i > 0:
if alist[i] < alist[i-1]:
alist[i],alist[i-1] = alist[i-1],alist[i]
i -=1
else: # 如果后面的数比前面的数大,可以直接退出,降低时间复杂度
break
# for i in range(j,0,-1)就是把 while i>0和 i-=1结合
alist = [54,226,93,17,77,31,44,55,20]
print(alist)
insert_sort(alist)
print(alist)
希尔排序
最优时间复杂度:根据步长序列的不同而不同
最坏时间复杂度:O(n2)
稳定性:不稳定
# coding:utf-8
def shell_sort(alist):
"""希尔排序"""
n = len(alist)
gap = n//2 # 在python2里面是一个/
while gap >= 1 : # gap变化到0之前,插入算法执行的次数
# 插入算法,与普通的插入算法的区别就是gap步长
for j in range(gap,n):
# j = [gap,gap+1,gap+2,,,,,]
i = j
while i >= gap :
if alist[i] < alist[i-gap]:
alist[i],alist[i-gap] = alist[i-gap],alist[i]
i -= gap # i=i-gap
else:
break
gap //= 2 # 缩短gap步长
alist = [54,226,93,17,77,31,44,55,20]
print(alist)
shell_sort(alist)
print(alist)
快速排序(递归嵌套)---必会
从数列中挑出一个元素,称为"基准"(pivot),
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
# coding :utf-8
def quick_sort(alist,first,last):
"""快速排序"""
if first >= last: #嵌套退出
return
mid_value = alist[first]
low = first #从前往后
high = last #从后往前
while low < high:
while low < high and alist[high] >= mid_value:
high -= 1
alist[low] = alist[high]
while low < high and alist[low] < mid_value:
low += 1
alist[high] = alist[low]
alist[low] = mid_value
# 对low左边的列表执行快速排序,然后对low右边的列表快速排序
quick_sort(alist,first,low-1)
quick_sort(alist,low+1,last) #调用函数自身就是递归
if __name__ == '__main__':
alist = [54,226,93,17,77,31,44,55,20]
print(alist)
quick_sort(alist,0,len(alist)-1)
print(alist)
最优时间复杂度:O(nlogn)
最坏时间复杂度:O(n2)
稳定性:不稳定
在最好的情况,每次我们运行一次分区,我们会把一个数列分为两个几近相等的片段。这个意思就是每次递归调用处理一半大小的数列。因此,在到达大小为一的数列前,我们只要作log n次嵌套的调用。这个意思就是调用树的深度是O(log n)。但是在同一层次结构的两个程序调用中,不会处理到原来数列的相同部分;因此,程序调用的每一层次结构总共全部仅需要O(n)的时间(每个调用有某些共同的额外耗费,但是因为在每一层次结构仅仅只有O(n)个调用,这些被归纳在O(n)系数中)。结果是这个算法仅需使用O(n log n)时间。
归并排序(递归嵌套)
54,26,93,17,77,31,44,55,100 | |||||||||
54,26,93,17 | 77,31,44,55,100 | ||||||||
54,26 | 93,17 | 77,31 | 44,55,100 | ||||||
54 | 26 | 93 | 17 | 77 | 31 | 44 | 55,100 | ||
54 | 26 | 93 | 17 | 77 | 31 | 44 | 55 | 100 | |
26,54 | 17,93 | 31,77 | 44 | 55,100 | |||||
26,54 | 17,93 | 31,77 | 44,55,100 | ||||||
17,26,54,93 | 31,44,55,77,100 | ||||||||
17,26,31,44,54,54,77,93,100 |
奇数多拆一层,也是拆到一个一个
alist = [54, 226, 93, 17, 77, 31, 44, 55, 20]
left_li = merge_sort[54, 226, 93, 17]
left_li = merge_sort[54, 226]
left_li = [54]
right_li = [226]
result = [54, 226]
return result
right_li = [93, 17]
left_li = [93]
right_li = [17]
result = [17,93]
return result
result = [17, 54, 93, 226]
return result
left_li = [17, 54, 93, 226]
right_li = merge_sort[ 77, 31, 44, 55, 20]
.....
right_li = [20, 31, 44, 55, 77]
soted_list = merge_sort(alist)
soted_list = [17, 20, 31, 44, 54, 55, 77, 93, 226]
# coding:utf-8
def merge_sort(alist):
"""归并排序"""
n = len(alist)
if n <= 1:
return alist
mid = n//2
# left 采用归并排序后有序的新的列表
left_li = merge_sort(alist[:mid])
right_li = merge_sort(alist[mid:])
# 将两个有序的子序列合并成一个整体
# merge(left,right)
left_pointer,right_pointer = 0,0
result = []
while left_pointer < len(left_li) and right_pointer < len(right_li):
if left_li[left_pointer] < right_li[right_pointer]:
result.append(left_li[left_pointer])
left_pointer += 1
else:
result.append(right_li[right_pointer])
right_pointer += 1
result += left_li[left_pointer:] # result后追加左边剩余的部分
result += right_li[right_pointer:]
return result
if __name__ == "__main__":
alist = [54, 226, 93, 17, 77, 31, 44, 55, 20]
print(alist)
sorted_li = merge_sort(alist) #原先的列表并没有改变,需要一个返回值,返回值会改变
print(alist)
print(sorted_li)
最优时间复杂度:O(nlogn)
最坏时间复杂度:O(nlogn)
稳定性:稳定
常见排序算法效率比较
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(nlogn)~O(n^2) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn)~O(n) | 不稳定 |
搜索---二分查找
模仿查英语字典,一半一半来
要求:操作对象是排序过之后的,对象支持下标索引(顺序表:列表)---有序的顺序表
# coding:utf-8
def binary_search(alist,item):
"""二分查找,递归版本"""
n = len(alist)
if n > 0:
mid = n//2
if alist[mid] == item:
return True
elif item < alist[mid]:
return binary_search(alist[:mid],item)
else:
return binary_search(alist[mid+1:],item)
return False
def binary_search_2(alist,item):
"""二分查找,非递归版本"""
n = len(alist)
first = 0
last = n-1
while first <= last:
mid = (first + last)//2
if alist[mid] == item:
return True
elif item < alist[mid]:
last = mid - 1
else:
first = mid + 1
return False
if __name__ == "__main__":
alist = [17, 20, 31, 44, 54, 55, 77, 93, 226]
print(alist)
print(binary_search(alist,55))
print(binary_search(alist,100))
print(binary_search_2(alist, 55))
print(binary_search_2(alist, 100))
最优时间复杂度:O(1)
最坏时间复杂度:O(logn)