先看一个表:
零:小结
1、稳定排序和不稳定排序
稳定:冒泡、插入、归并和基数。
不稳定:选择、快速、希尔、堆。
1. 稳定排序:排序前后,相同元素的相对位置不变
Bubble Sort: 只比较相邻元素,相同就不换。
Insertion Sort:有序小序列中逐个插入元素,从小序列末端开始比较,这样元素的相对位置是不变的。
Merge Sort:大小不变不换,相对位置不变
基数排序:低位先排,再收集,直到高位。稳定。
2. 不稳定排序
Selection Sort:每个位置选当前元素最小的,这样元素排序依靠相对位置,
Quick Sort:快排会打乱相同元素顺序(反过来)
Hash Sort
Heap Sort:父节点选择元素会破坏稳定性,父节点交换会打乱顺序
2、时间复杂度:
平方阶 (O(n^2)) :冒泡、选择、插入
线性对数阶 (O(nlogn)): 归并、快速、堆
特殊的希尔 O(n^(1.3—2))
线性(O(n)) : 计数、桶、基数
3、基于比较的排序,和不基于比较的排序
基于比较的排序:冒泡、选择、插入、希尔、归并、快排、堆排序
不基于比较的排序:计数、桶、基数
桶排序、计数排序、基数排序:
1、与排序的样本的实际数据状况很有关系,所以实际中并不常用
2、时间复杂度:O(N),空间复杂度O(N)
3、稳定的排序
4、注意:
工程中排序算法的注意点:
基础类型:int,double,float,char,short,用快速排序(不用管稳定与否)
是自己定义的,用归并排序(需要考虑排序的稳定性)
如果数组长度短,用插入排序(常数项指标低)
有关排序问题的补充:
1、归并排序的额外空间复杂度可以变成O(1),但是非常难,不需要掌握,可以搜“归并排序 内部缓存法”
2、快速排序可以做到稳定性问题,但是非常难,不需要掌握,可以搜“01 stable sort”
3、有道题,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,要求时间复杂度为O(N),空间复杂度为O(1),做不到的。
一、冒泡排序
1、思想:
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
进行len - 1次冒泡
第k次冒泡将倒数第k个元素排好序
时间复杂度 :最坏:O(N^2),最好:O(N),平均:O(N^2)
空间复杂度为O(1)
稳定的排序
2、算法步骤
算法步骤:
1、比较相邻的元素,如果第一个比第二个大,就交换他们两个
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。做完后,最大的元素在最后面
3、针对所有的元素重复上面的步骤,除了最后一个
4、不断对每次越来越少的元素重复上面的步骤,直到不需要比较
3、代码实现:
代码1:
def bubbleSort(nums): # 时间复杂度 O(N^2)
if not nums or len(nums) < 2:
return
n = len(nums)
for i in range(n-1,-1,-1):
for j in range(i):
if nums[j] > nums[j + 1]:
nums[j],nums[j + 1] = nums[j + 1],nums[j]
代码2:
def bubbleSort(nums): # 时间复杂度 O(N^2)
if not nums or len(nums) < 2:
return
n = len(nums)
for i in range(1,n):
for j in range(n - i):
if nums[j] > nums[j+1]:
nums[j],nums[j+1] = nums[j+1],nums[j]
二、选择排序
1、思想
思想:
维护数组的左边是有序的,在未排序的序列中找最小元素,放在有序序列的末尾
时间复杂度 O(N^2)
空间复杂度:O(1)
不稳定的排序
性能不受数据的影响
2、算法步骤
算法步骤:
1、首先在未排序序列中找最小值,放在排序序列的开始位置
2、再从未排序的序列中找最小值,放在排序序列的末尾(通过交换来实现)
3、重复第二步,直到所有元素均排序完毕
3、代码实现
def selectionSort(nums): # 时间复杂度 O(N^2)
if not nums or len(nums) < 2:
return
n = len(nums)
for i in range(n-1):
temp = i
for j in range(i+1,n):
if nums[j] < nums[temp]:
temp = j
nums[i],nums[temp] = nums[temp],nums[i]
三、插入排序
1、思想
思想:
左边的有序的序列,对未排序的序列中的首元素,在有序序列中扫描,找到相应位置,插入
升序,时间复杂度 O(N);降序,时间复杂度 O(N^2);平均情况:O(N^2)
空间复杂度:O(1)
稳定的排序
2、算法步骤
算法步骤:
1、将待排序的第一个元素看做有序序列,把第二个元素到最后一个元素看做未排序序列
2、从头到尾依次扫描未排序的序列,将扫描到的每个元素插入到有序序列中的适当位置。(如果待插入的元素和
有序序列中的某个元素相等,就插入到期后面)
3、代码实现
def insertSort(nums): # 升序,时间复杂度 O(N);降序,时间复杂度 O(N^2);平均情况:O(N^2)
if not nums or len(nums) < 2:
return
n = len(nums)
for i in range(1,n):
for j in range(i - 1,-1,-1):
if nums[j] > nums[j + 1]:
nums[j],nums[j + 1] = nums[j + 1],nums[j]
四、希尔排序
1、思想
思想:
也叫递减增量排序算法,是插入排序的一种改进版本。
先将整个待排序的记录序列分割成若干个子序列分别进行直接插入排序,待整个序列中的记录
“基本有序”时,再对全体记录进行依次直接插入排序
插入排序的缺点:
1、插入排序在对几乎排好序的数据操作,效率高,可以达到线性的效率
2、但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
时间复杂度:最好:O(N(logN)^2),最坏:O(N(logN)^2),平均:O(NlogN)
空间复杂度:O(1)
不稳定排序
2、算法步骤
算法步骤:
1、选择一个增量序列t1,t2,...,tk,其中ti > tj,tk = 1
2、按增量序列个数k,对序列进行k趟排序
3、每趟排序,根据对应的增量ti,将待排序序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序
仅增量因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度
3、代码实现
def shellSort(nums):
import math
if not nums or len(nums) < 2:
return
gap = 1
n = len(nums)
while gap < n / 3:
gap = gap * 3 + 1
while gap > 0:
for i in range(gap,n):
for j in range(i-gap,-1,-1):
if nums[j] > nums[j + 1]:
nums[j],nums[j+1] = nums[j+1],nums[j]
gap = math.floor(gap/3)
或者:
def shellSort(arr):
import math
gap = 1
while (gap < len(arr) / 3):
gap = gap * 3 + 1
while gap > 0:
for i in range(gap, len(arr)):
temp = arr[i]
j = i - gap
while j >= 0 and arr[j] > temp:
arr[j + gap] = arr[j]
j -= gap
arr[j + gap] = temp
gap = math.floor(gap / 3)
五、归并排序
1、思想
思想:
分治思想(Divide and Conqueer)
先分,不断的分成两段小的序列,分到序列的长度为1,然后合并(此时两个序列是有序的,相当于合并两个有序的序列)
时间复杂度:O(NlogN)
空间复杂度:O(N)
稳定的排序
性能不受数据的影响
2、算法步骤
算法步骤:
1、在sortProcess()中,进行二分,然后递归左边,递归右边的,每次都能得到一个有序的序列,然后合并
2、合并,即将数组中左右有序序列合并成一个有序序列
申请一个数组,大小为(r-l+1),然后有两个指针分别指向前面和后面有序序列的首位置
循环进行把小的元素放在新数组中
然后把剩下的元素放在新数组中
然后遍历,再把新数组放在原数组中
3、代码实现
实现1:
class Solution:
def mergeSort(self,nums): #归并排序
if not nums or len(nums) < 2:
return
self.sortProcess(nums,0,len(nums) - 1)
def sortProcess(self,nums,l,r):
if l == r:
return
mid = l + ((r - l) >> 1)
self.sortProcess(nums,l,mid)
self.sortProcess(nums,mid + 1,r)
self.merge(nums,l,mid,r)
def merge(self,nums,l,mid,r):
temp = [0] * (r - l + 1)
i = 0
p1,p2 = l,mid + 1
while p1 <= mid and p2 <= r:
if nums[p1] < nums[p2]:
temp[i] = nums[p1]
p1 += 1
else:
temp[i] = nums[p2]
p2 += 1
i += 1
# temp[i:] = nums[p1:] if p1 <= mid else nums[p2:]
while p1 <= mid:
temp[i] = nums[p1]
i += 1
p1 += 1
while p2 <= r:
temp[i] = nums[p2]
i += 1
p2 += 1
for i in range(len(temp)):
nums[l+i] = temp[i]
实现2:
def merge_sort(nums):
n = len(nums)
if n <= 1:
return nums
mid = n // 2 #分解
left_nums = merge_sort(nums[:mid])
right_nums = merge_sort(nums[mid :])
res = merge(left_nums,right_nums)#合并
return res
def merge(left_nums,right_nums):
#初始化
if not left_nums or not right_nums:
return
n1 = len(left_nums)
n2 = len(right_nums)
res = [0] * (n1 + n2)
# print(aa,nums)
i,j,k = 0,0,0
while i < n1 and j < n2 :
if left_nums[i] <= right_nums[j]:
res[k] = left_nums[i]
i += 1
else:
res[k] = right_nums[j]
j += 1
k += 1
res[k:] = left_nums[i:] if i < n1 else right_nums[j:]
return res
if __name__ == '__main__':
# x = [3,6,8,9,7,8,5,2,3,9,8,41,25,16,25,78,96,34,58]
x = [6,2,3,4]
print(merge_sort(x))
print(x)
4、应用:
归并排序:牛客-剑指offer系列题解----数组中的逆序对
归并排序的改版:处理数组的在前面的数小于当前的数的之和问题,也即小和问题
链表 | 快速排序 | 归并排序:力扣148. 排序链表
六、快速排序
1、思想
思想:
采用分治法(Divide and Conquer)
先做划分数组,做partition分区,即拆分为比这个元素小的部分和比这个元素大的部分
然后对小的部分和大的部分分别进行递归快排
时间复杂度:最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),
且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。
所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
空间复杂度:O(logN)
不稳定的排序
2、算法步骤
算法步骤:
1、从数列中挑出一个元素,称为 “基准”(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;可以看荷兰国旗问题
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
递归结束:l == r
3、代码实现
实现1:
def qsort(nums):
if not nums or len(nums) < 2:
return
quickSort(nums, 0, len(nums) - 1)
def quickSort(nums,l,r):
if l < r :
p = partition(nums,l,r)
quickSort(nums,l,p[0] - 1)
quickSort(nums,p[1] + 1,r)
def partition(nums,l,r):
left = l - 1
right = r
while l < right:
if nums[l] < nums[r]:
nums[left+1],nums[l] = nums[l],nums[left + 1]
left += 1
l += 1
elif nums[l] > nums[r]:
nums[right - 1] ,nums[l] = nums[l],nums[right - 1]
right -= 1
else :
l += 1
nums[right],nums[r] = nums[r],nums[right]
return [left + 1,right]
实现2:改进,随机快速排序
import random
def qsort(nums):
if not nums or len(nums) < 2:
return
quickSort(nums, 0, len(nums) - 1)
def quickSort(nums,l,r):
if l < r :
p = partition(nums,l,r)
quickSort(nums,l,p[0] - 1)
quickSort(nums,p[1] + 1,r)
def partition(nums,l,r):
random_index = random.randint(l,r)
nums[r],nums[random_index] = nums[random_index],nums[r]
left = l - 1
right = r
while l < right:
if nums[l] < nums[r]:
nums[left+1],nums[l] = nums[l],nums[left + 1]
left += 1
l += 1
elif nums[l] > nums[r]:
nums[right - 1] ,nums[l] = nums[l],nums[right - 1]
right -= 1
else :
l += 1
nums[right],nums[r] = nums[r],nums[right]
return [left + 1,right]
4、应用
链表 | 快速排序 | 归并排序:力扣148. 排序链表
分治思想–快速排序:力扣215. 数组中的第K个最大元素
七、堆排序
1、思想:
思路:
利用堆这种数据结构设计的排序算法。堆可以看做是一个完全二叉树,子结点的键值或者索引总是小于(或大于)它的父节点
大根堆(大顶堆):每个节点的值都 >= 其子结点的值,可以用来升序排列
小根堆(小顶堆):每个节点的值都 <= 其子结点的值,可以用来降序排列
时间复杂度 :O(NlogN)
空间复杂度:O(1)
不稳定的排序
2、算法步骤
算法步骤:
1、将待排序的序列构建成一个堆H[0,1...,N-1],根据升序、降序需求,选择大根堆或小根堆
2、把堆首(最大值)和堆尾交换
3、把堆的尺寸缩小1,并调用heapify,目的是把新的数组顶端的数据调整到合适位置
4、重复步骤2,知道堆尺寸为1
heapInsert()建堆:
不断的使较大值上浮。
不断的与父节点比较,如果比父节点大,就交换,并改变指针指向新父节点;
直到不比父节点大,注意处理边界。
hepify()调整堆:
不断使较小值下沉。
让根节点与子结点中较大的结点进行交换,直到不能交换;
注意指针不能大于堆长度。
3、代码实现
class heapSort:
def heapSort(self,nums):
if not nums or len(nums) < 2:
return
for i in range(len(nums)): #建大根堆
self.heapInsert(nums,i) #0~i
heapSize = len(nums) - 1
nums[0],nums[heapSize] = nums[heapSize],nums[0]
while heapSize > 0:
self.heapify(nums,0,heapSize ) #调整大根堆
heapSize -= 1
nums[0],nums[heapSize] = nums[heapSize],nums[0]
def heapify(self,nums,index,heapSize):#,调整大根堆,表示下沉操作,(大根堆)把小的往下移动;heapSize为堆的长度
left = index * 2 + 1
while left < heapSize:
#找两个孩子中最大的那个
largest = left + 1 if left + 1 < heapSize and nums[left + 1] > nums[left] else left
#令largest为 根节点和两个孩子的最大值的下标
largest = largest if nums[largest] > nums[index] else index
if largest == index:
break
nums[largest],nums[index] = nums[index],nums[largest] #根节点与两个孩子中最大的那个交换
index = largest
left = index * 2 + 1
def heapInsert(self,nums,index): #建堆的时候,上浮操作,(大根堆)把大的往上移动 index为堆中的索引
while index > 0 and nums[index] > nums[(index - 1) // 2]:#如果比父节点大,就上浮
nums[index],nums[(index - 1) // 2] = nums[(index - 1) // 2],nums[index]
index = (index - 1) // 2