排序的定义
假设数据集合S,一个排序算法sort的功能就是将S的任意元素序列重新排列成一个有序的序列。
显然,对于同一集合的实际数据来说,完全可能存在很多中不同但都有实际意义的序,而且在不同的计算情况下,可能需要对同一个数据序列做不同的排序。因此在对某些数据集做排序时,需要明确说明采用哪种序,比如常用的典型序:整数的“小于等于”、字符串的字典序等。
排序的基本操作、性质和分类
基本操作:(1)比较关键码的操作(这里的关键码指的是数据记录中支持排序的关键码,例如整数或字符串);(2)移动记录的操作,用于调整记录的位置。
性质:(1)稳定性:当待排序的序列中存在多个关键码相同的元素,如果在排序之后它们之间的相对位置不变,则称这种排序算法是稳定的;(2)适应性:排序操作可能会被用于处理不同长短、复杂程度不同的序列,如果一个排序算法对接近有序的序列工作得更快,则称这种算法具有适应性。
基于排序的基本操作方式或特点对排序算法分类:插入排序;选择排序;交换排序;分配排序;归并排序;外部排序(如果待排序的序列全都保存在内存中,就叫内排序,在磁盘等外存上的排序叫做外排序)。
插入类排序算法
逐个考察每个待排序的元素,将每一个新元素插入到前面已经排好序的序列中适当的位置上,使得新序列仍然是一个有序序列。
1、直接插入排序
对于具有n个元素的序列,从第二个元素开始直接到第n个元素,逐个向有序序列执行插入操作,如下图所示。
代码实现:
import java.util.Arrays;
public class DirectInsert {
public static void insertSort(int[] array,int low, int high) {
for(int i=low+1;i<=high;i++) {
System.out.print("Insert number:"+array[i]);
if(array[i]<array[i-1]) {
int temp = array[i];
array[i] = array[i-1];
int j = i-2;
for(;j>=low && temp < array[j];j--) {
array[j+1] = array[j];
}
array[j+1] = temp;
}
System.out.println(" result is"+Arrays.toString(array));
}
System.out.println("final:"+Arrays.toString(array));
}
public static void main(String[] args) {
int[] array = {26,53,48,11,13,48,32,15};
insertSort(array,0,array.length-1);
}
}
运行结果:
直接插入排序的时间复杂度为O(n^2),并且是一个稳定的排序方法。
2、折半插入排序
由于插入排序中需要检索元素的插入位置,而且是有序的部分序列里检索,因此可通过二分查找来检索插入位置。
代码实现:
public void binInsertSort(int[] array,int low,int high) {
for(int i=low+1;i<=high;i++) {
System.out.print("Insert number:"+array[i]);
int temp = array[i]; //待插入的元素
int left = low, right = i-1;
while(left<=right) { //折半查找确定位置
int mid = (left+right)/2;
if(array[mid]<temp) {
left = mid+1;
} else {
right = mid-1;
}
}
//移动元素
for(int j=i-1;j>right;j--) {
array[j+1] = array[j];
}
array[right+1] = temp; //进行插入
System.out.println(" result is"+Arrays.toString(array));
}
System.out.println("final:"+Arrays.toString(array));
}
尽管折半插入排序减少了元素比较的次数,但是并没有减少元素移动的次数,因此折半插入排序的时间复杂度仍为O(n^2)。
3、希尔排序
首先将待排序的元素分为多个子序列,使得每个子序列的元素相对较少,对各个子序列分别进行直接插入排序,待整个待排序列基本有序后,再对所有元素进行一次直接插入排序。
以序列{26,53,67,48,57,13,48,32,60,50}为例,排序过程如下:
- 选择一个步长序列,步长逐渐减小,最后一个步长必须为1,假设我们选择的步长序列为{5,3,1}。
- 按照步长序列个数k,对待排序元素序列进行k趟排序,因为我们假设的步长序列长度为3,因此需要进行3趟排序。
- 每趟排序,根据对应的步长t,将待排序列分割成t个子序列,分别对各个子序列进行直接插入排序,例如第一个步长为5,所以在第一趟排序中将待排序列分成5个子序列{26,13},{53,48},{67,32},{48,60},{57,50},对它们分别进行直接插入排序,然后以此类推,进行二、三趟排序,得到最终有序的序列。
希尔排序如下图所示:
代码实现:
//对于步长为t的所有序列进行直接插入排序
public void shellInsert(int[] array,int low,int high,int t) {
for(int i=low+t;i<=high;i++) {
if(array[i]<array[i-t]) {
int temp = array[i];
int j = i-t;
for(;j>=low && temp<array[j];j-=t) {
array[j+t] = array[j];
}
array[j+t] =temp;
}
}
}
public void shellSort(int[] array,int low,int high,int[] tArray) {
for(int k=0;k<tArray.length;k++) {
shellInsert(array,low,high,tArray[k]); //第k趟排序
System.out.println(k+"th turn:"+Arrays.toString(array));
}
System.out.println("final:"+Arrays.toString(array));
}
从直观上看希尔排序的效率会高于直接插入排序,但是其时间复杂度与步长序列的选取密切相关,如何选择步长序列才能使得希尔排序的时间复杂度达到最佳,还是一个有待解决的问题。
在实际应用中,应使得步长序列中的步长互质。
参考
数据结构与算法(JAVA语言版—周鹏)
如有侵权,请联系作者删除