发表时间:2022-03-26来源:网络
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。
两年前,我曾在博客园发布过一篇《十大经典排序算法最强总结(含JAVA代码实现)》博文,简要介绍了比较经典的十大排序算法,不过在之前的博文中,仅给出了Java版本的代码实现,并且有一些细节上的错误。所以,今天重新写一篇文章,深入了解下十大经典排序算法的原理及实现。
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等,本文只讲解内部排序算法。用一张图概括:

图片名词解释:
n:数据规模 k:“桶”的个数 In-place:占用常数内存,不占用额外内存 Out-place:占用额外内存十种常见排序算法可以分类两大类别:比较类排序和非比较类排序。

常见的快速排序、归并排序、堆排序以及冒泡排序等都属于比较类排序算法。比较类排序是通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logn次,所以时间复杂度平均O(nlogn)。
比较类排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
而计数排序、基数排序、桶排序则属于非比较类排序算法。非比较排序不通过比较来决定元素间的相对次序,而是通过确定每个元素之前,应该有多少个元素来排序。由于它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。 非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。
非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
冒泡排序是一种简单的排序算法。它重复地遍历要排序的序列,依次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历序列的工作是重复地进行直到没有再需要交换为止,此时说明该序列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

JAVA
/** * 冒泡排序 * @param arr * @return arr */ public static int[] BubbleSort(int[] arr) { for (int i = 1; i < arr.length; i++) { // Set a flag, if true, that means the loop has not been swapped, // that is, the sequence has been ordered, the sorting has been completed. boolean flag = true; for (int j = 0; j < arr.length - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; // Change flag flag = false; } } if (flag) { break; } } return arr; }Python
def BubbleSort(arr): for i in range(len(arr)): is_sorted = True for j in range(len(arr)-i-1): if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] is_sorted = False if is_sorted: break return arr此处对代码做了一个小优化,加入了is_sorted Flag,目的是将算法的最佳时间复杂度优化为O(n),即当原输入序列就是排序好的情况下,该算法的时间复杂度就是O(n)。
选择排序是一种简单直观的排序算法,无论什么数据进去都是O(n²)的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

JAVA
/** * 选择排序 * @param arr * @return arr */ public static int[] SelectionSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { int minIndex = i; for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } if (minIndex != i) { int tmp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = tmp; } } return arr; }Python
def SelectionSort(arr): for i in range(len(arr)-1): # 记录最小值的索引 minIndex = i for j in range(i+1, len(arr)): if arr[j] < arr[minIndex]: minIndex = j if minIndex != i: arr[i], arr[minIndex] = arr[minIndex], arr[i] return arr插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

JAVA
/** * 插入排序 * @param arr * @return arr */ public static int[] InsertionSort(int[] arr) { for (int i = 1; i < arr.length; i++) { int preIndex = i - 1; int current = arr[i]; while (preIndex >= 0 && current < arr[preIndex]) { arr[preIndex + 1] = arr[preIndex]; preIndex -= 1; } arr[preIndex + 1] = current; } return arr; }Python
def InsertionSort(arr): for i in range(1, len(arr)): preIndex = i-1 current = arr[i] while preIndex >= 0 and current < arr[preIndex]: arr[preIndex+1] = arr[preIndex] preIndex -= 1 arr[preIndex+1] = current return arr希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为递减增量排序算法,同时该算法是冲破O(n²)的第一批算法之一。
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2, (n/2)/2, ..., 1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
选择一个增量序列{t1, t2, …, tk},其中(ti>tj, i 0) { for (int i = gap; i < n; i++) { int current = arr[i]; int preIndex = i - gap; // Insertion sort while (preIndex >= 0 && arr[preIndex] > current) { arr[preIndex + gap] = arr[preIndex]; preIndex -= gap; } arr[preIndex + gap] = current; } gap /= 2; } return arr; }Python
def ShellSort(arr): n = len(arr) # 初始步长 gap = n // 2 while gap > 0: for i in range(gap, n): # 根据步长进行插入排序 current = arr[i] preIndex = i - gap # 插入排序 while preIndex >= 0 and arr[preIndex] > current: arr[preIndex+gap] = arr[preIndex] preIndex -= gap arr[preIndex+gap] = current # 得到新的步长 gap = gap // 2 return arr归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
归并排序算法是一个递归过程,边界条件为当输入序列仅有一个元素时,直接返回,具体过程如下:
如果输入内只有一个元素,则直接返回,否则将长度为n的输入序列分成两个长度为n/2的子序列; 分别对这两个子序列进行归并排序,使子序列变为有序状态; 设定两个指针,分别指向两个已经排序子序列的起始位置; 比较两个指针所指向的元素,选择相对小的元素放入到合并空间(用于存放排序结果),并移动指针到下一位置; 重复步骤3 ~4直到某一指针达到序列尾; 将另一序列剩下的所有元素直接复制到合并序列尾。
JAVA
/** * 归并排序 * * @param arr * @return arr */ public static int[] MergeSort(int[] arr) { if (arr.length arr[largest]) { largest = left; } if (largest != i) { swap(arr, largest, i); heapify(arr, largest); } } /** * Heap Sort * @param arr * @return */ public static int[] HeapSort(int[] arr) { // index at the end of the heap heapLen = arr.length; // build MaxHeap buildMaxHeap(arr); for (int i = arr.length - 1; i > 0; i--) { // Move the top of the heap to the tail of the heap in turn swap(arr, 0, i); heapLen -= 1; heapify(arr, 0); } return arr; }Python
def HeapSort(arr): global heapLen # 用于标记堆尾部的索引 heapLen = len(arr) # 构建最大堆 buildMaxHeap(arr) for i in range(len(arr)-1, 0, -1): # 依次将堆顶移至堆尾 swap(arr, 0, i) heapLen -= 1 heapify(arr, 0) return arr def buildMaxHeap(arr): for i in range(len(arr)//2-1, -1, -1): heapify(arr, i) def heapify(arr, i): left = 2*i + 1 right = 2*i + 2 largest = i if right < heapLen and arr[right] > arr[largest]: largest = right if left < heapLen and arr[left] > arr[largest]: largest = left if largest != i: swap(arr, largest, i) heapify(arr, largest) def swap(arr, i, j): arr[i], arr[j] = arr[j], arr[i]稳定性:不稳定
时间复杂度:最佳:O(nlogn) 最差:O(nlogn) 平均:O(nlogn)
空间复杂度:O(1)
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

JAVA
/** * Gets the maximum and minimum values in the array * * @param arr * @return */ private static int[] getMinAndMax(int[] arr) { int maxValue = arr[0]; int minValue = arr[0]; for (int i = 0; i < arr.length; i++) { if (arr[i] > maxValue) { maxValue = arr[i]; } else if (arr[i] < minValue) { minValue = arr[i]; } } return new int[] { minValue, maxValue }; } /** * Counting Sort * * @param arr * @return */ public static int[] CountingSort(int[] arr) { if (arr.length < 2) { return arr; } int[] extremum = getMinAndMax(arr); int minValue = extremum[0]; int maxValue = extremum[1]; int[] countArr = new int[maxValue - minValue + 1]; int[] result = new int[arr.length]; for (int i = 0; i < arr.length; i++) { countArr[arr[i] - minValue] += 1; } for (int i = 1; i < countArr.length; i++) { countArr[i] += countArr[i - 1]; } for (int i = arr.length - 1; i >= 0; i--) { int idx = countArr[arr[i] - minValue] - 1; result[idx] = arr[i]; countArr[arr[i] - minValue] -= 1; } return result; }Python
def CountingSort(arr): if arr.size < 2: return arr minValue, maxValue = getMinAndMax(arr) countArr = [0]*(maxValue-minValue+1) resultArr = [0]*len(arr) for i in range(len(arr)): countArr[arr[i]-minValue] += 1 for i in range(1, len(countArr)): countArr[i] += countArr[i-1] for i in range(len(arr)-1, -1, -1): idx = countArr[arr[i]-minValue]-1 resultArr[idx] = arr[i] countArr[arr[i]-minValue] -= 1 return resultArr def getMinAndMax(arr): maxValue = arr[0] minValue = arr[0] for i in range(len(arr)): if arr[i] > maxValue: maxValue = arr[i] elif arr[i] < minValue: minValue = arr[i] return minValue, maxValue当输入的元素是n个0到k之间的整数时,它的运行时间是 O(n+k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量额外内存空间。
稳定性:稳定
时间复杂度:最佳:O(n+k) 最差:O(n+k) 平均:O(n+k)
空间复杂度:O(k)
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
在额外空间充足的情况下,尽量增大桶的数量 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行。

JAVA
/** * Gets the maximum and minimum values in the array * @param arr * @return */ private static int[] getMinAndMax(List arr) { int maxValue = arr.get(0); int minValue = arr.get(0); for (int i : arr) { if (i > maxValue) { maxValue = i; } else if (i < minValue) { minValue = i; } } return new int[] { minValue, maxValue }; } /** * Bucket Sort * @param arr * @return */ public static List BucketSort(List arr, int bucket_size) { if (arr.size() < 2 || bucket_size == 0) { return arr; } int[] extremum = getMinAndMax(arr); int minValue = extremum[0]; int maxValue = extremum[1]; int bucket_cnt = (maxValue - minValue) / bucket_size + 1; List buckets = new ArrayList(); for (int i = 0; i < bucket_cnt; i++) { buckets.add(new ArrayList()); } for (int element : arr) { int idx = (element - minValue) / bucket_size; buckets.get(idx).add(element); } for (int i = 0; i < buckets.size(); i++) { if (buckets.get(i).size() > 1) { buckets.set(i, sort(buckets.get(i), bucket_size / 2)); } } ArrayList result = new ArrayList(); for (List bucket : buckets) { for (int element : bucket) { result.add(element); } } return result; }Python
def BucketSort(arr, bucket_size): if len(arr) < 2 or bucket_size == 0: return arr minValue, maxValue = getMinAndMax(arr) bucket_cnt = (maxValue-minValue)//bucket_size+1 bucket = [[] for i in range(bucket_cnt)] for i in range(len(arr)): idx = (arr[i]-minValue) // bucket_size bucket[idx].append(arr[i]) for i in range(len(bucket)): if len(bucket[i]) > 1: # 递归使用桶排序,也可使用其它排序算法 bucket[i] = BucketSort(bucket[i], bucket_size//2) result = [] for i in range(len(bucket)): for j in range(len(bucket[i])): result.append(bucket[i][j]) return result def getMinAndMax(arr): maxValue = arr[0] minValue = arr[0] for i in range(len(arr)): if arr[i] > maxValue: maxValue = arr[i] elif arr[i] < minValue: minValue = arr[i] return minValue, maxValue稳定性:稳定
时间复杂度:最佳:O(n+k) 最差:O(n²) 平均:O(n+k)
空间复杂度:O(k)
基数排序也是非比较的排序算法,对元素中的每一位数字进行排序,从最低位开始排序,复杂度为O(n×k),n为数组长度,k为数组中元素的最大的位数;
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

JAVA
/** * Radix Sort * * @param arr * @return */ public static int[] RadixSort(int[] arr) { if (arr.length < 2) { return arr; } int N = 1; int maxValue = arr[0]; for (int element : arr) { if (element > maxValue) { maxValue = element; } } while (maxValue / 10 != 0) { maxValue = maxValue / 10; N += 1; } for (int i = 0; i < N; i++) { List radix = new ArrayList(); for (int k = 0; k < 10; k++) { radix.add(new ArrayList()); } for (int element : arr) { int idx = (element / (int) Math.pow(10, i)) % 10; radix.get(idx).add(element); } int idx = 0; for (List l : radix) { for (int n : l) { arr[idx++] = n; } } } return arr; }Python
def RadixSort(arr): if len(arr) < 2: return arr maxValue = arr[0] N = 1 for i in range(len(arr)): if arr[i] > maxValue: maxValue = arr[i] while maxValue // 10 != 0: maxValue = maxValue//10 N += 1 for n in range(N): radix = [[] for i in range(10)] for i in range(len(arr)): idx = arr[i]//(10**n) % 10 radix[idx].append(arr[i]) idx = 0 for j in range(len(radix)): for k in range(len(radix[j])): arr[idx] = radix[j][k] idx += 1 return arr稳定性:稳定
时间复杂度:最佳:O(n×k) 最差:O(n×k) 平均:O(n×k)
空间复杂度:O(n+k)
基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶 计数排序:每个桶只存储单一键值 桶排序:每个桶存储一定范围的数值https://www.cnblogs.com/guoyaohua/p/8600214.html
https://en.wikipedia.org/wiki/Sorting_algorithm
https://sort.hust.cc/
苏宁微店卖家版app(苏宁推客)下载v9.8.40 安卓最新版
57.19MB |生活服务
机友邦工程机械网官方版app下载v4.0.4 安卓版
88.56MB |系统工具
苏宁微店客户端(改名苏宁推客)下载v9.8.40 安卓版
57.19MB |生活服务
优腿商家端app下载v1.23.5 安卓版
34.13MB |系统工具
龙湖物业app下载v1.20.0 安卓版
84.54MB |生活服务
moon月球手机版下载v2.6.5 安卓版
51.55MB |生活服务
邻里掌柜app官方版下载v8.12.3 安卓版
151.25MB |生活服务
药采采app官方版下载v2.6.3 安卓版
31.88MB |生活服务
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-03-26
2022-02-15
2022-02-14