冒泡排序
两层循环
外层循环:遍历数组中待排序部分
内层循环:当前元素和它后面的元素比较大小,如果逆序就交换位置;下一个元素和它后面的元素比较大小,如果逆序就交换位置...直到结束
内层循环的排序过程,就是一个冒泡的过程,很形象,当内层排序结束后,最后一个元素就是已经排好序的元素,就像气泡冒出水面了。
数组:[5,1,3,4,2]
外层遍历,遍历数组待排序部分:
当前待排序部分是整个数组:
[【5,1,3,4,2】]
内层遍历,当前元素和后面的元素比较大小,如果逆序就交换位置
[(5,1),3,4,2] => [(1,5),3,4,2]
[1,(5,3),4,2] => [1,(3,5),4,2]
[1,3,(5,4),2] => [1,3,(4,5),2]
[1,3,4,(5,2)] => [1,3,4,(2,5)]
5已排好序,5之前的部分是待排序部分
当前待排序部分:
[【1,3,4,2】,/5/]
内层遍历,当前元素和后面的元素比较大小,如果逆序就交换位置
[(1,3),4,2,/5/] => [(1,3),4,2,/5/]
[1,(3,4),2,/5/] => [1,(3,4),2,/5/]
[1,3,(4,2),/5/] => [1,3,(2,4),/5/]
4已排好序,4之前的部分是待排序部分
...以此类推
时间复杂度:
最坏情况:
冒泡排序一共要进行(n-1)次循环,每一次循环都要进行当前n-1次比较
所以一共的比较次数是: (n-1) + (n-2) + (n-3) + … + 1 = n*(n-1)/2;
所以冒泡排序的时间复杂度是 O(n2)
最好情况:待排序数组本身就是正序的,一轮扫描即可完成排序,此时时间复杂度仅为比较时的时间复杂度,为O(n)
平均情况:O(n2)
空间复杂度:
就是在交换元素时那个临时变量所占的内存空间,最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为0,最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为O(n),平均的空间复杂度为O(1)
def bubble_sort(nums):
n = len(nums)
for i in range(n):
# 外层每结束一次循环,就有1个元素已排序
# i=0 没有已排序元素;i=1 1个已排序元素;i=2 2个已排序元素...
# 所以内层循环次数 = 元素个数-i
# 同时内层循环最后一次遍历,只需要遍历到倒数第二个元素,和倒数第一元素比较即可,所以内层循环次数 = 元素个数-i-1
for j in range(n-i-1):
if nums[j] > nums[j+1]:
nums[j],nums[j+1]=nums[j+1],nums[j]
return nums
快速排序
快速排序有种二分法+双指针+递归的感觉
选择一个基准值,设置两个指针left和right,left负责小于基准值的部分,right负责大于基准值的部分,将数组分成小于该值、大于等于该值两部分
再将这两部分当成一个新的数组,重复上面的步骤
直到最终两部分只有1个元素为止
具体流程:
数组= [4,3,5,7,6,1,2,4]
设置一个基准值,一般选择数组第一个元素即可
基准值:4
设置两个指针,left在头,right在尾
基准值:4
[4(l),3,5,7,6,1,2,4(r)]
因为left指向的元素就是基准值本身,所以我们先去比较right指向的元素
right也指向4,>=基准值,属于right的范围,不做改变,right向前移动
基准值:4
[4(l),3,5,7,6,1,2(r),4]
right指向2,<基准值,属于left的范围,将2赋值给left指针,left向后移动
基准值:4
[2,3(l),5,7,6,1,2(r),4]
left指向3,<基准值,属于left的范围,不做改变,left向后移动
基准值:4
[2,3,5(l),7,6,1,2(r),4]
left指向5,>=基准值,属于right的范围,将5赋值给right指针,right向前移动
基准值:4
[2,3,5(l),7,6,1(r),5,4]
right指向1,<基准值,属于left的范围,将1赋值给left指针,left向后移动
基准值:4
[2,3,1,7(l),6,1(r),5,4]
left指向7,>=基准值,属于right的范围,将7赋值给right指针,right向前移动
基准值:4
[2,3,1,7(l),6(r),7,5,4]
right指向6,>=基准值,属于right的范围,不做改变,right向前移动
基准值:4
[2,3,1,7(l,r),6,7,5,4]
此时left和right相遇,将该值改为基准值
基准值:4
[2,3,1,4(l,r),6,7,5,4]
此时指针的左半部分[2,3,1]都是小于基准值4的
指针的右半部分[6,7,5,4]都是大于等于基准值4的
这两个部分重新选额各自的基准值,重复上述快排步骤,直到递归结束
最终就将整个数组完成排序
时间复杂度:
- 最好情况:O(nlogn)
最好情况时,每次基准值都在在数组中间并且刚好将数组平分,经过logn次划分,就分出了左右两部分
每一次划分,都涉及到n个元素
所以总共时间为O(n)*O(logn)=O(nlogn) - 最坏情况:O(n^2)
最坏情况时,每次基准值都在数组开头,并且数组完全正序,那么就需要划分n次,才能分出左右两部分
每一次划分,都涉及到n个元素
所以总共时间为O(n)*O(n)=O(n^2) - 平均:O(nlogn)
空间复杂度:
最优的情况下空间复杂度为:O(logn);每一次都平分数组的情况
最差的情况下空间复杂度为:O( n );退化为冒泡排序的情况
def quickSort(nums,left,right):
# 递归结束条件:子数组长度被分割到只有1个元素是,此时left和right指针指向同一个元素,left和right的值相等
if left>=right:
return
# 递归执行快排操作
# 先对传递进来的数组,执行选取基准值、排出较小、较大两部分
mid = partition(nums,left,right)
# 再分别对 较小部分、较大部分,再次递归执行快排
quickSort(nums,left,mid-1)
quickSort(nums,mid+1,right)
return nums
def partition(nums,left,right):
# 设置数组第一个元素作为基准值
pivot = nums[left]
# 遍历元素,移动left、right指针,
while left < right:
# 此处必须先判断right部分,因为第一个left是基准值,相当于一个空位
# 如果先判断left部分的话,left先移动,那么基准值的那个空位就永远空着了
# 在left和right相遇之前,right指向的元素>=基准值,就是正常的,只向前移动right指针
while left < right and nums[right]>=pivot:
right-=1
# 否则,nums[right]<pivot,则将right的值赋值给left
nums[left] = nums[right]
# 在left和right相遇之前,left指向的元素<=基准值,就是正常的,只向后移动left指针
while left < right and nums[left]<=pivot:
left+=1
# 否则,nums[left]>pivot,则将left的值赋值给right
nums[right] = nums[left]
# 最后,当left=right时,left和right相遇,排序结束
# 将基准值放在该位置上
nums[left] = pivot
# 返回基准值的索引
return left
选择排序
将数组分为已排序部分和待排序部分
多次遍历数组的待排序部分
每次遍历,选出待排序部分的最小值,它和待排序部分的第一个元素交换位置,变成已排序部分
数组:[5,1,3,4,2]
开始时,整个数组都是待排序部分
[【5,1,3,4,2】]
待排序部分最小值为1,和待排序部分第一个元素5交换位置,1变成已排序
[1,【5,3,4,2】]
待排序部分最小值为2,和待排序部分第一个元素5交换位置,2变成已排序
[1,2,【3,4,5】]
待排序部分最小值为3,和待排序部分第一个元素3交换位置,3变成已排序
[1,2,3,【4,5】]
待排序部分最小值为4,和待排序部分第一个元素4交换位置,4变成已排序
[1,2,3,4,【5】]
此时待排序部分只有一个元素5,不需要排序了,就完成了全部排序
[1,2,3,4,5]
时间复杂度:O(n^2)
第一次内循环比较N - 1次,然后是N-2次,N-3次,……,最后一次内循环比较1次。
共比较的次数是 (N - 1) + (N - 2) + ... + 1,求等差数列和,得 (N - 1 + 1)* N / 2 = N^2 / 2。
舍去最高项系数,其时间复杂度为 O(N^2)。
def selectionSort(nums):
n = len(nums)
for i in range(n):
# 设置一个指针,保存待排序部分最小值的索引
min_index = i
# 遍历待排序部分,从i开始,i之前的是已排序部分
for j in range(i,n):
# 更新最小值的索引
if nums[j]<nums[min_index]:
min_index = j
# 将待排序部分的第一个元素,和最小值元素,交换位置
# 相当于将最小值放到已排序部分,待排序部分向后缩
nums[i],nums[min_index] = nums[min_index],nums[i]
return nums
插入排序
将数组分为已排序部分和待排序部分
开始时将数组的第一个元素当作已排序部分
遍历待排序部分的每个元素,与已排序部分的元素比较,插入到已排序部分中的正确位置上
数组:[5,1,3,4,2]
开始时,第一个元素5当作已排序部分,遍历待排序部分的元素
[/5/,(1),3,4,2]
待排序的1,小于已排序的5,将1放在5前面
[/1,5/,(3),4,2]
待排序的3,小于已排序的5,将3放在5前面
[/1,3,5/,(4),2]
待排序的3,大于已排序的1,将3放在1后面,即不改变位置
[/1,3,5/,(4),2]
待排序的4,小于已排序的5,将4放在5前面
[/1,3,4,5/,(2)]
待排序的4,大于已排序的3,将4放在3后面,即不改变位置
[/1,3,4,5/,(2)]
待排序的2,小于已排序的5,将2放在5前面
[/1,3,4,2,5/]
待排序的2,小于已排序的4,将2放在4前面
[/1,3,2,4,5/]
待排序的2,小于已排序的3,将2放在3前面
[/1,2,3,4,5/]
待排序的2,大于已排序的1,将2放在1后面,即不改变位置
[/1,2,3,4,5/]
排序完毕
时间复杂度:
- 最好情况:[1,2,3,4,5],遍历n-1次,移动0次,时间复杂度O(n)
- 最差情况:[5,4,3,2,1],遍历n-1次,移动1+2+...+n-1次,时间复杂度O(n^2)
- 平均:O(N^2)
def insertionSort(nums):
n = len(nums)
# 将数组第一个1元素当作已排序,之后的作为待排序
# 遍历待排序部分
for i in range(1,n):
# 用一个变量保存当前待排序元素,为之后插入操作,腾出插入空间
tmp = nums[i]
# i之前是已排序部分,遍历已排序部分,从后向前遍历
for j in range(i-1,0,-1):
# 如果 当前已排序元素nums[j] 大于 待排序元素nums[i]
# 则 已排序元素向后移,最后一个已排序元素后面就是待排序元素
# 之前已经用变量保存了待排序元素,所以不用担心待排序元素会丢失
if nums[j]>nums[i]:
nums[j+1]=nums[j]
# 如果 当前已排序元素nums[j] 小于 待排序元素nums[i]
# 则j+1是空位,将待排序元素插入j+1位置
else:
nums[j+1] = nums[i]
break
return nums
归并排序
先将数组不断向下二分,一直分到子数组只有一个元素不可再分为止。
然后开始向上排序合并子数组,用两个指针指向两个子数组,比较指针位置元素的大小,较小的元素先添加到数组中,并且移动该指针继续比较。
数组:[8,4,5,7,1,3,6,2]
先向下递归二分数组,直到子数组不可再分
[8,4,5,7] [1,3,6,2]
[8,4] [5,7] [1,3] [6,2]
[8][4][5][7] [1][3][6][2]
开始向上递归,通过两个指针比较元素大小,排序合并每层子数组
[8][4][5][7] [1][3][6][2]
[4,8] [5,7] [1,3] [2,6]
[4,5,7,8] [1,2,3,6]
[1,2,3,4,5,6,7,8]
指针排序过程:
例如
[4,8] [5,7]
两个子序列开头设置指针
[4(i),8] [5(j),7]
比较指针元素大小,较小的放入结果中,并向前移动该指针
[ ,8(i)] [5(j),7]
res=[4]
重复上述步骤
[ ,8(i)] [,7(j)]
res=[4,5]
[ ,8(i)] [ , ]
res=[4,5,7]
[ ,] [ , ]
res=[4,5,7,8]
时间复杂度:O(nlogn)
一个长度为n的序列,它的排序时间就等于,它二分后的2个长度为n/2的子序列的排序时间,加上它们的合并时间。
假设长度为n的序列,排序时间为T(n),那么长度为n/2的子序列,排序时间就为T(n/2),两个n/2的子序列合并,需要操作n个元素,所以T(n)的合并时间为n
所以,T(n)=2*T(n/2)+n
以此类推,
长度为n/2的子序列,它的排序时间T(n/2)=2*T(n/4)+n/2
长度为n/4的子序列,它的排序时间T(n/4)=2*T(n/8)+n/4
...
将T(n/4)带入到T(n/2),再将T(n/2)带入到T(n)
得到每一层T(n)
T(n)=2*T(n/2)+n
T(n)=4*T(n/4)+2n
T(n)=8*T(n/8)+3n
...
最后一层的子序列长度为1,所以最后一层的排序时间是T(1),用T(1)表示T(n)就是
logn表示:长度为n的数组,通过二分logn次,能达到不可分
T(n)=n*T(1)+(logn)*n
因为T(1)=0,所以T(n)=nlogn
def mergeSort(nums):
n = len(nums)
# 递归二分数组结束条件
# 当递归二分数组,分到不可分时,返回数组
if n==1:
return nums
# 找到数组中间位置
mid = n//2
# 递归二分数组
left = mergeSort(nums[:mid])
right = mergeSort(nums[mid:])
# 合并排序部分
tmp = []
# 当 左右数组 都不为空时,比较左右的第1个元素,较小的放入结果
while left and right:
if left[0]<right[0]:
tmp.append(left.pop(0))
else:
tmp.append(right.pop(0))
# 当 右数组为空时,左数组的元素从第1个开始,挨个放入结果
while left:
tmp.append(left.pop(0))
# 当 左数组为空时,右数组的元素从第1个开始,挨个放入结果
while right:
tmp.append(right.pop(0))
# 返回该层的合并排序结果
return tmp
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。