十大排序算法可以分为比较类排序以及非比较类排序。
(1) 冒泡排序
算法思想:
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来
说并没有什么太大作用。
算法步骤:
动图演示:
def Bubble_Sort(arr): for i in range(len(arr)-1): # 如果某一趟排序并没有发生交换,那么可以认为数组有序,终止排序即可。 flag = False for j in range(len(arr)-i-1): if arr[j] > arr[j+1]: flag = True arr[j], arr[j+1] = arr[j+1], arr[j] if flag == False: break
(2) 快速排序
算法思想:
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
算法步骤:
动图演示:
代码实现:
import random# 随机选择基准def random_selection(arr, l, r): pos = random.randint(l, r) arr[pos], arr[r] = arr[r], arr[pos]# 根据基准对数组进行划分def partition(arr, l, r): pivot = arr[r] i = l - 1 for j in range(l, r): if arr[j] <= pivot: i += 1 arr[i], arr[j] = arr[j], arr[i] arr[r], arr[i+1] = arr[i+1], arr[r] return i + 1# 随机选择基准后并划分def random_partition(arr, l, r): random_selection(arr, l, r) pos = partition(arr, l, r) if pos - l >= 2: random_partition(arr, l, pos-1) if r - pos >= 2: random_partition(arr, pos+1, r)def Quick_Sort(arr): random_partition(arr, 0, len(arr)-1)
(1) 简单插入排序
算法思想:
插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。就像我们斗地主时,抽牌阶段会把抽到的牌插入到相应的位置中去,使手上的牌有序。
插入排序有个小优化叫做折半插入,就是往前寻找插入位置时,因为前面的数组全部有序,因此我们用二分查找法来寻找插入位置。
算法步骤:
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面,保持相应顺序不变,插入排序是一个稳定的排序算法。)
动图演示:
def insertionSort(arr): for i in range(1,len(arr)): pos, insert_num = 0, arr[i] for j in range(i-1,-1,-1): if insert_num < arr[j]: arr[j+1] = arr[j] if insert_num >= arr[j]: arr[j+1] = insert_num pos = j+1 break if pos == 0: arr[0] = insert_num
(2) 希尔排序
算法思想:
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
算法步骤:
动图演示:
代码演示:
只需要将插入排序稍微修改一下,就可以得到希尔排序。
def gap(length): ans = [length//2] while ans[-1] > 1: ans.append(ans[-1]//2) return ansdef insertionSort(arr, step): for i in range(step, len(arr), step): pos, insert_num = 0, arr[i] for j in range(i - step, -step, -step): if insert_num < arr[j]: arr[j+step] = arr[j] if insert_num >= arr[j]: arr[j+step] = insert_num pos = j+1 break if pos == 0: arr[0] = insert_numdef ShellSort(arr): steps = gap(len(arr)) for step in steps: insertionSort(arr, step)
(1) 简单选择排序
算法思想:
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
算法步骤:
动图演示:
def SelectSort(arr): for i in range(len(arr)-1): min_val, pos = arr[i], i for j in range(i+1, len(arr)): if arr[j] < min_val: min_val, pos = arr[j], j arr[i], arr[pos] = arr[pos], arr[i]
(2) 堆排序
算法思想:
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
堆排序的平均时间复杂度为 Ο(nlogn),利用堆的特性,其实我们可以很方便的得到一个未排序数组中的Top K元素。
算法步骤:
动图演示:
代码实现:
def insert(arr, index): current = index while current > 0: parent = (current - 1) // 2 if arr[current] > arr[parent]: arr[parent], arr[current] = arr[current], arr[parent] else: break current = parentdef shift_down(arr, index): current = 0 while current <= (index-1) // 2: left_child = 2 * current + 1 right_child = 2 * current + 2 # 无右孩子 if right_child > index: if arr[left_child] > arr[current]: arr[left_child], arr[current] = arr[current], arr[left_child] current = left_child else: break else: if arr[current] > max(arr[left_child], arr[right_child]): break else: if arr[left_child] == max(arr[left_child], arr[right_child]):arr[left_child], arr[current] = arr[current], arr[left_child]current = left_child else:arr[right_child], arr[current] = arr[current], arr[right_child]current = right_childdef HeapSort(arr): # 构建初始堆 for index in range(len(arr)): insert(arr, index) for index in range(len(arr)-1,0,-1): arr[index], arr[0] = arr[0], arr[index] if index > 1: shift_down(arr, index-1)
(1) 二路归并排序
算法思想:
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
算法步骤:
动图演示:
代码实现:
def MergeSort(arr, l, r): if r - l > 1: mid = (l + r) // 2 MergeSort(arr, l, mid) MergeSort(arr, mid+1, r) temp = [] i, j = l, mid+1 while i <= mid and j <= r: if arr[i] < arr[j]: temp.append(arr[i]) i += 1 else: temp.append(arr[j]) j += 1 while i <= mid: temp.append(arr[i]) i += 1 while j <= r: temp.append(arr[j]) j += 1 for index in range(l, r+1): arr[index] = temp[index-l] elif r - l == 1 and arr[l] > arr[r]: arr[l], arr[r] = arr[r], arr[l]
多路归并排序的思路参考上述代码,无非就是多了几个有序数组合并而已。
(1) 计数排序
算法思想:
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。
由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。
通俗地理解,例如有 10 个年龄不同的人,统计出有 8 个人的年龄比 A 小,那 A 的年龄就排在第 9 位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去 1 的原因。
算法步骤:
动图演示:
代码实现:
def CountingSort(arr, maxValue): bucketLen = maxValue+1 bucket = [0]*bucketLen sortedIndex =0 arrLen = len(arr) for i in range(arrLen): if not bucket[arr[i]]: bucket[arr[i]]=0 bucket[arr[i]]+=1 for j in range(bucketLen): while bucket[j]>0: arr[sortedIndex] = j sortedIndex+=1 bucket[j]-=1 return arr
(2) 桶排序
算法思想:
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
1.什么时候最快
当输入的数据可以均匀的分配到每一个桶中。
2.什么时候最慢
当输入的数据被分配到了同一个桶中。
3.示意图
元素分布在桶中:
def bucket_sort(arr, bucket_size): min_val = min(arr) max_val = max(arr) bucket_count = ((max_val - min_val) // bucket_size) + 1 buckets = [[] for _ in range(bucket_count)] for data in arr: index = ((data - min_val) // bucket_size) buckets[index].append(data) for i in range(bucket_count): buckets[i].sort() new_arr = [] for i in range(bucket_count): for j in range(len(buckets[i])): new_arr.append(buckets[i][j]) return new_arr
(3) 基数排序
算法思想:
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
基数排序 vs 计数排序 vs 桶排序
基数排序有两种方法:
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;
def radix_sort(s): i = 0 max_num = max(s) j = len(str(max_num)) while i < j: bucket_list =[[] for _ in range(10)] for x in s: bucket_list[int(x / (10**i)) % 10].append(x) print(bucket_list) s.clear() for x in bucket_list: for y in x: s.append(y) i += 1
联系客服