注:本文主要目的是总结网上的资料,方便自己复习,非原创,参考链接见文末。
冒泡排序
算法描述步骤1: 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
步骤2: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
步骤3: 针对所有的元素重复以上的步骤,除了最后一个;
步骤4: 重复步骤1~3,直到排序完成。
算法分析最佳情况:T(n) = O(n)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
def bubleSort(array):
size=len(array)
if size<=1:
return array
for i in range(size):
for j in range(0,size-1-i):
if array[j]>array[j+1]:
array[j],array[j+1]=array[j+1],array[j]
return array
选择排序
算法描述:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
算法分析:最佳情况:T(n) = O(n2)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
def selectionSort(array):
size=len(array)
if size<=1:
return array
for i in range(size):
min_index=i
for j in range(i+1,size):
#扫描出未排序数组中最小的元素,保存其下标
if array[j]
min_index=j
array[i],array[min_index]=array[min_index],array[i]
return array
插入排序
算法描述:步骤1: 从第一个元素开始,该元素可以认为已经被排序;
步骤2: 取出下一个元素,在已经排序的元素序列中从后向前扫描;
步骤3: 如果该元素(已排序)大于新元素,将该元素移到下一位置;
步骤4: 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
步骤5: 将新元素插入到该位置后;
步骤6: 重复步骤2~5。
算法分析:最佳情况:T(n) = O(n)
最坏情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
def insertSort(array):
size = len(array)
if size <= 1:
return array
for i in range(size):
j=i
target=array[i]
# 只要前面的元素大于当前元素,元素位置后移
while j-1>=0 and array[j-1]>target:
array[j]=array[j-1]
j-=1
array[j]=target
return array
希尔排序
算法描述:
希尔排序是插入排序的高效实现,将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序。步骤1:选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
步骤2:按增量序列个数k,对序列进行k 趟排序;
步骤3:每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
算法分析:最佳情况:T(n) = O(nlog2 n)
最坏情况:T(n) = O(nlog2 n)
平均情况:T(n) =O(nlog2n)
def shellSort(array):
size = len(array)
if size <= 1:
return array
gap=size//2
while gap>0:
#增量序列
for i in range(gap,size):
j=i
target=array[i]
while j>0 and array[j-gap]>target:
#分成gap个组,分组排序和插入
array[j]=array[j-gap]
j-=gap
array[j]=target
#当gap=1时整个数组为一组
gap//=2
return array
归并排序
算法描述:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
归并排序使用了分治法的思想。步骤1:把长度为n的输入序列分成两个长度为n/2的子序列;
步骤2:对这两个子序列分别采用归并排序;
步骤3:将两个排序好的子序列合并成一个最终的排序序列。
算法分析:最佳情况:T(n) = O(n)
最差情况:T(n) = O(nlogn)
平均情况:T(n) = O(nlogn)
def mergeSort(array):
def merge(left,right):
res=[]
i,j=0,0
while i
if left[i]<=right[j]:
res.append(left[i])
i+=1
else:
res.append(right[j])
j += 1
res.extend(left[i:] if i
return res
def sort(array):
if len(array)<=1:
return array
l,r=0,len(array)
mid=len(array)//2
# 先递归拆分左边序列,归并和排序,再递归拆分右边序列
return merge(sort(array[l:mid]),sort(array[mid:r]))
size = len(array)
if size <= 1:
return array
return sort(array)
快速排序
算法描述:
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:步骤1:从数列中挑出一个元素,称为 “基准”(pivot );
步骤2:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
步骤3:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
算法分析:最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(nlogn)
看了一些博客的代码和GIF图,会出现GIF图跟代码不匹配的情况(我第一次看也很懵)。实际上快排算法是有不同的实现方式的。其基本思想是使用两个指针来遍历待排序的数组,根据两个指针遍历的方向可以分为两类:第一,两个指针从待排序数组的同一个方向遍历。第二,两个指针分别从待排序数组的两端进行遍历。
从同一方向遍历时的状态。在循环中,low指向的是小于枢纽的那部分元素的最右端,high在未排序的部分遍历。循环直到high指针到达right的位置(数组的最右端),如下图第二个图所示。此时数组除了枢纽pivot被划分为两部分:小于pivot的和大于等于pivot的。然后将low和枢纽元素交换就可以得到本次排序的第一趟结果:
def quickSort(array):
def partition(array,left,right):
pivot=right # 默认为最后一个为基准数
low=left-1
high=left
while high
# 一旦找到比pivot小的元素,就把low往右移动一位。
# 在每次交换后,low所指的都是已经遍历的、小于枢纽的一系列元素的最右边一个(low的右边下一个元素就大于枢纽了),
# 因此high发现了小于等于枢纽的元素后,交换low和high所指元素,
# 即可将low所指的大元素放到数组右侧,而把high所指的小元素放到数组左侧
if array[high]<=array[pivot]:
low+=1
array[low],array[high]=array[high],array[low]
high+=1
array[low+1],array[pivot]=array[pivot],array[low+1]
return low+1
def sort(array,left,right):
if left
mid=partition(array,left,right)
sort(array,left,mid-1)
sort(array,mid+1,right)
return array
size = len(array)
if size <= 1:
return array
sort(array,0,size-1)
return array
当从两个方向分别遍历数组时,下图是遍历的循环过程状态。循环时,low向右移动,high向左移动。
def quickSort(array):
def partition(array,left,right):
pivot=left # 默认为第一个数为基准数,可优化
while left
# 注意两个while循环的顺序和pivot的选择,先右后左,保证最终left的值小于pivot
# 当pivot=right时,循环位置反过来
# 如果列表后边的数,比基准数大或相等,则前移一位直到有比基准数小的数出现
while left < right and array[right] >= array[pivot]:
# 这里是>=不能使>,否则当数组元素等于枢纽时会死循环
right -= 1
# 如果列表前边的数,比基准数小或相等,则后移一位直到有比基准数大的数出现
while left < right and array[left] <= array[pivot]:
left += 1
# 如果列表后边的数,比基准数大或相等,则前移一位直到有比基准数小的数出现
# 此时已找到一个比基准大的书,和一个比基准小的数,将他们互换位置
array[left],array[right]=array[right],array[left]
# 当从两边分别逼近,直到两个位置相等时结束,将左边小的同基准进行交换
array[left],array[pivot]=array[pivot],array[left]
return left
def sort(array,left,right):
if left
mid=partition(array,left,right)
#print(array)
sort(array,left,mid-1)
sort(array,mid+1,right)
return array
size = len(array)
if size <= 1:
return array
sort(array,0,size-1)
return array
堆排序
算法描述:
利用堆这种数据结构而设计的一种排序算法。
堆是具有以下性质的完全二叉树:
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
基本步骤:将无序数组构建成堆,根据升序降序需求选择大顶堆小顶堆
将堆顶元素与末尾元素交换,将最大/最小元素放在数组末尾
调整结构满足堆定义
综上,反复执行调整堆结构和交换元素的步骤。
算法分析:
堆排序的初始建堆过程比价复杂,对O(n)级别个非叶子节点进行堆调整操作O(logn),时间复杂度O(nlogn);之后每一次堆调整操作确定一个数的次序,时间复杂度O(nlogn)。合起来时间复杂度O(nlogn)。最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(nlogn)
平均情况:T(n) = O(nlogn)
def heapSort(array):
def heapAdjust(array,root,end):
# 将以root为根节点的堆调整为大顶堆
temp=array[root]
son=2*root+1
while son<=end:
if son
son+=1
if temp>=array[son]: #r 如果是大顶堆,打破循环
break
array[root]=array[son] # 交换元素之后继续向下调整
root=son
son=2*son+1
array[root]=temp #将原堆顶插入正确位置
n=len(array)
if n<=1:
return array
# 建立大顶堆
root=n//2-1 # 完全二叉树中最后一个非叶子节点
while root>=0:
heapAdjust(array,root,n-1)
root-=1
# 将堆顶元素和数组最后一个元素交换,再调整堆结构
i=n-1
while i>=0:
array[0],array[i]=array[i],array[0] # 堆顶放在数组末
heapAdjust(array,0,i-1) # 调整剩余元素
i-=1
return array
排序算法总结稳定 :如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定 :如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序 :所有排序操作都在内存中完成;
外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度 : 一个算法执行所耗费的时间。
空间复杂度 :运行完一个程序所需内存的大小。
参考资料