排序算法目录如下:
冒泡排序[1]:时间复杂度O(n^2)
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubble_sort(arr): # 冒泡排序
for i in range(len(arr) - 1):
for j in range(len(arr) - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
def bubble_sort_2(arr): # 冒泡排序优化
"""假如在某一趟没有发生交换,则说明已经排好顺序,结束算法"""
for i in range(len(arr) - 1):
exchange = False
for j in range(len(arr) - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
exchange = True
if not exchange:
break
选择排序[2]:时间复杂度O(n^2)
对比数组中前一个元素跟后一个元素的大小,如果后面的元素比前面的元素小则用一个变量min_loc来记住他的位置,接着第二次比较,前面“后一个元素”现变成了“前一个元素”,继续跟他的“后一个元素”进行比较如果后面的元素比他要小则用变量min_loc记住它在数组中的位置(下标),等到循环结束的时候,我们应该找到了最小的那个数的下标了,然后进行判断,如果这个元素的下标不是第一个元素的下标,就让第一个元素跟他交换一下值,这样就找到整个数组中最小的数了。然后找到数组中第二小的数,让他跟数组中第二个元素交换一下值,以此类推。
def select_sort(arr): # 选择排序
"""每趟找最小元素对应的下标,然后与第i个元素交换"""
for i in range(len(arr) - 1):
min_loc = i
for j in range(i + 1, len(arr)):
if arr[i] < arr[min_loc]:
min_loc = j
arr[i], arr[min_loc] = arr[min_loc], arr[i]
快速排序[3]:一般时间复杂度O(nlog(n)),缺点:极端情况复杂度O(n^2)。
通过一趟排序将要排序的数据分割成独立的两部分(如下代码的position函数实现将数据分成两部分),其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
def position(arr, left, right):
tmp = arr[left]
while left < right:
while left < right and arr[right] >= tmp:
right -= 1
arr[left] = arr[right]
while left < right and arr[left] <= tmp:
left += 1
arr[right] = arr[left]
arr[left] = tmp
return left
def quick_sort(arr, left, right):
if left < right:
mid = position(arr, left, right)
quick_sort(arr, left, mid - 1)
quick_sort(arr, mid + 1, right)
堆排序[4]:平均时间复杂度O(nlogn),缺点:在较快排序算法中速度较慢。
1.建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
2.调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
3.堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,堆排序的时间复杂度是O(nlgn) 。
def sift(arr, low, high):
"""
构造堆,节点的左右孩子都是堆,但本身不是堆
arr:非堆数组
low:第一个索引
high:最后一个索引
"""
i = low # root node
tmp = arr[i] # root value
j = 2 * i + 1 # left child node
while j <= high:
if j < high and arr[j] < arr[j + 1]: # 如果左孩子值小于右孩子值,j指向右孩子
j += 1
if arr[j] > tmp: #孩子比父亲节点大
arr[i] = arr[j]#孩子填到父节点
i = j #新的父节点
j = 2 * i + 1 #新的子节点
else:
break
arr[i] = tmp#将父节点放到该放的位置
def heap_sort(arr):#堆排序
n = len(arr)
#构建堆
for i in range(n // 2 - 1, -1, -1):
sift(arr, i, n - 1)
#堆排序
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0]
sift(arr, 0, i-1)
归并排序[5]:时间复杂度O(nlogn),缺点:需要另开内存空间。
归并操作的工作原理如下:
1:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2:设定两个指针,最初位置分别为两个已经排序序列的起始位置
3:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
def merge(arr, low, mid, high):
"""一次归并操作,假设low~mid和mid~high是有序序列"""
i = low
j = mid + 1
tmp_arr = []
while i <= mid and j <= high:
if arr[i] < arr[j]:
tmp_arr.append(arr[i])
i += 1
else:
tmp_arr.append(arr[j])
j += 1
while i <= mid:
tmp_arr.append(arr[i])
i += 1
while j <= high:
tmp_arr.append(arr[j])
j += 1
arr[low:high+1] = tmp_arr
def merge_sort(arr, low, high):
"""先分解,后合并,这里的递归调用很巧妙"""
if low < high:
mid = (low + high) // 2
merge_sort(arr, low, mid)
merge_sort(arr, mid + 1, high)
merge(arr, low, mid, high)