排序是数据结构与算法中不可绕过的部分。所谓的排序,就是按照某种约定的比较规则,将一个序列排成某种顺序。例如将n个整数${a\text{1,}a\text{2,...,}an}$按大小顺序排成一个非降序列,使$a1<=a2...<=an$。

排序就是让序列从无序到有序的操作。

排序算法的分类

可以将排序算法分为比较排序算法和非比较排序算法两大类。比较类算法顾名思义,就是通过元素之间的比较来确定元素再序列中的位置,这是一般人比较容易想到的排序思路,经典算法包括选择排序,交换排序,插入排序等,我也会首先介绍这类排序算法。还有另一类排序通过其他手段,避免了元素之间的比较,这类算法在某些情况下,可以突破比较类排序算法时间复杂度的极限O(nlogn),达到O(n)。除此之外,根据待排序元素所在存储空间的位置,可以将排序算法分为内部排序和外部排序,内部排序在计算机内存当中进行,而外部排序需要访问访问计算机外存。这部分内容我可能以后会进一步介绍。

排序算法的稳定性

排序算法的稳定性考虑的是具有相同值的元素在排序前后的次序是否发生变化,如果未发生变化,则说明该排序算法是稳定的,否则是非稳定的。例如,在原序列a中,a[i]=a[j](i>j),在排序过后的序列中,a[j]=a[i](j<i),排序过程中a[i]和a[j]的相对位置发生了变化,所以这种排序算法是不稳定的算法。

三大简单排序算法

学习排序算法一般从三大简单排序算法开始,即直接选择排序,直接插入排序和冒泡排序。这三类排序算法的思路都比较简单,都是将序列分为排序完成的序列和未完成的序列两部分,然后从未完成的元素中挑选一个,添加到已完成序列中。缺点是他们的时间复杂度都只能达到O(n2),当数据量较大时,排序的效率很低下。

1.直接选择排序

直接选择排序的思路是将未排序序列中最大(或最小)的元素挑选出来,添加到排序完成的序列的末尾。下面我们以数组{72,34,90,5,88,58,71,27,24,3}实现降序排序进行讲解。

初始状态下,整个序列都未排序。

 

然后,从未排序序列中挑出最大的元素,并和第一个未排序元素交换。

 

重复此操作

 

直到所有元素都有序为止

直接选择排序算法的代码实现如下

 1 void selectSort(int a[],int length)
 2 {
 3     for(int i=0;i<length;i++)
 4     {
 5         int index = i;
 6         for(int j=i;j<length;j++)
 7             index = a[index]<a[j]?j:index;
 8         swap(a[index],a[i]);
 9     }
10 }

 

2.直接插入排序

直接选择排序的思路是将未排序序列中的第一个元素和有序序列中的元素逐个比较,以确定该元素在有序序列中的位置,为求方便,一般从有序序列的末尾向前比较。

初始状态下,整个序列都未排序。

 

 

将未排序元素中的第一个元素作为待排序元素,和已排序元素比较。

因为当前没有已排序元素,所以该元素直接作为第一个已排序元素。

然后重复这个过程,此时待排序元素为34,将它和已排序元素进行比较,因为34<72,所以当前位置即为它在有序元素中的位置。


继续排序,当前元素为90,90>34,所以交换这两个数,即将90插入到34之前,然后再将90和有序序列中更大的元素比较,发现90最终会插入到有序序列的第一个。

重复这个过程,直到所有未排序的元素都插入到已排序的元素当中。

 

 

直接插入排序代码如下:

 1 void insertSort(int a[],int length)
 2 {
 3     for(int i=1;i<length;i++)
 4     {
 5         for(int j=i;j>0;j--)
 6         {
 7             if(a[j]>a[j-1]) swap(a[j],a[j-1]);
 8             else break;
 9         }
10     }
11 }

 

3.冒泡排序

冒泡排序是一种基于交换的排序方法,它的思想是每次都选取未排序序列中的第一个数,然后让这个数不断地和其他未排序序列中数比较比较,如果小于则交换这两个数,当一轮比较结束以后,这个数就是未排序序列中最小的数。整个排序过程就像冒泡泡一样,泡泡从水底浮到水面上,越来越大,越来越大(当然这里我们降序排序,它会越来越小)。然后将这个数添加到有序序列中,它就成了有序序列中最大的数。

 

 

和直接插入排序以及直接选择排序相同,冒泡排序也是从无序集合开始。

 

 

 从第一个待排序元素开始,和它的后一个比较

 

如果该元素大于它的后一个元素,则直接向后移动。

如果该元素小于它的后一个元素,则交换这两个元素。

重复这个过程,直到该元素到达未排序元素的尾部

然后把它添加到已排序元素中。




一轮排序确定了一个元素在有序序列中的位置,然后不断重复这一过程,直到所有元素的在有序序列中的位置全都被确定下来。

 


冒泡排序的代码如下:

1 void bubbleSort(int a[],int length)
2 {
3     for(int i=0;i<length-1;i++)
4     {
5         for(int j=0;j<length-i-1;j++)
6             if(a[j]<a[j+1]) swap(a[j],a[j+1]);
7     }
8 }