1.插入排序
- 函数代码如下:
public void insertSort(int length){
int k = 0;
for(int present = 2; present <= length; present++){
this.array[0] = this.array[present];
for(k = present-1; array[k] > array[0]; k--){
array[k+1] = array[k];
}
if(k < present - 1){
array[k+1] = array[0];
}
}
算法分析
待排序数组将从数组的下标为1处开始储存,array[0]将在每次循环前,储存待出入元素。这样做相较于普通插入排序有两个优点:
1.不必在每次比较后交换之前元素与待比较元素,只需将之前比待比较元素移动到之后空出的位置上,待循环结束后再将待比较元素放在循环截至处便可。
2.待比较元素放在数组第一位可做哨兵只用,而后的循环中无需再做数组是否越界的检测。由于插入排序每次比较对象均为相邻元素,故插入排序是一种稳定的排序算法。
时间复杂度分析
算法包含两重循环,步长均为n,故算法的时间复杂度为O(n^2)。
2.选择排序
- 函数代码如下:
public void sectionSort(int length){
int k = 0;
int max = 0;
for(int i = 0;i <= length; i++){
max = array[0];
int maxnum = 0;
for(k = 0;k <= length - i; k++){
if(array[k] > max){
max = array[k];
maxnum = k;
}
}
swap(maxnum, length-i);
}
}
算法分析
选择排序的主要思想是在每一趟循环中,找出未排序元素中的最大值或最小值,而后将其交换到已排好序的序列的末尾。第二层循环 K < length - i的终止条件可以减少不必要的扫描。
选择排序的主要优点是交换次数少,因而适用于关键字信息量较大、占用存储空间很大的记录。时间复杂度分析
算法包含两重循环,步长均为n,故算法的时间复杂度为O(n^2)。
3.希尔排序
- 函数代码如下:
public void shellSort(int length){
int k;
for(int increment = length/3 + 1; increment > 0; increment = increment/3 + 1){
for(int i = increment + 1; i <= length; i++){
array[0] = array[i];
for(k = i-increment; k > 0&&array[k] > array[0]; k = k-increment){
array[k+increment] = array[k];
}
if(k < i-increment){
swap(k+increment,0);
}
}
if(increment == 1){
break;
}
}
}
public void swap(int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
算法分析
希尔排序又名增量递减算法,利用了插入排序对于最好情况的极佳处理。希尔排序增量不断递减,使待排序序列越来越接近有序的情况;同时直接比较交换下标相距一个增量的两个元素可以使元素更快地接近最终位置。应当注意最后一次排序增量务必为1.希尔排序的性能主要与增量的大小选取有关,最好能在每一次循环中,使得总的序列更加接近有序。大量研究表明,当增量序列为increment[k] = 2^(t-k+1) -1时,可以获得不错的效果。一般情况下使用increment = increment/3+1的增量序列即可。
由于希尔排序比较的对象是下标相隔一个增量的元素,故而希尔排序是一种不稳定的排序算法。
时间复杂度分析
在本段代码实现中,增量序列的选择为increment = increment/3+1,显然时间复杂度为O(n^3/2)。希尔排序的时间复杂度介于O(nlogn)~O(n^2)之间,取决于增量序列的选择。最坏情况为插入排序的复杂度。
4.归并排序
- 函数代码如下:
public void merge(int begin, int end){
int mid = (begin + end)/2;
int i = begin;
int j = end;
int k;
if(begin == end) return;
else{
merge(begin,mid);
merge(mid+1,end);
}
for(i = begin;i < mid+1;i++){
temp[i] = array [i];
}
//将第二个子序列逆置存放
for(j = 1;j <= end-mid; j++){
temp[end-j+1] = array[j+mid];
}
int a = array[begin];
int b = array[end];
for( k = begin,i = begin,j = end;k <= end;k++ ){
if(a > b){
array[k] = temp[j--];
b = temp[j];
}else{
array[k] = temp[i++];
a = temp[i];
}
}
}
算法分析
归并算法通过递归的思想,先将排序序列拆分为不可拆分的子序列,而后两两合并,合并为新的有序的子序列。递归的基础情形为begin == end。
代码为优化后的归并排序,在将待排序序列拷贝到开辟的临时数组空间
时,将第二个子序列逆置存放,而后合并扫描则从数组一头一尾交替进行。较普通的归并排序有几点优点:
1.在循环合并中,两个子数组相互为监视哨,避免了在循环中对数组是否越界的检查。
2.避免了在一个子序列处理完后边跳出循环进行另一子序列拷贝的情况。在其中某一个子序列处理结束后,工作指针将前移或后移指向另一个子序列的末尾。显然将继续另一未处理完的序列的拷贝而无需跳出循环。小于O(n^2)的排序中唯一的稳定排序。因此在看重稳定性且对空间要求不高的情况下,当使用归并排序。
可通过改递归为迭代的方法减少归并排序的栈开销。时间复杂度分析
归并排序需要将1~n中所有的记录扫描一遍,因此耗费O(n)的时间,而由二叉树的深度可知,整个归并排序需要进行O(logn)次。故最好、最坏及平均时间复杂度均为O(nlogn)。
但归并排序具有无法避免的空间复杂度的开销,这是归并排序无法回避的缺点。
5.快速排序
- 函数代码如下:
public void quickSort(int begin,int end,int length){
int axis = 0;
setAxis(begin,end);
axis = disposal(begin, end, length);
if(axis-1 > begin)
quickSort(begin,axis-1,axis-begin);
if(end > axis+1)
quickSort(axis+1,end,end-axis);
}
//选择轴心
//轴心将位于每组数据的第一位
public void setAxis(int begin,int end){
if(end - begin > 3){
int center = (begin + end)/2;
if(array[begin] > array[end])
swap(begin, end);
if(array[end] > array[center])
swap(end, center);
if(array[center] > array[begin]){
swap(begin,center);
}
}
}
//整理顺序
//并将轴值归位,返回值为轴值
public int disposal(int begin,int end,int length){
int i = begin + 1;
int j = end;
if(length == 2&&array[begin]<array[end]){
swap(begin,end);
}else{
while(i < j){
while(array[j] > array[begin])
j--;
while(array[i] < array[begin]&&i < end)
i++;
swap(i,j);
}
}
swap(i,j);
swap(begin,j);
return j;
}
算法分析
快速排序算法主要利用
了分而治之的思想,通过每次选择的枢轴将序列一分为二,通过递归再分别进行快速排序。算法中递归的基础情形为begin == end.
为保证轴值能准确的将数组分做大于枢轴的元素序列和小于枢轴的元素序列,使用两指针从一头一尾进行扫描,将序列左侧大于枢轴的元素和序列右侧小于枢轴的元素交换直至两指针相遇,此时交换指针相遇位置的元素和枢轴。利用快速排序中枢轴可将序列分为大于枢轴元素的序列和小于枢轴元素的序列,将算法稍加改进后可用于寻找第K大元素的问题。
时间复杂度分析
快速排序的最好时间复杂度与平均时间复杂度均为O(nlogn),最坏时间复杂度为O(n^2)。可以通过改进枢轴的选取方式来尽量避免最坏情况的出现。