分别有冒泡排序,选择排序,插入排序(前三个属于简单排序,平方阶排序)
快速排序,归并排序以及堆排序(线性对数阶排序)
还有希尔排序,基数排序,计数排序,桶排序
1.冒泡排序
冒泡排序步骤:
比较相邻的元素,如果第一个比第二个大,就交换他门两个->
对第0个到第n-1个的数据做同样的动作,这时,最大的数就在最后的位置上->
针对所有的元素重复以上步骤,除了最后一个->
持续每次对越来越少的元素重复以上的步骤,直到没有任何一对数字需要比较
python实现:
def bubble_sort(arry):
n = len(arry)
for i in range(n):
for j in range(1, n-i):
if arry[j- 1] > arry[j]:
arry[j - 1], arry[j] = arry[j], arry[j - 1]
return arry
修改版的python实现:
def bubbleSort(lyst):
n = len(lyst)
while n > 1:
flag = false
i = 1
while i < n:
if lyst[i] < lyst[i - 1] :
swap(lyst, i, i -1)
flag = true
if not flag:
return #return if no swaps
n -= 1
冒泡排序的平均时间复杂度为O(n ** 2)n的平方
最好情况是O(n)
最坏情况是O(n ** 2) 最坏情况冒泡排序的交换工作超过线性方式
空间复杂度为O(1)
稳定性:稳定
2.选择排序
工作原理:
首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列第二个位置。以此类推,直到所有元素均排序完毕
python实现:
def select_sort(arry):
n = len(arry)
for i in range(0,n):
min = i
for j in range(i + 1, n):
if arry[j] < arry[min]:
min = j
arry[min], arry[i] = arry[i], arry[min]
return arry
选择排序的平均时间复杂度,最好情况,最坏情况都是O(n ** 2)n的平方
空间复杂度:O(1)
稳定性:不稳定
3.插入排序
工作原理:
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
算法执行步骤:
1.从第一个元素开始,该元素可以认为是已经被排序
2.取出下一个元素,在已经排序的元素序列中从后往前扫描
3如果被扫描的元素(已排序)大于新元素,则将被扫描的元素后移一位
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到该位置后
6.重复步骤2-5
python实现:
def insert_sort(arry):
n = len(arry)
for i in range(1,n):
if arry[i] < arry[i - 1]:
temp = arry[i]
index = i
for j in range(i - 1, -1, -1):
if arry[j] > temp:
arry[j + 1] = arry[j]
index = j
else:
break
arry[index] = temp
return arry
插入排序平均时间复杂度是O(n ** 2)n的平方
最好情况是O(n)
最坏情况是O(n ** 2)n的平方
空间复杂度是O(1)
稳定性:稳定
以下是更快的排序方法(也是线性对数阶排序)
4.快速排序
步骤:
1.从数列中挑出一个元素,称为“基准”
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作。
3.递归的把小于基准值元素的子数列和大于基准值的子数列排序
总体来说,有两种快排方式~(区别就是选择基准点的方式不同)
一、一种是每次把基准数设为每个分组数组(分为左右子列表)的第一个元素,进行从后往前扫描和从前往后扫描,把大于基准数的元素都放到了右列表,在循环结束的时候,基准数与分组中最后一个比它小的数交换位置,到此,第一遍已经完成了把大于基准数的元素放后,小于的放前;然后重新分组,把上个循环的基准数后面的子列表(右列表)的第一个元素视为基准数去排,把前面的分组(左列表)也做同样的操作,这样循环,直到子列表的项数小于2即完成
二、以每个数组的middle作为基准点,然后进行左右子列表的排序(用同样的方式),大小比较与第一种方法相同,每次遇到少于2个项的一个子列表,就结束这个过程。
python实现(一):
def quick_sort(arry):
return qsort(arry, 0, len(arry) - 1)
def qsort(arry, left, right):
if left >= right:return arry
key = arry[left]
lp = left
rp = right
while lp < rp:
while arry[rp] >= key and lp < rp:
rp -= 1
while arry[lp] <= key and lp
python实现(二):
def quicksort(lyst):
quicksortHelper(lyst, 0, len(lyst) - 1)
def quicksortHelper(lyst, left, right):
if left < right:
pivotLocation = partition(lyst, left, right)
quicksortHelper(lyst, left, pivotLocation - 1)
quicksortHelper(lyst, pivotLocation + 1, right)
def partition(lyst, left, right):
middle = (left + right) // 2
pivot = lyst[middle]
lyst[middle] = lyst[right]
lyst[right] = pivot
boundary = left
for index in range(left, right):
if lyst[index] < pivot:
swap(lyst, index, boundary)
boundary += 1
swap(lyst, right, boundary)
return boundary
def swap(lyst, left, right):
lyst[left], lyst[right] = lyst[right], lyst[left]
import random
def main(size = 20, sort = quicksort):
lyst = []
for count in range(size):
lyst,append(random.randint(1, size + 1))
print(lyst)
sort(lyst)
print(lyst)
if __name__ == "__main__":
main()
以上两种方法都经过实际上机验证,都是可以正确运行的。
快速排序的平均时间复杂度和最好情况是O(nlogn)
最坏情况是O(n ** 2)n的平方
空间复杂度O(logn)
稳定性:不稳定
5.归并排序
归并排序是采用分治法的一个非常典型的应用,归并排序的思想就是先递归分解数组,再合并数组。
先考虑合并两个有序数组,基本思路是比较两个数组最前面的数,谁小就先取谁,取了后相应的指针就往后移一位,然后在比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
再考虑递归分解,基本思路是将数组分解成left和right,如果这两个数组内部数据是有序的,那么就可以用上面合并数组的方法将这两个数组合并排序。
如何让这两个数组内部是有序的?继续二分。直至分解出的小组只含有一个元素时为止,此时认为该小组内部已有序。然后合并排序相邻两个小组即可。
python实现:
def merge_sort(ary):
if len(ary) <= 1:return ary
num = int(len(ary)/2)
left = merge_sort(ary[:num])
right = merge_sort(ary[num:])
return merge(left, right) #合并数组
def merge(left, right):
l, r = 0, 0
result = []
while l < len(left) and r < len(right):
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result += left[l:]
result += right[r:]
return result
归并排序的精髓就在于对于每一次使用递归执行的语句顺序要十分细心的把握好,推导细心一点,即使是多层的递归也不算难。
但对于更多层次的递归的使用,还要再加强学习。这也是递归的精髓,通过不断的调用来实现。
归并排序的平均时间复杂度,最好情况,最坏情况都是O(nlogn)
空间复杂度是O(n)
稳定性:稳定
6.堆排序
堆排序在top k问题中使用比较频繁。堆排序是采用二叉堆的数据结构来实现的,虽然实质上还是一维数组。二叉堆是一个近似完全二叉树。
二叉堆具有以下性质:
父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值
每个节点的左右子树都是一个二叉堆(都是最大堆或最小堆)
步骤:
1.构造最大堆:
若数组下标范围为0-n,考虑到单独一个元素是大根堆,则从下标n/2开始的元素均为大根堆。于是只要从n/2 - 1开始,向前依次构造大根堆,这样就能保证,构造到某个节点时,它的左右子树都已经是大根堆。
2.堆排序:
由于堆是数组模拟的,得到一个大根堆后,数组内部并不是有序的。因此需要将堆化数组有序化。思想是移除根节点,并作最大堆调整的递归运算。第一次将heap[0]与heap[n-1]交换,再对heap[0……n-2做最大堆调整。第二次将heap[0]与heap[n-2]交换,再对heap[0……n-3]做最大堆调整。重复该操作直至heap[0]和heap[1]交换。由于每次都是将最大的数并入到后面的有序区间,所以操作完成后整个数组就是有序的了
3.最大堆调整:
该方法是提供给上述两个过程调用的。目的是将堆的末端子节点做调整,使得子节点永远小于父节点。
python实现:
def heap_sort(ary):
n = len(ary)
first = int(n/2 - 1) #最后一个非叶子节点
#构造大根堆
for start in range(first, -1, -1):
max_heapify(ary, start, n - 1)
#堆排,将大根堆转换成有序数组
for end in range(n-1, 0, -1):
ary[end], ary[0] = ary[0], ary[end]
max_heapify(ary, 0, end - 1)
return ary
#最大堆调整:将堆的末端子节点做调整,使得子节点永远小于父节点。
#start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary, start, end):
root = start
while true:
#调整节点的子节点
child = root *2 + 1
if child >end:
break
if child + 1 <= end and ary[child] < ary[child + 1]:
#取较大的子节点
child = child + 1
#较大的子节点成为父节点
if ary[root] < ary[child]:
#交换
ary[root], ary[child] = ary[child], ary[root]
root = child
else:
break
堆排序的精髓同样是递归,先构造最大堆,在进行堆排序,然后进行最大堆调整。
堆排序的平均时间复杂度,最好情况,最坏情况都是O(nlogn)
空间复杂度是O(1)
稳定性:不稳定
以上的算法都是十分重要和常见的排序算法。