顺序查找Sequential Search
对于无序表
def sequentialSearch(alist,item):
pos = 0
found = False
while pos < len(alist) and not found:
if alist[pos] == item:
found = True
else:
pos = pos + 1#下标顺序增长
return found
testlist = [1,2,32,8,17,19,42,13,0]
print(sequentialSearch(testlist,3))
print(sequentialSearch(testlist,13))
终于开始用pycharm了真香😊
对于有序表,当数据项不存在时比对可以提前结束
二分查找算法及分析
有序法依然改变不了数量级
与中间项比对,每次都会将比对范围缩小一半
def binarySearch(alist,item):
first = 0
last = len(alist)-1
found = False
while first<=last and not found:
midpoint = (first + last)//2
if alist[midpoint] == item:
found = True
else:
if item < alist[midpoint]:
last = midpoint - 1
else:
first = midpoint + 1
return found
testlist = [0,1,2,8,13,17,19,32,42]
print(binarySearch(testlist,3))
print(binarySearch(testlist,13))
递归
def binarySearch(alist,item):
if len(alist) == 0:
return False
else:
midpoint = len(alist)//2
if alist[midpoint] == item:
return True
else:
if item<alist[midpoint]:
return binarySearch(alist[:midpoint],item)
else:
return binarySearch(alist[midpoint+1:],item)
testlist = [0,1,2,8,13,17,19,32,42]
print(binarySearch(testlist,3))
print(binarySearch(testlist,13))
如果把第一个条件结构去掉
基本结束条件就没有路径能返回False
或者可以把len(alist)==1时item是不是等于列表当中的唯一项
算法分析:
当比对次数足够多以后,比对范围内就会仅剩余1个数据项
所以二分法查找的算法复杂的是O(log n)但,
这个递归调用使用了列表切片,而切片操作的复杂度是O(k)
总的来说是优于顺序查找的
但,
还要考虑对数据项进行排序的开销
所以,适合于排序一次查找多次的情况
冒泡排序Bubble Sort
冒泡排序的算法思路在于对无序表进行多趟比较交换,每趟包括了多次两两相邻比较,并将逆序的数据项互换位置,最终能将本趟的最大项就位
经过N-1趟比较交换,实现整表排序
每趟的过程类似于“气泡”在水中不断上浮到水面的经过
def bubbleSort(alist):
for passnum in range(len(alist)-1,0,-1):
for i in range(passnum):
if alist[i]>alist[i+1]:
temp = alist[i]
alist[i] = alist[i+1]
alist[i+1] = temp
#其实python里面可以作直接交换alist[i],alist[i+1]=alist[i+1],alist[i]
alist = [54,26,93,17,77,31,44,55,20]
bubbleSort(alist)
print(alist)
算法分析:
比对的次数是固定的是1~n-1的累加
交换的时间复杂度也是O(n^2)最差就是每次比对都要交换,交换次数就等于比对次数
冒泡排序差在哪?大部分比对和交换的操作是无效的。
但有一点优势,无需任何额外的存储空间开销。
性能改进:监测每趟比对是否发生过交换,如果某趟比对没有发生任何交换,说明列表已经排好序,可以提前结束算法
def shortBubbleSort(alist):
exchanges = True
passnum = len(alist)-1
while passnum > 0 and exchanges:
exchanges = False
for i in range(passnum):
if alist[i]>alist[i+1]:
exchanges = True
temp = alist[i]
alist[i] = alist[i+1]
alist[i+1] = temp
passnum = passnum-1
alist=[20,30,40,90,50,60,70,80,100,110]
shortBubbleSort(alist)
print(alist)
选择排序Selection Sort
选择排序对冒泡排序进行了改进,每趟仅进行1次交换,记录最大项所在位置,最后再跟本趟最后一项交换O(n)
def selectionSort(alist):
for fillslot in range(len(alist)-1,0,-1):
positionOfMax=0
for location in range(1,fillslot+1):
if alist[location]>alist[positionOfMax]:
positionOfMax = location
temp = alist[fillslot]
alist[fillslot] = alist[positionOfMax]
alist[positionOfMax] = temp
alist=[20,30,40,90,50,60,70,80,100,110]
selectionSort(alist)
print(alist)
插入排序Insertion Sort
时间复杂度仍是O(n^2),但算法思路不同
插入排序维持一个已排好序的子列表,其位置始终在列表的前部,然后逐步扩大这个子列表直到全表
第一趟,子列表仅包含第1和数据项,将第二个数据项作为“新项”插入到子列表的合适位置中,这样已排序的子列表就包含了2个数据项
第二趟,再继续将第3个数据项跟前2个数据项比对,并移动比自身大的数据项,空出位置来,以便加入到子列表中
经过n-1趟比对和插入,子列表扩展到全表,完成排序
分析:最差的情况是每趟都与子列表的所有项进行比对,总比对次数与冒泡排序相同,数量级仍是O(n^2)
思路:比对,移动所有比“新项”大的数据项
def insertionSort(alist):
for index in range(1,len(alist)):
currentvalue = alist[index]
position = index
while position>0 and alist[position-1]>currentvalue:
alist[position]=alist[position-1]
position = position-1
alist[position]=currentvalue
alist=[20,30,40,90,50,60,70,80,100,110]
insertionSort(alist)
print(alist)
由于移动操作仅包含1次赋值,是交换操作的1/3,所以插入排序性能会较好一些
谢尔排序Shell Sort
列表越接近有序,插入排序的比对次数就越少
谢尔排序以排序作为基础,对无序表进行“间隔”划分子列表,每个子列表都执行插入排序
def shellSort(alist):
sublistcount = len(alist)//2
while sublistcount > 0:
for startposition in range(sublistcount):
gapInsertionSort(alist,startposition,sublistcount)
print("After increments of size",sublistcount,
"The list is",alist)
sublistcount = sublistcount // 2
def gapInsertionSort(alist,start,gap):
for i in range(start+gap,len(alist),gap):
currentvalue = alist[i]
position = i
while position>=gap and alist[position-gap]>currentvalue:
alist[position]=alist[position-gap]
position = position-gap
alist[position]=currentvalue
alist=[20,30,40,90,50,60,70,80,100,110]
shellSort(alist)
print(alist)
归并排序Merge Sort
def merge_sort(lst):
#递归结束条件
if len(lst) <= 1:
return lst
#分解问题,并递归调用
middle = len(lst) // 2
left = merge_sort(lst[:middle])#左半部排好序
right = merge_sort(lst[middle:])#右半部排好序
#合并左右半部,完成排序
merged = []
while left and right:
if left[0] <= right[0]:
merged.append(left.pop(0))
else:
merged.append(right.pop(0))
merged.extend(right if right else left)
return merged
分裂过程O(log n)
归并过程O(n)
快速排序算法Quick Sort
思路:依据一个“中值”数据项来把数据表分为两半:小于中值的一半和大于中值的一半,然后每部分分别进行快速排序(递归)
递归三要素
基本结束条件:数据表仅有1各数据项,自然是好排序的
缩小规模:根据”中值“,将数据表分为两半,最好情况是相等规模的两半
调用自身:将两半分别调用自身进行排序(排序基本操作在分裂过程中)
分裂数据表的目标:找到”中值“位置
分裂数据表的手段:
设置左右标
左标向右,右标向左移动
- 左标一直向右移动,碰到比中值大的就停止
- 右标一直向左移动,碰到比中值小的就停止
然后把左右标所指的数据项交换继续移动,直到左标移到右标的右侧,停止移动
这时右标所指位置就是”中值“应处的位置
将中值和这个位置交换
分裂完成,左半部比中值小,右半部比中值大
def quickSort(alist):
quickSortHelper(alist,0,len(alist)-1)
def quickSortHelper(alist,first,last):
#基本结束条件
if first<last:
splitpoint = partition(alist,first,last)#分裂
quickSortHelper(alist,first,splitpoint-1)
quickSortHelper(alist,splitpoint+1,last)#递归调用
def partition(alist,first,last):#选定"中值"
pivotvalue = alist[first]
leftmark = first+1
rightmark = last#左右标初值
done = False
while not done:
while leftmark <= rightmark and \
alist[leftmark] <= pivotvalue:#向右移动左标
leftmark = leftmark + 1
while alist[rightmark] >= pivotvalue and \
rightmark >=leftmark:#向左移动右标
rightmark = rightmark - 1
if rightmark < leftmark:#两标向错就结束移动
done = True
else:#左右标的值交换
temp = alist[leftmark]
alist[leftmark] = alist[rightmark]
alist[rightmark] = temp
temp = alist[first]
alist[first] = alist[rightmark]
alist[rightmark] = temp#中值就位
return rightmark#中值点,也是分裂点
alist=[54,26,93,17,77,31,44,55,20]
quickSort(alist)
print(alist)
分析:
快速排序过程分为两部分:分裂和移动
如果分裂总能把数据表分为相等的两部分,那么就是O(log n)的复杂度
而移动需要将每项都与中值进行比对,还是O(n)
综合起来就是O(nlog n )
而且,算法运行过程中不需要额外的存储空间
但是,如果中值所在的分裂点过于偏离中部造成左右两部分数量不平衡
极端情况,有一部分没有数据,这样时间复杂度就退化到O(n^2)
还要加上递归调用的开销(比冒泡排序还要糟糕)
改进:
适当改进中值的选取方法