前言
排序是我们在日常生活和工作中常见的一种操作。在计算机科学中,排序算法就是将一串或一组数据按照特定的顺序进行排列的算法。这些顺序可能是数字的升序或降序,也可能是字母或字词的字母顺序等。我们将探讨几种不同的排序算法,包括他们的原理、优缺点以及代码实现。
1. 排序算法简介
常见的排序算法包括:冒泡排序、快速排序、插入排序、选择排序、希尔排序、归并排序、堆排序、基数排序等。这些排序算法中,有些是比较排序,即通过比较元素之间的关系进行排序;有些是非比较排序,即不通过比较元素之间的关系进行排序,而是通过其他方法,如计数或者映射到其他空间等。
排序算法的效率通常由平均情况时间复杂度、最差情况时间复杂度、空间复杂度、稳定性等多个因素来衡量。不同的排序算法有不同的优点和缺点,适用于不同的应用场景。例如,一些排序算法适用于大数据集,而另一些则更适用于小数据集或几乎已经排好序的数据集。
此博客将详细介绍这些常见的排序算法,包括它们的工作原理,以及适用的场景等。希望通过这个博客,读者可以对这些排序算法有一个全面的理解。
2 算法效率
度量一个程序执行时间的两种主要方法是事后统计方法和事前估算方法。事后统计方法是直接运行程序,观察实际运行时间。这种方法可行,但存在两个问题:首先,为了评测算法性能,我们需要实际运行程序,这在许多情况下是不现实的;其次,实际运行时间会受到许多因素的影响,包括硬件性能、操作系统、输入数据的大小和顺序等,因此得到的结果可能并不准确。
因此,我们通常使用事前估算方法,通过分析算法的时间复杂度来预测其性能。时间复杂度是对算法执行时间增长速度的一种度量,它描述的是输入数据规模n与算法执行时间的关系。在时间复杂度的计算中,我们通常只关心最高次项,因为它在n足够大的时候,将主导整个时间复杂度。
2.1 度量一个程序执行时间两种方法
事后统计方法
这种方法可行,但是存在于两个问题:一是要想对设计的算法运行性能进行评测就需要实际去运行该程序,而是所得时间统计量依赖于计算机硬件、软件等因素,这种方式要在同一台计算机的相同状态下运行,才能比较出哪一个算法速度更快,更好。
事前估算方法
通过分析某一个算法的时间复杂度来判断哪个算法更优,更好。
2.2 时间频度
时间频度:一个算法花费的时间与算法中语句的执行次数成正比,哪一个算法中语句执行次数多,那么他所花费的时间就会多。 一个算法中语句执行次数称之为语句频度或时间频度。记为T(n)
int sum = 0; for(int i=1;i<=n;i++){ sum+=i; } n = 100; T(n) = n+1; n*2/100 T(n) =1;
忽略常数项:
结论:
1、 2n+20 和 2n随着n变大,执行曲线无线接近,20可以忽略
2、 3n+10 和 3n随着n变大,执行曲线无限接近,10可以忽略
忽略低次项:
结论:
1、2n2+3n+10 和2n2随着n变大,执行曲线无线接近,可以忽略3n+10
2、n2+5n+20 和n2 随着n变大,执行曲线无线接近,可以忽略5n+20
忽略系数:
结论:
1、 随着n值变大,5n2+7n 和 3n2+2n执行区间重合,说明这种情况下5和3可以忽略
2、 而n3 +5n和6n3+4n执行区间分离,说明多少次方式关键
2.3 时间复杂度
在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,渐近时间复杂度又称之为时间复杂度。
2.4 常见的时间复杂度
1、常数时间
若对于一个算法,的上界与输入大小无关,则称其具有常数时间
,记作O(1)时间
T(n) =1
2、对数时间
若算法的T(n) =O(logn),则称其具有对数时间
。
int I = 1; while(i<n){ i=i*2; }
3、幂对数时间
对于某个常数k,若算法的T(n) = O((logn)),则称其具有幂对数时间
4、次线性时间
对于一个算法,若其匹配T(n) = o(n),则其时间复杂度为次线性时间
(sub-linear time或sublinear time)。
5、线性时间
如果一个算法的时间复杂度为O(n),则称这个算法具有线性时间
,或O(n)时间。
for(i=1;i<=n;++i){ j=I; j++; }
6、线性对数时间
若一个算法时间复杂度T(n) = O(nlog n),则称这个算法具有线性对数时间
。
7、指数时间
若T(n) 是以 2为上界,其中 poly(n) 是n的多项式,则算法被称为指数时间
常见的时间复杂度对应图
结论:
1、 常见的算法时间复杂度由小到大依次为:O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(nk)<O(2n)<Ο(n!)
2、 尽可能的避免使用指数阶的算法
2.5 平均和最坏时间复杂度
平均时间复杂度是指所有可能的输入实例均以等概率的出现情况下得到算法的运行时间
最坏时间复杂度,一般讨论的时间复杂度均是最坏情况下的时间复杂度,这样做的原因是最坏情况下的时间复杂度是算法在任何输入实例上运行的界限,这就保证了算法的运行时间不会比最坏情况更长。平均时间复杂度和最坏时间复杂度
是否一样,这就需要根据算法不同而不同了。
3. 常见排序算法详解
3.1 基数排序 (Radix Sort)
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。即所有的待比较数值统一设置为同样的数位长度,位数比较短的数前面补零,然后从最地位开始依次进行一次排序,这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
(1) 算法过程
取得数组中的最大数,并取得位数;
对数组按照"指定的位数"进行稳定的排序;
从最低位开始,依次进行一次排序;
从最低位排序一直到最高位排序完成以后, 数组就变成一个有序序列。
这个过程是稳定的,也就是说,两个元素相等,它们的相对顺序不会改变。基数排序的时间复杂度为O(nk),其中n是排序元素的数量,k是数字的最大长度。
(2)代码实现
假设我们要对一个列表进行排序,我们可以使用以下java代码实现基数排序:
import java.util.*; public class RadixSort { public static void radixsort(int arr[], int n) { int m = getMax(arr, n); for (int exp = 1; m/exp > 0; exp *= 10) countSort(arr, n, exp); } static int getMax(int arr[], int n) { int mx = arr[0]; for (int i = 1; i < n; i++) if (arr[i] > mx) mx = arr[i]; return mx; } static void countSort(int arr[], int n, int exp) { int output[] = new int[n]; int i; int count[] = new int[10]; Arrays.fill(count,0); for (i = 0; i < n; i++) count[ (arr[i]/exp)%10 ]++; for (i = 1; i < 10; i++) count[i] += count[i - 1]; for (i = n - 1; i >= 0; i--) { output[count[ (arr[i]/exp)%10 ] - 1] = arr[i]; count[ (arr[i]/exp)%10 ]--; } for (i = 0; i < n; i++) arr[i] = output[i]; } }
总结一下,基数排序的优点在于,对于长度为k的n个数字来说,时间复杂度可以达到线性O(nk),这对于非常大的数据量和大范围的数据来说,是很高效的一种排序算法。然而,基数排序算法实现起来相对较复杂,需要额外的存储空间,这可能是其在实际使用中较少被选择的一个原因。
3.2 冒泡排序 (Bubble Sort)
冒泡排序是一种简单的排序算法。它重复地遍历待排序的序列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历序列的工作是重复地进行直到没有再需要交换,也就是说该序列已经排序完成。