由于需要即将实习面试的需要,所以现在在对一些算法抱一抱佛脚。首先学习的是几种常见的排序算法,网上也有人称:八大排序算法
。本博客主要针对网上动图来对它们进行初步的认识。
主要参考大神博客:
- https://www.cnblogs.com/onepixel/articles/7674659.html
- https://blog.csdn.net/hguisu/article/details/7776068
图片来源:十大经典排序算法动画与解析,看我就够了!(配代码完全版)
测试用例:
q = [4, 67, 8, 12, 2, 82, 0, 7, 53, 96, 7, 23, 9, 4000, -1, 6, -3]
1. 交换——冒泡排序
冒泡排序是属于交换排序当中的一种方法,其思想非常简单:从头开始两两比较(绿色的两个数字),大的数就往上冒,这样遍历一轮后,最大的数字就会直接筛选出来(动图中变为黄色)。然后再重复上述操作,即可完成第二大数的冒泡。以此类推,直到所有的数字排序完成。
代码
def BubbleSort(arr):
for i in range(len(arr)):
flag = 0 # 设置flag,若本身已经有序,则直接break
for j in range(len(arr) - i - 1):
if arr[j] > arr[j + 1]:
flag = 1
arr[j], arr[j+1] = arr[j+1], arr[j]
if flag == 0:
break
return arr
2. 交换——快速排序
交换排序的另一种方式是快速排序,这个排序是目前使用非常广的方法,其也是使用交换的思想,同时还使用了分治的思想。
结合上面动图来进行解释,我们先设定一个“哨兵”(黄色的数),然后我们将比这个“哨兵”小的数字放在左边(绿),比他大的放右边(紫),然后结束操作后,这个数字变为橙色。
接着我们使用分治思想,对比“哨兵”小的,和比它大的两个总体分别使用前面的做法,进行递归,最终对所有数字进行排序。
代码
def QuickSort(arr):
if len(arr) <= 1:
return arr
mid, left, right = arr[0], [], []
for i in arr[1:]:
if i < mid:
left.append(i)
else:
right.append(i)
return QuickSort(left) + [mid] + QuickSort(right)
QuickSort(q)
3. 选择——直接选择排序
直接选择排序法,思想非常简单,我们就直接一个一个遍历,选出其中最小的,记录下来(红),遍历完一遍,交换到对应的次序后变为橙色,重复上述过程,最终按元素大小,从小到大排列。
代码
def SelectSort(arr):
for i in range(len(arr) - 1):
ind = i
for j in range(i + 1, len(arr)):
if arr[ind] > arr[j]:
ind = j
arr[i], arr[ind] = arr[ind], arr[i]
return arr
4. 选择——堆排序
堆排序,稍微会比较复杂,其也是选择排序的一种。首先我们一步一步排成一棵近似二叉树结构,然后首先对每一个叶子结点的小三角进行排序,将最大的值往上冒,然后从底层一步一步往上遍历,最终处于根节点的就是所有值中,最大的值了。
接着,我们将根节点与最右边的叶子结点调换顺序,之后就不再进行考虑,然后对剩下的结点重复前面的操作,最终可以得到一个排好序的数列。
下面有另一个图来理解堆排序是怎样的一个过程:
代码
def Build(arr, root, end):
while True:
child = 2 * root + 1 # 由于堆是一个完全二叉树,所以可以根据其特性,推算出左子节点的位置
if child > end: # 若左子节点超过了最后一个节点,则终止循环
break
if (child + 1 <= end) and (arr[child + 1] > arr[child]): # 若右子节点在最后一个节点之前,并且右子节点比左子节点大,则我们的孩子指针移到右子节点上
child += 1
if arr[child] > arr[root]: # 若最大的孩子节点大于根节点,则交换两者顺序,并且将根节点指针,移到这个孩子节点上
arr[child], arr[root] = arr[root], arr[child]
root = child
else:
break
return arr
def HeapSort(arr):
n = len(arr)
first_root = n // 2 - 1 # 首先根据完全二叉树的性质,确认最深最后的那个根节点的位置
for root in range(first_root, -1, -1): # 由后向前遍历所有的根节点,建堆并进行调整
Build(arr, root, n - 1)
for end in range(n - 1, 0, -1): # 调整完成后,将堆顶的根节点与堆内最后一个元素调换位置,此时为数组中最大的元素,然后重新调整堆,将最大的元素冒到堆顶。依次重复上述操作
arr[0], arr[end] = arr[end], arr[0]
Build(arr, 0, end - 1)
return arr
HeapSort(q)
我们可以在原有堆排序基础上稍作修改,变为Topk
的代码:
Topk代码
def Topk(arr, k):
n = len(arr)
if k >= n: # 首先判断,若k >= n,则输出整个数组
return arr
else:
first_root = n // 2 - 1
for root in range(first_root, -1, -1):
Build(arr, root, n - 1)
for end in range(n - 1, n - k - 1, -1): # 我们只需要循环k次,找出前k个最大元素即可
arr[0], arr[end] = arr[end], arr[0]
Build(arr, 0, end - 1)
return arr[::-1][:k] # 最后找出尾部的k个元素
Topk(q, 10)
5. 插入——直接插入排序
直接插入排序也是非常简单的一个排序方式,我们先将两个数字排好序(橙),然后往后选一个数字(红),在排好序的位置中找个合适的位置插入,然后变为橙色。接着重复上述操作,直到序列中的所有的数字排好序。
6. 插入——希尔排序
另一种更厉害一些的插入排序方法就是希尔排序,它会将数字分几遍挑出来进行排序。
预先我们指定一个增量序列:[5, 2, 1]
第一遍,我们每隔5个进行两两大小比较,大的排后面,小的排前面。
第二遍,我们每隔2个进行两两大小比较,大的排后面,小的排前面。
第三遍,我们每隔1个(相邻)进行两两大小比较,大的排后面,小的排前面。
最终就可得到我们的排序结果。
7. 归并排序
归并排序也是一种非常高效的排序方式,其也用了分治的思想,具体的代码可以写成递归的形式。
我们首先将整个序列两两分开,然后每组中的两个元素排好序。接着就是组与组和合并,这个只需将两组所有的元素遍历一遍,即可按顺序合并。以此类推,最终所有组合并为一组时,整个数列就已经排好序了。
代码
def Merge(arr1, arr2): # 可参考有序链表的合并
result = []
while arr1 and arr2:
if arr1[0] < arr2[0]:
result.append(arr1.pop(0))
else:
result.append(arr2.pop(0))
if arr1:
result += arr1
if arr2:
result += arr2
return result
def MergeSort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
return Merge(MergeSort(arr[:mid]), MergeSort(arr[mid:]))
MergeSort(q)
8. 基数排序
基数排序是一种非常有意思的排序。对于数值偏小的一组序列,其速度是非常快的,时间复杂度达到了线性,而且思想也非常的巧妙。
首先我们按照个位数的大小,分别放入10个队列中,然后采用先进先出的原则,摆回去。继而再对十位数进行相同的操作。以此类推,直至最后最大一个数的那个数位结束了这种操作,最终我们就得到排好序的数列。
虽然这种排序方法是线性复杂度,但如果存在非常大的数,就会非常慢,以为线性复杂度的倍数会非常高,从而得不偿失,这点需要注意。
由于时间原因,最近只是针对不同排序算法的含义进行理解,后续会逐渐补充上Python
实现的代码。
最后再补充另一个动图做的非常好的十大排序的博客:十大经典排序算法动画与解析,看我就够了!(配代码完全版)