目录
时间和空间复杂度总结
选择排序
思想
选择+交换。在待排序列中,选出最小(最大)的一个数与第一个位置的数交换,然后在剩下数中,再找最小(最大)的数与第二个位置的数交换位置,依次类推,直到第N-1个元素与第N个元素交换位置,选择排序结束。
步骤
- 遍历数组,找到最小的数,把最小的数与第一个数位置交换;
- 对除第一个数的剩余数进行第二轮遍历,找到最小数,和第二个数位置交换。以此类推,直至进行比较的数只剩一个停止
模板代码
def selection_sort(arr):
for i in range(len(arr) - 1): #第一层循环表示循环选择的遍数
min = i #起始元素设为最小元素
for j in range(i + 1, len(arr)): #第二层循环表示min和后面的元素逐个比较
if arr[j] < arr[min] #如果当前元素比min小,则把当前元素下标标记为最小元素下标
min = j
arr[min], arr[i] = arr[i], arr[min] #查找一遍,将最小元素与起始元素互换
return arr
冒泡排序
思想
每一轮,从杂乱无章的数组头部开始,每两个元素比较大小并进行交换,直到这一轮当中最大或最小的元素被放置在数组的尾部,然后不断地重复这个过程,直到所有元素都排好位置。其中,核心操作就是元素相互比较
步骤
- 首先指针指向第一个数,比较第一个数和第二个数的大小,若当前数比下一个大则交换这两个数,若小则两者位置不变;
- 接下来指针往前移动一步,继续比较当前值和下一个值的大小,直到最大数在数组的最后面;
- 进行第二轮比较,把指针重新指向第一个元素,重复步骤1、2;
- 若没有交换元素,说明列表已排好序,结束。
模板代码
def bubble_sort(self, arr):
for i in range(len(arr) - 1): #外循环:i代表比较的轮次(由于每轮比较完,都会选出最大数,而下一轮最大数不需要在比较)
indicator = False #定义一个flag,用来标记每轮遍历中是否发生了交换
for j in range(len(arr) - 1 - i): #内循环:遍历数组所有元素,使最大值放到最后面
if arr[j] > arr[j + 1]: #若当前值比下一个大则交换这两个数
arr[j], arr[j+1] = arr[j+1], arr[j]
indicator = True #记录有交换发生
if not indicator: #如果没有交换说明列表已经有序,结束循环
break
插入排序
思想
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
步骤
- 首先将数组分成左右两部分,左边是已经排好序部分,右边是未排好序部分,刚开始左边已排好序部分只有第一个元素;
- 每次取右边未排好序部分第一个数,在已排序序列中从后向前依次比较。若右元素比左元素小,则将右元素插入早左元素前,交换位置;若大,保持位置不变;
- 找到相应位置并插入(经过每一轮的排序处理后,数组前端的数都是排好序的,区别于冒泡)
模板代码
def insertSort(self, nums):
#外循环用来遍历数组
for i in range(1,len(nums)): #将第一个元素当做已经排好序的,从第二个元素开始,即i从1开始遍历数组
temp = nums[i] #把当前i指向的值用temp保存,即插入值
#内循环用来将当前元素进行比较
for j in range(i,-1,-1): #从列表下标为0的元素开始,倒序取到下标为0的元素。j为当前位置
if temp < nums[j-1]: #如果当前位置的值小于比较的值,则将比较的值赋值给当前位置
nums[j] = nums[j-1]
else:
break
nums[j] = temp #当前的值插入到比它大的元素之前
return nums
快速排序(重点)
思想
快速排序使用分治法策略来把一个序列分为较小和较大的2个子序列,然后递归地排序两个子序列。(注意:快速排序是直接在原始数组里进行各种交换操作,所以当子数组被分割出来的时候,原始数组里的排列也被改变了)
步骤
-
选择基准值:从数列中挑出一个元素,即被比较数,称为"基准"(pivot),本文随机产生基准值(注:若固定基准值,当数组已经有序时,此时每次划分只能使待排数组减一,沦为冒泡排序)
-
分割操作:从头开始遍历序列,将所有比pivot值小的元素放在pivot前面,所有比pivot值大的元素放在pivot后面(与pivot值相等的数可以到任何一边,本示例中放在了前面);
-
递归排序:将"基准"元素左边的序列和"基准"元素右边的序列进行partition操作;直到所有子序列的元素只有一个时,结束。
模板代码
#分组函数:选择基准值 pivot 将数组分成两个子数组,≤该元素的值放在左子数组,>该元素的值放在右子数组
def partition(self, arr, low, high): #arr: 列表;low: arr的第一个索引0;high: arr的最后一个索引
rand = random.randint(low, high) #随机取一个作为基准值
i = low # 最小元素索引
pivot = arr[rand] # 随机选择一个数作为基准值,我们把列表中的所有元素同它比较
#从左到右用每个数和基准值比较,若比基准值小,则放到指针i所指向的位置。循环完毕后,i 指针之前的数都比基准值小
for j in range(low, high): #遍历整个数组,j为数组的每一个元素
if arr[j] <= pivot:
arr[i], arr[j] = arr[j], arr[i] #≤除了基准值的所有元素依次放在左边索引0~i的位置(交换两元素值)
i = i + 1 #比pivot大的元素索引值不变,即i不加1
arr[i], arr[rand] = arr[rand], arr[i] #基准值放置到指针 i 的位置,i 指针之后的数都比基准值大
return i #返回指针i,作为基准点的位置
# 快速排序主体函数
def quickSort(arr, low, high):
if low < high: #如果列表有1个以上的元素
pi = partition(arr, low, high) #利用partition函数找到一个随机的基准点
#递归的对基准点左半边和右半边的数进行排序
quickSort(arr, low, pi - 1)
quickSort(arr, pi + 1, high)
归并排序(重点)
思想
核心是分治(拆分、合并),就是把一个复杂的问题分成两个或多个相同或相似子问题,然后把子问题分成更小子问题,直到子问题可以简单的直接求解,原问题的解就是子问题解的合并。
步骤
- 将一个序列从中间位置拆分成两个子序列,递归不断的对子序列继续二分下去,直到所有的子序列长度都为1
- 按照大小顺序合并两个元素,依次按照递归的返回顺序,两两合并成有序序列,直到最后把整个数组的顺序排好;
模板代码
#主体函数
def merge_sort(self, lists):
if len(lists) <= 1:
return lists
mid = len(lists) // 2 #将列表分成更小的两个列表
#分别对左右两个列表进行处理,分别返回两个排好序的列表
left = merge_sort(lists[:mid])
right = merge_sort(lists[mid:])
return merge(left, right)
#对排序好的两个列表合并,产生一个新的排序好的列表(递归:压栈、出栈的过程;其中压栈的值暂时存在栈中)
#归并操作
def merge(self, left, right):
#合并两个已排好序的列表,产生一个新的已排序好的列表
result = [] #新的已排序好的列表
i = 0 #i指针表示左列表的起始位置
j = 0 #j指针表示右列表的起始位置
# 对两个列表中的元素两两对比;将最小的元素,放到result中,并对当前列表下标加1
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
# 当两个列表中的其中一个指针走完了,则剩余的列表直接追加到result(由于剩下的都是有序的)
result += left[i:]
result += right[j:]
return result
例题:LeetCode88题