排序算法复杂度分析
文章开头先推荐一本书《程序员实用算法》(Andrew Binstock / John Rex)<book.douban.com/subject/3923731/>
排序算法在算法中占着很重要的地位,很多算法的实现都是基于排序算法的(如搜索算法和合并算法)。所以排序算法也是笔试面试中必考内容。下面是wiki上总结的11种常用排序算法:
1 Simple sorts
1.1 Insertion sort(插入排序)
1.2 Selection sort(选择排序)
2 Efficient sorts
2.1 Merge sort (归并排序)
2.2 Heapsort (堆排序)
2.3 Quicksort (快速排序)
3 Bubble sort and variants
3.1 Bubble sort (冒泡排序)
3.2 Shell sort (希尔排序)
3.3 Comb sort (梳排序)
4 Distribution sort
4.1 Counting sort(计数排序)
4.2 Bucket sort(桶排序)
4.3 Radix sort(基数排序)
1、插入排序(直接插入排序、希尔排序);
2、交换排序(冒泡排序、快速排序);
3、选择排序(直接选择排序、堆排序);
4、归并排序;
5、基数排序;
交换排序,就是不断的交换,一直到适合为止。选择排序主要是不断的选择,然后才交换.
稳定排序和非稳定排序
稳定排序即:保证排序前两个相等的数据其在序列中的先后位置顺序与排序后它们两个先后位置顺序相同。再简单具体一点,如果A i == A j,Ai 原来在 Aj 位置前,排序后 Ai 仍然是在 Aj 位置前。
![]()
An example of stable sorting on playing cards. When the cards are sorted by rank with a stable sort, the two 5s must remain in the same order in the sorted output that they were originally in. When they are sorted with a non-stable sort, the 5s may end up in the opposite order in the sorted output.
算法稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。
算法的复杂度分析
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n}=0(f(n))。它表示随问题规模n的增大,算法执行时间的埔长率和 f(n)的埔长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f( n)是问题规横n的某个函数。
这样用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。
常见的算法时间复杂度以及他们在效率上的高低顺序:
O(1) 常数阶 < O(logn) 对数阶 < O(n) 线性阶 < O(nlogn) < O(n^2) 平方阶 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }
下面用常见的冒泡排序分析复杂度:
void bubble_sort(int* a, int size)
{
_DEBUG_ENTER(bubble_sort);
int i,j;
for(i=1; i<size-1; i++)
for(j=0; j<size-i; j++)
{
if(a[j]>a[j+1]) //执行1+2+...+n次
{
swap_array(&a[j],&a[j+1]);
}
}
}
分析: 执行总次数 f(n) = 1+2+...+n = (n+1)*n/2, 用大O记法为O( n^2 ),
int num1, num2;
for(int i=0; i
分析: 注释(1)一句执行次数的数量级最高,故只计算这一行的次数,为nlogn(底数为2, 这里忽略), O(n*logn)
下图是常用排序的时间复杂度:
![排序复杂度]()
下面介绍六种常用排序:
插入排序:直接插入排序,希尔排序;
交换排序:冒泡排序, 快速;
选择排序:直接选择, 堆排序;
1 插入排序
1.1 直接插入排序(insertion sort)
将所有待排序数据分成两个序列,一个是有序序列S,另一个是待排序序列U,初始时,S为空,U为所有数据组成的数列,然后依次将U中的数据插到有序序列S中的合适位置,直到U变为空。
适用场景:当数据已经基本有序时,采用插入排序可以明显减少数据交换和数据移动次数,进而提升排序效率。
最好,最坏,平均情况:
最好:已经排好顺序的集合,这样只需要线性时间即遍历一次集合,每次只需要比较当前元素与前一个元素的大小问题,时间复杂度O(n);
最坏:即刚好与所要的顺序相反,时间复杂度为O(n^2);
平均:时间复杂度也是O(n^2);
插入排序算法流程如下图所示:
![insertion_sort]()
1.2 希尔排序(shell sort)
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;
当元素基本有序时,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比O(N^2)好一些。
2 交换排序
2.1 冒泡排序(bubble sort)
遍历要排序的数列(从小到大排序),一次比较相邻两个元素,如果他们的顺序错误就把他们交换过来。第一次遍历完后,最大的元素已经被交换到最后(形成有序序列),然后再对剩下的size-1个继续进行交换。伪码如下:
冒泡排序效率非常低,效率还不如插入排序。数据量大时效率低,对于顺序颠倒的序列效率最低。
i∈[0,N-1) //循环N-1遍
j∈[0,N-1-i) //每遍循环要处理的无序部分
swap(j,j+1) //两两排序(升序/降序)
![swap_sort]()
2.2 快速排序(quick sort)
快速排序(类似于归并算法)是一种分而治之算法。首先它将列表分为两个更小的子列表:一个大一个小。然后递归排序这些子列表。下面就用分而治之的方法来排序子数组A[p...r];
步骤:
Divide:从列表中取一个元素作为支点,将数组分为A[p‥q - 1] andA[q + 1‥r] ,A[p ‥ q - 1]中每一个元素都小于A[q] , 而A[q + 1 ‥ r]中每个元素都大于A[q].计算出支点实际存在数组中的位置,即q的值就是PARTITION操作。
Conquer:通过递归的方法对两个数组进行排序。
Combine:因为子数组是原地处理的(即in-place),所以不需要合并他们,A[p....r]已经是排好序的。
QUICKSORT(A, p, r)
if p < r
then q ← PARTITION(A, p, r)
QUICKSORT(A, p, q - 1)
QUICKSORT(A, q + 1, r)
3 选择排序
3.1 直接选择排序(selection sort)
遍历要排序的数列,记录最大的元素直到序列尾,交换序列尾和最大的元素,这样序列尾形成有序序列。继续对剩下的size-1个序列进行遍历,记录最大的元素。每一轮过后有序序列都会增加,直到有序序列增长到n-1。
3.2 堆排序(heap sort)



浙公网安备 33010602011771号