文章目录
常见的排序算法如下:
1. 基于比较的低效算法
这类算法时间复杂度一般都是 O ( n 2 ) O(n^2) O(n2)
选择排序
选择排序(Selection Sort)每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
基本步骤
以下是选择排序的基本步骤:
- 在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置。
- 重复上述操作,直到所有元素均排序完毕。
给定一个长度为n的列表,循环n-1次得到有序序列:
- 第0次循环从[0,n-1]中找到最小元素a[x],与a[0]进行交换
- 第1次循环从[1,n-1]中找到最小元素a[x],与a[1]进行交换
- …
- 第n-2次循环从[n-2,n-1]中找到最小元素a[x],与a[n-2]进行交换
代码实现
对于列表的每个位置"i",假设它是当前未排序部分的最小值,并记录下它的索引"min_idx"。
遍历从"i+1"到列表末尾的所有元素,寻找是否存在比当前假设的最小值还要小的元素。如果存在,我们更新"min_idx"为那个更小的元素的索引。
完成内层循环后,交换位置"i"和"min_idx"处的元素。这样,位置"i"处的元素就是到目前为止排序好的部分中的最小元素。
i:循环的次数,范围是[0,n-2]
j:列表的数的位置,从i+1开始(前面i个都是已经排序好的最小值)
min_idx:每次循环中最小值对应的索引
def selection_sort(lst):
for i in range(len(lst)):
#不断拿出第一小的,第二小的……
min_idx=i
for j in range(i+1,len(lst)):
if lst[min_idx]>lst[j]:
lst[min_idx],lst[j]=lst[j],lst[min_idx]
选择排序的时间复杂度是 O ( n 2 ) O(n^2) O(n2),其中n是列表的长度。这是因为我们需要两层循环:一层遍历整个列表,另一层在未排序的部分寻找最小值。
选择排序的时间复杂度是O(n^2),空间复杂度O(1),稳定。
冒泡排序
冒泡排序(Bubble Sort)重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
基本步骤
冒泡排序算法步骤:
- 比较相邻元素,如果a[i]>a[i+1],则位置互换
- 从左到右遍历,把大的数放到最后
- 循环n-1次排序完毕
给定一个长度为n的列表,循环n-1次得到有序序列:
- 第0次循环:比较<a[0],a[1]>,<a[1],a[2]>,…,<a[n-3],a[n-2]>,<a[n-2],a[n-1]>,确定a[n-1]
- 第1次循环:比较<a[0],a[1]>,<a[1],a[2]>,…<a[n-4],a[n-3]>,<a[n-3],a[n-2]>,确定a[n-2]
- …
- 第n-2次循环:比较<a[0],a[1]>,确定a[1]
代码实现
对于a的第i个数,把a[i]和a[i+1]进行比较,若a[i]>a[i+1],位置互换,循环n-1次。
i:循环的次数,范围是[0,n-2]
j:列表的数的位置,从0开始,到len(a)-i-2为止(后面i个都是已经排序好的最小值)
def bubble_sort(a):
#相当于一共要比较len(a)-1次
for i in range(len(a)):
#每次比较都是相邻的两个数比较,比较完放到数组的末尾
for j in range(len(a)-i-1):
if a[j]>a[j+1]:
a[j],a[j+1]=a[j+1],a[j]
选择排序的时间复杂度是O(n^2),空间复杂度O(1),稳定。
插入排序
插入排序(Insertion Sort)的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
基本步骤
以下是插入排序的基本步骤:
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
对于a数组的第i个数a[i],前面[0,i-1]个数都视为已经排序好了的。从后往前逐个判断a[j]是否大于a[i]:
- 如果a[j]>a[i]:a[j]往后挪一个位置
- 如果a[j]<=a[i]:a[j+1]=a[i]
代码实现
i:循环的次数,要插入的数字的下标,范围是[1,n-2](因为第一个数视为已排序)
key:i对应的值的大小,用于比较大小
j:前面排好序的列表的数的位置,初始值i-1(表示在i前面一位数)
def insertion_sort(lst):
for i in range(1,len(lst)):
#要插入的值key
key=lst[i]
#前面已经排好序的数组,从后往前遍历
j=i-1
#已排序的元素的值大于要插入的值,该已排序元素往后挪一位
while j>=0 and key<lst[j]:
lst[j+1]=lst[j]
j-=1
#找到正确的位置,插入
lst[j+1]=key
选择排序的时间复杂度是O(n^2),空间复杂度O(1),不稳定。
2. 基于比较的高效算法
时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn)。
归并排序
归并排序(Merge Sort)将一个待排序的列表递归地分割成两个子列表,直到每个子列表只包含一个元素(此时认为它是已排序的)。然后,算法开始合并这些子列表,并在合并过程中进行排序。
合并两个已排序的子列表是归并排序算法的核心部分。假设我们有两个有序列表,我们将从这两个列表的起始位置开始比较元素,并将较小的元素放入一个新的列表中,直到其中一个列表为空。然后,我们将另一个列表的剩余部分(如果有的话)添加到新列表的末尾。
基本步骤
两个有序列表如何合并成一个列表?
- 对于A=[1,3,5,7],B=[2,4,6,8]
- 构建一个result=[]
- 当A非空且B非空:
- 比较A[0]和B[0]
- result添加较小的那个元素,并从原始数组中弹出
- 如果A非空,将A添加到result末尾
- 如果B非空,将B添加到result末尾
def Merge(A,B):
result=[]
while len(A) and len(B):
if A[0]<=B[0]:
result.append(A.pop([0]))
else:
result.append(B.pop([0]))
result.extend(A)
result.extend(B)
return result
以下是归并排序的基本步骤:
- 先把数组分成两部分
- 每部分递归处理变成有序数组
- 将两个有序列表合并
代码实现
def merge(l,r):
merged=[]
l_id=0
r_id=0
while l_id<len(l) and r_id<len(r):
if l[l_id]<=r[r_id]:
merged.append(l[l_id])
l_id+=1
else:
merged.append(r[r_id])
r_id+=1
merged.extend(l[l_id:])
merged.extend(r[r_id:])
return merged
def merge_sort(lst):
if len(lst)<=1:
return lst
mid=len(lst)//2
l=merge_sort(lst[:mid])
r=merge_sort(lst[mid:])
return merge(l,r)
快速排序
快速排序(Quick Sort)是一种高效的排序算法,其基本思想是采用分而治之(Divide and Conquer)的策略。快速排序通过选择一个“基准”(pivot)元素,将待排序的序列划分为两个子序列,左边子序列的元素都比基准小,右边子序列的元素都比基准大,然后再对这两个子序列分别进行快速排序,最终得到有序的序列。
基本步骤
以下是快速排序的基本步骤:
-
选择一个基准元素(通常选择序列的第一个元素)x。
-
把列表分成三部分,所有数据都比基准小的一部分,基准元素,所有数据都比基准大的一部分。
-
对这两部分数据分别进行快速排序。
快速排序的关键在于“分区”操作,即如何划分出两个子序列。通常使用双指针法,一个指针从左向右扫描,另一个指针从右向左扫描,直到两个指针相遇。
例子
a=[7,6,4,5,8,9],left=0.right=8(这里的left、right是指针)
- 基准值下标设为left
- idx存放小于等于基准值的数,idx初始值为left+1
- 从left+1到right的每个元素a[i]:
- 如果a[i]<=基准值a[left]:将a[i],a[idx]互换,idx=idx+1
- 最终结果为【6,4,5】【7】【8,9】
- 对左侧右侧数组重复上述操作
代码实现
def quick_sort(lst):
# 如果lst里面不多于1个元素,视为已经排列好
if len(arr)<=1:
return arr
else:
# 基准值
pivot=arr[0]
less=[x for x in arr[1:] if x<=pivot]
more=[x for x in arr[1:] if x>pivot]
return quick_sort(less)+[pivot]+quick_sort(more)
然而,这种实现方式并不是最高效的,因为它使用了额外的空间来创建子列表。在实际应用中,我们通常会使用“原地”(in-place)分区方法来避免额外的空间开销,直接在原数组上进行操作。下面是一个原地分区的快速排序实现:
n= int(input())
a=list(map(int,input().split()))
# 求出基准值的位置排序即可
def partition(a,left,right):
#找基准值,为a[left]
idx=left+1
for i in range(left+1,right+1):
if a[i]<=a[left]:
a[i],a[idx]=a[idx],a[i]
idx+=1
#把前半部分的最后一个和基准值对调
a[left],a[idx-1]=a[idx-1],a[left]
#返回基准值坐标
return idx-1
def quicksort(a,left,right):
if left<right:
mid=partition(a,left,right)
#此时a分为三部分:a[left:mid],a[mid],a[mid+1:right+1]
quicksort(a, left, mid-1)
quicksort(a,mid+1,right)
#只需要考虑怎么把当前的问题分成两个子问题
quicksort(a, 0, n-1)
print(' '.join(map(str,a)))
3. 基于数值划分的高效算法
时间复杂度一般为 O ( n ) O(n) O(n)
桶排序
桶排序(Bucket Sort)利用了函数的映射关系,将数据分配到有限的桶里,分别进行排序。
高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
-
首先要使得数据分散得尽可能均匀;
-
对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
桶排序的基本思想是:将待排序的数据分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
基本步骤
以下是桶排序的基本步骤:
- 初始化k个桶
- 遍历数据,把数据放到对应桶里
- 每个桶单独排序
- 各个桶的数据拼接起来
代码实现
def bucket_sort(arr):
# 1. 获取数组的最大值和最小值
max_val = max(arr)
min_val = min(arr)
# 2. 计算桶的数量
bucket_range = (max_val - min_val) // len(arr) + 1 #多加一个桶以防不够
bucket_list = [[] for _ in range(len(arr) + 1)]
# 3. 将元素分配到桶中
for num in arr:
index = int((num - min_val) // bucket_range)
bucket_list[index].append(num)
# 4. 对每个桶中的元素进行排序
for bucket in bucket_list:
bucket.sort()
# 5. 将桶中的元素放回原数组
sorted_arr = []
for bucket in bucket_list:
sorted_arr.extend(bucket)
return sorted_arr
# 测试桶排序
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = bucket_sort(arr)
print("排序后的数组:")
for num in sorted_arr:
print(num),
4. 自带函数
sort()
只能用在列表上,在原列表基础上排序
代码实现
list.sort()
sorted()
可以用在任何迭代器上,产生一个新的列表,不对原列表造成影响
代码实现
sorted(iterable,key=None,reverse=False)
key是指可迭代对象中的用来排序的某个元素
reverse指排序规则,True默认是降序,False是升序