1. 排序( sorting) 的功能是将一个数据元素的任意序列,重新排列成一个按关键字有序的序列。其确切的定义为:
假设有n个数据元素的序列{R1 , R2 , … , Rn},其相应关键字的序列是{K1 , K2 , … , Kn} ,通过排序要求找出下标 1 , 2 , … , n的一种排列p1 , p2 , … , pn,使得相应关键字满足如下的非递减(或非递增)关系Kp1 ≤ Kp2 ≤ … ≤ Kpn这样,就得到一个按关键字有序的纪录序列{ Rp1 , Rp2 , … , Rpn }。
根据排序时待排序的数据元素数量的不同,使得排序过程中涉及的存储器不同,可以将排序方法分为两类。一类是整个排序过程在内存储器中进行,称为内部排序;另一类是由于待排序元素数量太大,以至于内存储器无法容纳全部数据,排序需要借助外部存储设备才能完成,这类排序称为外部排序。
2. 通常,在排序的过程中需要进行两种基本操作: ⑴比较两个关键字的大小; ⑵将一个关键字从一个位置移动到另一个位置。第一种操作对大多数排序方法来说都是必要的,而后一种操作可以通过改变记录的存储方式来予以避免。
在讨论排序算法之前,我们必须要提及一下我们的前提:
1).大多数情况下为简单起见,讨论从小到大的整数排序。
2).只讨论基于比较的排序,即 > , < , = 均是有定义的。
3).只讨论内部排序。
4).稳定性:任意两个相等的数据,排序前后的相对位置不发生改变。
5).没有一种排序在任何情况下的都是表现最好的,所以才有了如此多的排序算法。
3.直接插入排序
直接插入排序是一种最简单的插入排序方法,它的基本思想是:仅有一个元素的序列总是有序的因此对n个记录的序列,可从第二个元素开始直到第 n 个元素,逐个向有序序列中执行插入操作,从而得到n个元素按关键字有序的序列。
一般来说,在含有 j-1 个元素的有序序列中插入一个元素的方法是:从第 j-1 个元素开 始依次向前搜索应当插入的位置,并且在搜索插入位置的同时可以后移元素,这样当找到适 当的插入位置时即可直接插入元素。
以关键字序列{ 26 , 53 , 48 , 11 , 13 , 48 , 32 , 15}为例:
public void Insert_Sort(int A[], int N) {
for (int P = 1; P < N; P++) {
int temp = A[P];
int i = P;
for (; i > 0 && A[i - 1] > temp; i--)
A[i] = A[i - 1];//留出空位
A[i] = temp;//赋值
}
for(int i = 0 ; i < N; i++){
System.out.print(A[i] + " ");
}
}
【效率分析】
空间效率: 仅使用一个辅存单元。
时间效率: 假设待排序的元素个数为 n,则向有序表中逐个插入记录的操作进行了 n-1趟,每趟操作分为比较关键码和移动记录,而比较的次数和移动记录的次数取决于待排序列按关键码的初始排列。
⑴ 在最好情况下,即待排序序列已按关键字有序,每趟操作只需 1 次比较 0 次移动。此时有:
总比较次数 = n-1 次
总移动次数 = 0 次
⑵ 在最坏情况下,即待排序序列按关键字逆序排序,这时在第 j 趟操作中,为插入元素需要同前面的 j 个元素进行 j 次关键字比较,移动元素的次数为 j+1 次。此时有:
总比较次数 = = n(n-1)/2 次
⑶ 平均情况下:即在第 j 趟操作中,插入记录大约需要同前面的 j/2 个元素进行关键字
比较,移动记录的次数为 j/2+1 次。此时有:
总比较次数 ≈ n2/4 次
总移动次数 ≈ n2/4 次
由此,直接插入排序的时间复杂度为O(n2),并且是一个稳定的排序方法。
4.希尔排序
希尔排序又称为“缩小增量排序”,它也是一种属于插入排序类的排序方法,是一种对直接插入排序的改进,但在时间效率上却有较大的改进。从对直接插入排序的分析中知道,虽然直接插入排序的时间复杂度为O(n2),但是在待排序元素序列有序时,其时间复杂度可提高至O(n)。
由此可知在待排序元素基本有序时, 直接插入排序的效率可以大大提高。从另一方面看,由于直接插入排序方法简单,则在n值较小时效率也较高。希尔排序正是从这两点出发,对直接插入排序进行改进而得到的一种排序方法。
希尔排序的基本思想是:首先将待排序的元素分为多个子序列,使得每个子序列的元素个数相对较少,对各个子序列分别进行直接插入排序,待整个待排序序列“基本有序”后,再对所有元素进行一次直接插入排序。
根据上述排序思想,下面我们给出希尔排序的排序过程:
⑴ 选择一个步长序列t1, t2, …, tk,其中ti>tj( i<j), tk=1 ;
⑵ 按步长序列个数 k,对待排序元素序列进行 k 趟排序;
⑶ 每趟排序,根据对应的步长ti,将待排序列分割成ti个子序列,分别对各子序列
进行直接插入排序。
·在每趟排序过程中子序列的划分并不是简单的逐段划分,而是将间隔某个步长的元素组成一个子序列。如此,在对每个子序列进行简单插入排序时,关键字较小的元素就不是一步一步向前移动,而是按步长跳跃式向前移动,从而使得在进行最后一趟步长为 1 的插入排序时,整个序列已基本有序,此时,只需要作比较少的比较和移动即可完成排序。
public void Shell_Sort1(int[] A, int N) {
int temp;
for (int k = N / 2; k > 0; k /= 2) {// 希尔增量序列
for (int i = k; i < N; i++) {// 每相隔k/2个增量进行操作
for (int j = i; j >= k; j -= k) {
if(A[j] < A[j - k]){
temp = A[j - k];
A[j - k] = A[j];
A[j] = temp;
}
}
}
}
for(int i = 0 ; i < N; i++){
System.out.print(A[i] + " ");
}
}
public void Shell_Sort2(int[] A, int N) {
for (int k = N / 2; k > 0; k /= 2) {// 希尔增量序列
for (int i = k; i < N; i++) {// 插入排序
int temp = A[i];
int j = i;
for (; j >= k && A[j - k] > temp; j -= k)
A[j] = A[j - k];
A[j] = temp;
}
}
for(int i = 0 ; i < N; i++){
System.out.print(A[i] + " ");
}
}
通过前面的分析,从直观上我们可以预见希尔排序的效率会较直接插入排序要高,然而对希尔排序的时间复杂度分析是一个复杂的问题,因为希尔排序的时间复杂度与步长序列的选取密切相关,如何选择步长序列才能使得希尔排序的时间复杂度达到最佳,这还是一个有待解决的问题。实际的应用中,在选择步长序列时应当注意:应使步长序列中的步长值互质,并且最后一个步长值必须等于 1。
5.后面我们将继续讨论排序算法··交换类排序