排序的分类标准有多种,如果按照排序过程中一句的不同原子对内部排序方法进行分类,大致可以分为:插入排序、交换排序、选择排序和基数排序。
// 直接插入排序 java
/**
* 直接插入排序 空间复杂度为1 时间复杂度n*n
* @param arr
*/
public void InsertSort(int[] arr){
if(arr == null || arr.length == 0){
throw new RuntimeException("wrong parameters");
}
if(arr.length == 1)
return;
for(int i = 1;i<arr.length;i++){
int tmp = arr[i];
for(int j = i-1;j>=0;--j){
if(tmp > arr[j]){
arr[1+j] = tmp;
break;
}
arr[1+j] = arr[j];
}
}
}
直接插入排序优点是代码简单,适合n不大的情况。直接插入排序主要包括两个步骤:“比较”和“移动”,可以从这两个方面进行优化。
折半插入排序
相比于直接插入排序依次遍历有序部分查找最终插入位置,折半插入排序使用折半查找方式提高查找插入位置的速度是。
/**
* 折半插入排序,使用折半查找方法在有序列表中找到待插入元素的位置
* @param arr
*/
public void BInsertSort(int[] arr){
if(arr == null || arr.length == 0){
throw new RuntimeException("wrong parameters");
}
if(arr.length == 1)
return;
for(int i = 1;i<arr.length;++i){
int tmp = arr[i];
// 折半查找
int low = 0;
int high = i-1;
while(low <= high){ // low和high当前位置元素并不能确定与tmp的大小关系,所以<=
int mid = (low+high)/2;
if(arr[mid] < tmp){
low = mid+1;
}else {
high = mid-1;
}
}
int j = i-1;
while(j>high){
arr[1+j] = arr[j];
}
arr[high+1] = tmp;
}
}
折半插入排序相比直接插入排序减少了查找元素的时间,但是移动元素方式和直接插入排序移动元素方式相同,所以时间复杂度任然是n*n。
2-路插入排序
/2-路插入排序
public void twoInsert1(int[] arr){
if(arr == null || arr.length == 0){
throw new RuntimeException("wrong parameters");
}
if(arr.length == 1)
return;
int len = arr.length;
int[] tmp = new int[arr.length];
tmp[0] = arr[0];
int end = 0;
int start = 0;
// 将arr中的元素插入到tmp数组中
for(int i = 1;i<arr.length;++i){
if(arr[i] >= tmp[end]){
tmp[++end] = arr[i];
continue;
}else if(arr[i] <= tmp[start]){
tmp[(start+len-1)%len] = arr[i];
continue;
}
// 在start的end范围内使用二分查找,此时low和high是逻辑上的坐标
int low = end;
int high = start+len;
int mid = 0;
while(low < high){
mid = (low + high)/2;
if(tmp[mid%len] <= arr[i]){
low = mid + 1;
}else{
high = mid - 1;
}
}
// 移动元素,tmpIndex 是逻辑上的坐标
int tmpIndex = start + len + 1;
while(tmpIndex > high){
tmp[tmpIndex%len] = tmp[--tmpIndex % len];
}
tmp[low % len] = arr[i];
}
// 将tmp中的元素放入arr中
for(int k =0;k<len;k++){
tmp[k] = tmp[(k+start)%len];
}
}
移动元素的次数大约是n*n/8,如果tmp[0]值是最大值或者最小值,将失去2路插入排序的优势。
参考:二路插入排序
表插入法(Java中有结构体吗?)
为了减少移动次数,可以将待排序数组中的元素以链表的形式组织起来,然后将未排序元素插入链表中。表插入法的思路是以表的结构表示链表,每次插入数据只需要更改相应坐标即可。
/**
* 表插入法,以表的形式表示链表,从而减小移动元素的次数,但是比较次数没法少,复杂度n*n。
* @param arr 数组所有元素的next为空
*/
public void tableInsert(TableNode[] arr){
if(arr == null || arr.length <= 1){
throw new RuntimeException("wrong exception");
}
if(arr.length == 2)
return;
// 构建有序的循环链表
arr[0].value = Integer.MAX_VALUE;
arr[0].next = 1;
arr[1].next = 0;
// 将未排序的元素插入链表中
for(int i = 2;i<arr.length;i++){
int pre = 0;
int cur = arr[0].next;
while(arr[i].value < arr[cur].value && cur!=0){
pre = cur;
cur = arr[cur].next;
}
arr[pre].next = i;
arr[i].next = cur;
}
}
最终得到的是一个以表结构表示的链表,如果要实现对表中的元素进行随机查找,需要将对表进行重新排序。
/**
* 将表链表中的元素从小到大排序
* @param arr
*/
public void arrangeLinkTable(TableNode[] arr) {
if (arr == null || arr.length <= 1) {
throw new RuntimeException("wrong exception");
}
if (arr.length == 2)
return;
int cur = arr[0].next;
int next = 0;
for(int i = 0;i< arr.length-1;++i){// i为带插入元素在数组中的位置
// 找出链表中下一个节点
next = arr[cur].next;
while(next <= i){
next = arr[next].next;
}
// 交换
TableNode tmp = arr[cur];
arr[cur] = arr[++i];
arr[i] = tmp;
arr[i].next = cur;
cur = next;
}
}
希尔排序
如果待排序元素基本有序,直接插入排序具有很高的效率,基于这点,希尔排序将整个待排序数组分成若干个子序列进行直接插入排序,当待排序列基本有序时候再对全体进行一次直接插入排序。需要注意的是希尔排序子序列构成不是逐段分隔,而是将相隔某个增量的记录组成一个子序列。
/**
* 对arr进行shell排序
* @param arr
* @param dk
*/
public void shellInsert(int[] arr,int dk){
for(int i = dk;i<arr.length;++i){
if(arr[i] < arr[i-dk]){
// 需要进行插入排序
int tmp = arr[i];
int k = i;
while(arr[k] <= arr[k-dk] && k>=0){
arr[k] = arr[k-dk];
k = k-dk;
}
arr[k+dk] = tmp;
}
}
}
/**
* 希尔排序,offset数组中最后一个增量必须是1,并且是递减的
* @param arr
* @param offset
*/
public void shellSort(int[] arr,int[] offset){
for(int i = 0;i<offset.length;i++){
shellInsert(arr,offset[i]);
}
}
希尔排序的复杂度与增量序列的函数,需要注意的是:应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须是1。
/**
* 将数组的第一个元素放在数组中的最终位置,返回坐标
* @param arr
* @param low
* @param high
* @return
*/
public int getPartition(int[] arr,int low ,int high){
int slow = low -1;
int fast = low;
swap(arr,low,high);// 以high坐标的元素为中间值
while(fast < high){
if(arr[fast] <= arr[high]){
++ slow;
if(slow != fast){
swap(arr,++slow,fast);
}
}
}
swap(arr,++slow,high);
return slow;
}
public void quickSort1(int[] arr,int low ,int high){
if(low >= high)
return;
int pivort = getPartition(arr, low, high);
quickSort1(arr,low,pivort-1);
quickSort1(arr,pivort+1,high);
}
优化:在每次调用getPartition
的时候设置isSwitched变量,如果从slow到high没有发生交换,则在低端子表不用进行排序。