contents
排序算法(Sorting algorithm)
一种能将一串数据依照特定顺序进行排列的一种算法。
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。即,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
冒泡排序(Bubble Sort)
重复地遍历要排序的数列,一次比较两个元素。若比较的这两个元素顺序错误就交换顺序。遍历数列的工作重复地进行,直到不再需要交换,说明该数列已经排序完成。
算法原理:
- 比较相邻的元素,若第一个比第二个大则交换(升序)。
- 移到下一个位置,对相邻元素做同样的工作,直到最后第二个位置(结尾的最后一对)。
一轮后,最后的元素是最大的数,不再进入排序。 - 对剩下的元素重复以上的步骤,直到没有任何一对数字需要比较。
算法实现
用顺序表、链表实现的不同,在于元素地址交换,一个是交换两个位置保存的数据,一个是交换节点(链接区需要重新排布),本质是相同的。(下面其他算法同)
def Bubble_sort(alist):
"""冒泡排序"""
for i in range(len(alist)-1):
#外层控制走多少次
for j in range(len(alist)-i-1):
#内层控制从头走到“尾”
if alist[j]>alist[j+1]:
alist[j],alist[j+1]=alist[j+1],alist[j]
return alist
优化实现
一旦轮次检测到顺序排列,则跳出循环。
def Bubble_sort(alist):
"""冒泡排序"""
for i in range(len(alist)-1):
#外层控制走多少次
count=0
for j in range(len(alist)-i-1):
#内层控制从头走到“尾”
if alist[j]>alist[j+1]:
alist[j],alist[j+1]=alist[j+1],alist[j]
count+=1
if count==0:
break
return alist
时间复杂度
最优时间复杂度:
O
(
n
)
O(n)
O(n)(表示遍历一次发现没有任何可以交换的元素,排序结束。)
最坏时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
稳定性:稳定
选择排序(Selection sort)
算法原理
- 在未排序序列中找到最小(大)元素,与起始位置的元素交换位置
- 从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列后的末尾,直到所有元素排序完毕。
算法实现
def selection_sort(alist):
"""选择排序"""
n=len(alist)
for i in range(n-1):#最后第二个确定的时候,最后一个自然就确定了
min_index=i
for j in range(i+1,n):
if alist[min_index]>alist[j]:
min_index=j
if alist[min_index]!=alist[i]:
alist[i],alist[min_index]=alist[min_index],alist[i]
return alist
时间复杂度
最优时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
最坏时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
稳定性:不稳定(考虑升序每次选择最大的情况)
插入排序
算法原理
- 构建有序序列,其后为未排序序列
- 取出未排序数据的首位
- 对有序序列从后向前扫描元素,若元素与取出的首位顺序不符则交换位置,直到顺序正确
- 重复上述步骤知道未排序序列为空。
算法实现
def insert_sort(alist):
for i in range(1,len(alist)):
tem=alist[i]
for j in range(i,0,-1):
if tem <alist[j-1]:
alist[j],alist[j-1]=alist[j-1],alist[j]
else:
break
return alist
或
def insert_sort(alist):
for i in range(1,len(alist)):
#j代表内层循环起始值
j=i
while j>0:
if alist[j-1]>alist[j]:
alist[j-1],alist[j]=alist[j],alist[j-1]
j-=1
else:
break
return alist
时间复杂度
最优时间复杂度:
O
(
n
)
O(n)
O(n) (升序排列,序列已经处于升序状态)
最坏时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
稳定性:稳定
希尔排序(Shell Sort)
也称缩小增量排序,插入排序的一种,是直接插入排序算法的一种更高效的改进版本。
算法原理
-
将数组分成gap相等的子列分别进行插入排序
-
减小gap(即增长子列),来进行插入排序。直到gap=1(即只有一列)。
算法实现
def shell_sort(alist):
n=len(alist)
gap=n//2
while gap>0: # 对不同gap进行插入排序
for i in range(gap,n):#对gap确定的子序列,都进行插入排序
j=i
while (j>=gap)&(alist[j-gap]>alist[j]): #这样就省去了break
alist[j-gap],alist[j]=alist[j],alist[j-gap]
j-=1
gap//=2
return alist
时间复杂度
最优时间复杂度:根据步长序列的不同而不同
最坏时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
稳定想:不稳定
快速排序(Quicksort)
又称划分交换排序(partition-exchange sort)。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据递归进行,以此达到整个数据变成有序序列。
算法原理
- 选"基准"(pivot):从数列中挑出一个元素。
- 分区(partition)操作:对数列排序,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
- 递归(recursive):把小于基准值元素的子数列和大于基准值元素的子数列再进行分区操作。
算法实现
def quick_sort(alist, start, end):
"""快速排序"""
if start>=end:
return
base=alist[start] #设定起始元素为要寻找位置的基准元素
low=start # low为序列左边的由左向右移动的游标
high=end # high为序列右边的由右向左移动的游标
while low<high:
while low<high and alist[high]>=base: #若low与high未重合,high指向的元素>=基准,则左移
high-=1
alist[low]=alist[high]
while low<high and alist[low]<base: #若low与high未重合,low指向的元素>基准,则又移
low+=1
alist[high]=alist[low]
alist[high]=base
#列表切片后返回的是新的列表,我们应该传原有列表
quick_sort(alist, start, low-1)
quick_sort(alist, low+1, end)
return alist
时间复杂度
最优时间复杂度:
最坏时间复杂度:
最优时间复杂度: O ( n log n ) O(n\log n) O(nlogn)
- 纵向来看,若每次挑出的基准元素都在中间的位置,那么每次将序列分成大小 相等的两部分,以此类推,每次子列基准也位于子列中间,则只需要划分 log n \log n logn次则会得到长度都为0或1的子列。
- 横向来看,每次划分都会遍历几乎所有元素,从而要吃乘上n
最坏时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 纵向来看, 每次跳出的基准元素都为子列的最大(小)值。
- 横向来看,每次划分都会遍历几乎所有元素,从而要吃乘上n
稳定性:不稳定
归并排序
归并排序是采用分治法的一个非常典型的应用。思想主要是先递归分解数组,再合并数组。
算法原理
- 先递归分解数组,直到数组分解最小
- 合并两个有序数组:比较两个数组的最前面的数,取小,相应数组指针往后移一位。再比较,直至一个数组为空
- 将另一个数组的剩余部分复制到合并的数组后即可。
算法实现
def merge_sort(alist):
"""归并排序"""
if len(alist)<=1:
return alist
n=len(alist)
mid=n//2
left_list=merge_sort(alist[:mid])
right_list=merge_sort(alist[mid:])
result=[]
left_pointer,right_pointer=0,0
while left_pointer<len(left_list) and right_pointer<len(right_list):
if left_list[left_pointer]<=right_list[right_pointer]:
result.append(left_list[left_pointer]) #合并时,元素相等情况,拿左边,所以稳定
left_pointer+=1
else:
result.append(right_list[right_pointer])
right_pointer+=1
result+=left_list[left_pointer:]
result+=right_list[right_pointer:]#切片数据,超出范围返回空列表,不会报错
return result
时间复杂度
最优时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn)
最坏时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn)
- 拆分不涉及循环,靠切片,与 n n n无关
- 合并时,横向来看,两两合并需要比较,时间复杂度花费在元素比较,为
n
n
n。
\qquad 纵向来看, 需要合并 log n \log n logn次。
稳定性:稳定
- 拆分时,保持原有次序
- 合并时,元素相等情况,拿左边,所以稳定
常见排序算法效率比较
归并算法涉及到空间的额外开销,其他的没有。