首先讲解的网上很多,我直接上python代码
注意下表是平均时间,笔试面试可能会问最好最坏
排序算法 | 平均时间复杂度 |
---|---|
冒泡排序 | O(n2) |
选择排序 | O(n2) |
插入排序 | O(n2) |
希尔排序 | O(n1.5) |
快速排序 | O(N*logN) |
归并排序 | O(N*logN) |
堆排序 | O(N*logN) |
基数排序 | O(d(n+r)) |
1、冒泡排序
-
基本思想:两个数比较大小,较大的数下沉,较小的数冒起来。
-
过程:
- 比较相邻的两个数据,如果第二个数小,就交换位置。
- 从后向前两两比较,一直到比较最前两个数据。最终最小数被交换到起始的位置,这样第一个最小数的位置就排好了。
- 继续重复上述过程,依次将第2.3...n-1个最小数排好位置。
-
平均时间复杂度:O(n2)
def bubblesort(seq):
for i in range(len(seq)):
for j in range(len(seq) - 1, i, -1):
if seq[j] < seq[j - 1]:
seq[j - 1], seq[j] = seq[j], seq[j - 1]
return seq
s = [4, 6, 2, 5, 7, 9, 8, 1]
bubblesort(s)
print(s)
2、选择排序
-
基本思想:
在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换;
第二次遍历n-2个数,找到最小的数值与第二个元素交换;
。。。
第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成。 -
过程:
-
平均时间复杂度:O(n2)
def selectsort(seq):
for fillslot in range(len(seq)-1,0,-1):
pos = 0
for loc in range(1,fillslot):
if seq[loc] > seq[pos]:
pos = loc
seq[pos], seq[fillslot] = seq[fillslot], seq[pos]
return seq
3、插入排序
-
基本思想:
在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。 -
过程:
插入排序
相同的场景
-
平均时间复杂度:O(n2)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
插入排序
由于其内层循环非常紧凑,对于小规模的输入,
插入排序是一种非常快的原址排序算法
注: 如果输入数组中仅有常数个元素需要在排序过程中存储在数组外,
则称这种排序算法是原址的。
"""
def insertsort(seq):
for index in range(1, len(seq)):
currentvalue = seq[index]
pos = index
while pos > 0 and seq[pos-1] > currentvalue:
seq[pos] = seq[pos-1]
pos -= 1
seq[pos] = currentvalue
return seq
4、归并排序
def mergesort(seq):
"""归并排序"""
if len(seq) <= 1:
return seq
mid = len(seq) // 2 # 将列表分成更小的两个列表
# 分别对左右两个列表进行处理,分别返回两个排序好的列表
left = mergesort(seq[:mid])
right = mergesort(seq[mid:])
# 对排序好的两个列表合并,产生一个新的排序好的列表
return merge(left, right)
def merge(left, right):
"""合并两个已排序好的列表,产生一个新的已排序好的列表"""
result = [] # 新的已排序好的列表
i = 0 # 下标
j = 0
# 对两个列表中的元素 两两对比。
# 将最小的元素,放到result中,并对当前列表下标加1
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
5、快排
-
基本思想:(分治)
- 先从数列中取出一个数作为key值;
- 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
- 对左右两个小数列重复第二步,直至各区间只有1个数。
-
辅助理解:挖坑填数
- 初始时 i = 0; j = 9; key=72
由于已经将a[0]中的数保存到key中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比key小的数。当j=8,符合条件,a[0] = a[8] ; i++ ; 将a[8]挖出再填到上一个坑a[0]中。
这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。
这次从i开始向后找一个大于key的数,当i=3,符合条件,a[8] = a[3] ; j-- ; 将a[3]挖出再填到上一个坑中。数组:72 - 6 - 57 - 88 - 60 - 42 - 83 - 73 - 48 - 85 0 1 2 3 4 5 6 7 8 9
- 此时 i = 3; j = 7; key=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将key填入a[5]。数组:48 - 6 - 57 - 88 - 60 - 42 - 83 - 73 - 88 - 85 0 1 2 3 4 5 6 7 8 9
- 可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
<数组:48 - 6 - 57 - 42 - 60 - 72 - 83 - 73 - 88 - 85 0 1 2 3 4 5 6 7 8 9
- 初始时 i = 0; j = 9; key=72
-
平均时间复杂度:O(N*logN)
'''O(nlog n)
找枢纽值,然后分而治之
'''
def quicksort(seq):
if len(seq)<=1:
return seq
else:
pivot = seq[0]
small = [i for i in seq[1:] if i < pivot]
big = [j for j in seq[1:] if j >=pivot]
return quicksort(small) + [pivot] + quicksort(big)
6、桶排序
#! /usr/bin/env python
#coding=utf-8
'''
桶排序
复杂度O(n)
'''
def countsort( A, d ): #A待排数组, d位数 如果位置需要先修改一下知道A中最多位数
for k in range(d): #k轮排序
s[ [] for i in range(10) ] #数字0~9,建10个桶
'''对于数组中的元素,首先按照最低有效数字进行
排序,然后由低位向高位进行'''
for i in A:
s[ i//(10**k)%10 ].append(i)
A = [j for i in s for j in i] #977/10=97(小数舍去),87/100=0
return A
7、堆排序
-
基本思想:
- 图示: (88,85,83,73,72,60,57,48,42,6)
Heap Sort
- 图示: (88,85,83,73,72,60,57,48,42,6)
-
平均时间复杂度:O(NlogN)
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
堆排序
堆排序的时间复杂度是O(nlg(n)),并且具有空间原址性
二叉堆heap是一种数据结构,可用来实现优先队列
给定一个节点的下标i(下标从0开始),则其父节点、坐孩子、右孩子坐标:
parent(i) = floor((i+1)/2 - 1) = ((i + 1) >> 1) - 1
left(i) = 2*i + 1 = (i << 1) + 1
right(i) = 2*i + 2 = (i + 1) << 1
最小堆定义: 所有i必须满足A[parent(i)] <= A[i]
最大堆定义: 所有i必须满足A[parent(i)] >= A[i]
在堆排序中,我们使用最大堆
在优先队列算法中,使用最小堆
"""
class Heap():
def __init__(self, seq, heapSize, length):
"""
seq: 存放待排序的序列
heapSize: 堆的大小
lenght: 整个序列大小
"""
self.seq = seq
self.heapSize = heapSize
self.length = length
def heapSort(seq):
"""
堆排序算法
"""
heap = Heap(seq, len(seq), len(seq))
__buildMaxHeap(heap)
s = heap.seq
for i in range(heap.length - 1, 0, -1):
s[0], s[i] = s[i], s[0]
heap.heapSize -= 1
__maxHeapify(heap, 0)
def __maxHeapify(heap, i):
"""
前提是i的两棵子树left(i)和right(i)的二叉树都是最大堆了
现在加入i节点后,要保持这个二叉树为最大堆的性质
heap: Heap数据结构
"""
seq = heap.seq
slen = heap.heapSize
while True:
left = (i << 1) + 1
right = (i + 1) << 1
if left < slen and seq[left] > seq[i]:
largest = left
else:
largest = i
if right < slen and seq[right] > seq[largest]:
largest = right
if largest != i:
seq[largest], seq[i] = seq[i], seq[largest]
i = largest
else:
break
def __buildMaxHeap(heap):
"""
由完全二叉树的性质可知:对于 n/2..n-1为下标的元素,都是叶子节点,
那么可从下标floor((i+1)/2 - 1)开始往前到0的元素调用maxHeapify
heap: Heap数据结构
"""
slen = heap.heapSize
for i in range(((slen + 1) >> 1) - 1, -1, -1):
__maxHeapify(heap, i)
8、希尔排序(代码暂未研究)
-
前言:
数据序列1: 13-17-20-42-28 利用插入排序,13-17-20-28-42. Number of swap:1;
数据序列2: 13-17-20-42-14 利用插入排序,13-14-17-20-42. Number of swap:3;
如果数据序列基本有序,使用插入排序会更加高效。 -
基本思想:
在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。
然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。 -
过程:
希尔排序
参考资料:
1. https://www.runoob.com/w3cnote/sort-algorithm-summary.html