排序算法(一)
此文档主要包含选择排序、插入排序、冒泡排序、归并排序、快排还有堆排序的内容。
文章目录
选择排序
思想
选择排序算法的思路为在当前的n个数组中选择出来最大(最小)的那一个,然后将其与其应该在的位置的元素进行交换。
然后再将第二大(小)的元素放到其应该在的位置。
code
def selectionSort(array):
n = len(array)
for i in range(n-1,0,-1):
print(array)
maxIdx = i
for j in range(0,i):
if array[j] > array[maxIdx]:
maxIdx = j
swapElement(array,maxIdx,i)
print(array)
def swapElement(array,x,y):
temp = array[x]
array[x] = array[y]
array[y] = temp
# 原始arry:
[29, 10, 14, 37, 13]
# 第一次选择之后的array,37 换到了最后一个位置(最大的位置)
[29, 10, 14, 13, 37]
# 第二次选择之后的array,29 换到了倒数第二个位置。
[13, 10, 14, 29, 37]
# 第三次选择之后的array,14 的位置未变,因为14就位于他应该在的位置上。
[13, 10, 14, 29, 37]
# 最后一次选择之后的array,13 换到了第二个位置上,此时整个array就排完序了。
[10, 13, 14, 29, 37]
总结
1.时间复杂度
选择排序算法的每一轮要遍历所有元素,共遍历n-1轮,所以时间复杂度是O(N2)
2.空间复杂度
选择排序算法排序过程中需要一个临时变量存储最小元素(最大元素),所需要的额外空间为1,因此空间复杂度为O(1)
3.稳定性
选择排序算法是一种不稳定排序算法,当出现相同元素的时候有可能会改变相同元素的顺序。
插入排序
思想
思路:
- 插入排序的思想和选择排序类似,将当前的元素往前比较,当遇到比他小的元素的时候,就停止交换。
- 从第一个元素依次往前比较插入,最后排下来的就是从小到大排好序的数组。
code
def insertionSort(array):
n = len(array)
for i in range(1,n):
print(array)
next_ = array[i]
j = i-1
while j>=0 and array[j]>next_:
array[j+1] = array[j]
j=j-1
array[j+1]=next_
print(array)
# 原始array
[29, 10, 14, 37, 13]
# 第一次插入,是对10这个元素处理的,往前比较,到头或是到比他小的元素之后,这里10换到了第一个位置。
[10, 29, 14, 37, 13]
#第二次插入,14这个元素换到了10之后,29之前。
[10, 14, 29, 37, 13]
#第三次插入,37这个元素之前没有比他小的,所以37的位置不变。
[10, 14, 29, 37, 13]
#最后一次插入,将13这个元素插入到了10之后,14之前。
[10, 13, 14, 29, 37]
总结
1.时间复杂度
插入排序算法要进行n-1轮,每一轮对比的元素最坏的情况依次是1, 2, 3 … n-1,所以时间复杂度是O(N2)
2.空间复杂度
插入排序算法排序过程中需要一个临时变量存储插入元素,所需要的额外空间为1,因此空间复杂度为O(1)
3.稳定性
插入排序算法在排序过程中,无序数列插入到有序区的过程中,不会改变相同元素的前后顺序,是一种稳定排序算法
冒泡排序
思路:
假设array
的长度为n。
-
从第一个元素到最后一个元素,两两比较,较大的放到后面。这样比较到最后,最大的值就会被换到最后的位置。
-
这次从第一个元素到倒数第二个元素,再次两两比较,这样比较到最后,第二大的值就会被放到其应该在的位置。
-
依次直到整个array都被冒泡排完序。这个array就是最大的array。
code
def bubbleSort(array):
n = len(array)
for i in range(n-1,0,-1):
for j in range(1,i+1):
if array[j-1]>array[j]:
swapElement(array,j,j-1)
# 原始array
[29, 10, 14, 37, 13]
# 第0个元素和第1个元素比较, 29 > 10所以位置交换。
[10, 29, 14, 37, 13]
# 第1个元素和第2个元素比较,29 > 14所以位置交换。
[10, 14, 29, 37, 13]
# 第2个元素和第3个元素比较,29 < 37所以位置不变。
[10, 14, 29, 37, 13]
# 第3个元素和第4个元素比较,37 > 13所以位置不变。此时第一轮排序已经排完了。
[10, 14, 29, 13, 37]
# 现在开始第二轮排序,第0个元素和第1个元素比较,10 < 14所以位置不变。
[10, 14, 29, 13, 37]
# 第1个元素和第2个元素比较,14 < 29所以位置不变。
[10, 14, 29, 13, 37]
# 第2个元素和第3个元素比较,29 > 13所以位置互换。此时第二轮排序已经排完
[10, 14, 13, 29, 37]
# 第三轮排序开始,第0个元素和第1个元素比较,10 < 14所以位置不变。
[10, 14, 13, 29, 37]
# 第1个元素和第2个元素比较,13 < 14所以位置交换。第三轮排序完成。
[10, 13, 14, 29, 37]
# 第四轮排序开始,10 < 13所以位置未变。第四轮排序完成,整体的排序也完成了。
[10, 13, 14, 29, 37]
Better Version
Better Version的核心点就是先判断这个array是不是已经排好序的,如果已经排好序了,则不需要进行后续的操作了。
def bubbleSort(array):
n = len(array)
for i in range(n-1,0,-1):
isSorted = True
for j in range(1,i+1):
if array[j-1]>array[j]:
swapElement(array,j,j-1)
isSorted = False
if isSorted:
return
总结
1.时间复杂度
对于原版的冒泡排序算法,时间复杂度一直都是O(n2)。
Better Version这一版,最坏的情况下的时间复杂度是O(n2),最好的情况下时间复杂度为O(n)。
2.空间复杂度
冒泡排序算法排序过程中需要一个临时变量进行两两交换,所需要的额外空间为1,因此空间复杂度为O(1)
3.稳定性
冒泡排序算法在排序过程中,元素两两交换时,相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法
归并排序
思想
归并排序的思想其实是将整个array
拆分,拆分成一个个子序列后,子序列再进行归并排序,然后再将子序列合并成一个有序的序列。
举个例子,[29, 10, 14, 37, 13]。对这个array进行归并排序。
那么顺序为:
先对[29,10]排序,排完的结果为[10,29]
然后对[10,29,14]排序,排完的结果为[10,14,29]
然后再对[37,13]排序,排完的结果为[13,37]
然后再将[13,37]和[10,14,29]进行合并,得到最终的排序结果[10,13,14,29,37]。
code
def mergeSort(array,low,high):
if low<high:
mid = (low+high)//2
mergeSort(array,low,mid)
mergeSort(array,mid+1,high)
merge(array,low,mid,high)
def merge(array,low,mid,high):
n = high-low+1
result = []
left = low
right = mid+1
while left<=mid and right <=high:
if array[left] <=array[right]:
result.append(array[left])
left+=1
else:
result.append(array[right])
right+=1
while left <= mid:
result.append(array[left])
left+=1
while right<=high:
result.append(array[right])
right+=1
for k in range(0,n):
array[low+k] = result[k]
def mergeSortHelper(array):
mergeSort(array,0,len(array)-1)
# result为每一次归并排序后的结果
result [10, 29]
result [10, 14, 29]
result [13, 37]
result [10, 13, 14, 29, 37]
总结
1.时间复杂度
归并排序算法每次将序列折半分组,共需要logn轮,因此归并排序算法的时间复杂度是O(nlogn)
2.空间复杂度
归并排序算法排序过程中需要额外的一个序列去存储排序后的结果,所占空间是n,因此空间复杂度为O(n)
3.稳定性
归并排序算法在排序过程中,相同元素的前后顺序并没有改变,所以归并排序是一种稳定排序算法
快排
思路
快排是先选取一个pivot
,将array
内比pivot
小的元素放到pivot
的左边,比pivot
大的元素就自然而然到了pivot
的右边。
然后再对左边和右边的数组再进行快速排序。
举个简单的例子:array : [29, 10, 14, 37, 13]
第一次选中的pivot为首元素 29:
code
def swapElement(array,x,y):
temp = array[x]
array[x] = array[y]
array[y] = temp
def quickSort(array,low,high):
if low < high:
pivotIdx = partition(array,low,high)
quickSort(array,low,pivotIdx-1)
quickSort(array,pivotIdx+1,high)
def partition(array,i,j):
pivot = array[i]
middle = i
for k in range(i+1,j+1):
if array[k] < pivot:
middle = middle+1
swapElement(array,k,middle)
swapElement(array,i,middle)
return middle
总结
1.时间复杂度
快速排序算法在分治法的思想下,原数列在每一轮被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可再分为止,平均情况下需要logn轮,因此快速排序算法的平均时间复杂度是O(nlogn)
在极端情况下,快速排序算法每一轮只确定基准元素的位置,时间复杂度为O(N^2)
2.空间复杂度
快速排序算法排序过程中只是使用数组原本的空间进行排序,因此空间复杂度为O(1)
3.稳定性
快速排序算法在排序过程中,可能使相同元素的前后顺序发生改变,所以快速排序是一种不稳定排序算法
Radix Sort(基数排序)
def radixSort(array):
numDigit = int(math.log10(max(array))) +1
for power in [10**i for i in range(numDigit)]:
digitBin = [[] for d in range(10)]
distribute(array,digitBin, power)
collect(digitBin,array)
def distribute(array,digitBin,power):
for item in array:
digit = (item//power) %10
digitBin[digit].append(item)
def collect(digitBin,array):
startIdx = 0
for eachBin in digitBin:
array[startIdx:] = eachBin
startIdx += len(eachBin)
堆排序
思路
堆排序就是利用大根堆和小根堆来实现排序的效果。
以小根堆为例,位于堆顶的是是堆内最小的值。依次pop出堆顶的值,就可以构成一个排好序的array。
code
# 这里直接运用python拥有的一个堆库 heapq。
import heapq
def heapqSort(array):
res = []
heapq.heapify(array)
while array:
res.append(heapq.heappop(array))
return res
总结
1.时间复杂度
下沉调整的时间复杂度等同于堆的高度O(logn),构建二叉堆执行下沉调整次数是n/2,循环删除进行下沉调整次数是n-1,时间复杂度约为O(nlogn)
2.空间复杂度
堆排序算法排序过程中需要一个临时变量进行两两交换,所需要的额外空间为1,因此空间复杂度为O(1)
3.稳定性