/**
* 一些结论:
* (1)所有的简单排序,即On2的,都是稳定排序。稳定排序的特征是比较操作存在于相邻的元素。
* (2)比On2快的,除了归并,其余都不稳定,但是归并需要On空间
*/
public class Sort {

//直接排序,最基本的思路就是在前i个元素已经排好的基础上,把第i+1个元素插入到合适的位置,但插入第i个时,缓存为t,向前遍历,只要大于就后移,直到找到第一个小于等于的元素k,然后k+1设置为t
public void straightInsertSort(int nums[]){
if (nums == null) {
return;
}
for(int i = 1; i < nums.length; i++){
int temp = nums[i];
int j = i - 1;
while(j >= 0 && nums[j] > temp){ //稳定
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = temp;
}
}

//二分插入排序,在查找插入位置时使用二分法,设置low和high,直到low>high,这时要么是因为high - 1要么因为low + 1,结果真正的插入位置就是high+1处,因此high+1到i-1的所有元素后移。注意在循环体内,当中间小于t时,往左边找,大于等于往右边,这样当l==h时,右边的全大于,左边的小于等于,那么此时的元素是小于等于t的最大元素,因此在这个元素后面插入。
//插入排序复杂度还是On2,因为sort分两种操作,比较和移动,比较是logn,但是移动仍然是On2
public void binaryInsertSort(int[] nums){
for(int i = 1; i < nums.length; i++){
int temp = nums[i], low = 0, high = i - 1;
while(low <= high){
int m = (low + high) / 2;
if (temp < nums[m]) {
high = m - 1;
}else {
low = m + 1;
}
}
for(int j = i ; j > high + 1; j--){
nums[j] = nums[j - 1];
}
nums[high + 1] = temp;
}
}

//希尔排序,思路是多次带gap的直接插入排序。最外层的while循环是控制gap的,每一次while循环都是一次带gap的插入排序,在每一次插入排序中,还是会遍历所有的元素,但是在向前插入的操作中是跳跃gap进行的。因此看上去像是有gap组子排序。
//关于其的时间复杂度分析尚未得出,但是可以知道介于On和On2之间,所以是插入排序中最好的。不稳定。
//关于选取increment也没有定论,但是有两条规则,第一是最后一次必须是1,第二个是相邻两次不要互为倍数。比较推荐的有2^k-1,3^k-1等。不稳定。
//关于shell排序的有点有一个比较直接的分析,第一趟排序,元素基本无序,但是因为gap很大,所以操作次数很少,最后一次,基本接近有序,虽然gap是1,但操作次数也不多。
public void shellSort(int[] nums){
int length = nums.length;
int increment = 1;
while(increment * 2 + 1 < length){
increment = increment * 2 + 1;
}
while(increment >= 1){
for(int i = increment; i < length; i++){
int temp = nums[i];
int j = i - increment;
while(j >= 0 && nums[j] > temp){
nums[j + increment] = nums[j];
j -= increment;
}
nums[j + increment] = temp;
}
increment = (increment - 1) / 2;
}
}

//快速排序目前是比较快的方法,复杂度是平均Onlogn。其思路是一个递归,包含不断地分解过程。每一次partition都会以第一个元素为准,把low到high的元素进行移位,使得左边比key小,右边比k大。
//这个partition本身就可以是一个算法题目,做法是通常选第一个为k,low和high指针分别交替靠拢,以遍历到所有之间的元素。一个移动时,另一个指向槽,最终high=low,均指向槽,把槽赋值为k即可。
//一直递归下去,知道包含一个元素时返回。快排不稳定。
public void Qsort(int[] nums){
int low = 0, high = nums.length - 1;
partition(nums, low, high);
}

private void partition(int[] nums, int low, int high) {
if (low < high) {
int key = nums[low];
int l = low;
int r = high;
while (l < r) {
while (l < r && key <= nums[r])
r--;
nums[l] = nums[r];
while (l < r && key >= nums[l])
l++;
nums[r] = nums[l];
}
nums[r] = key;
partition(nums, low, l - 1);
partition(nums, l + 1, high);
}
}

//归并排序,其思路很清晰,拿到数组以后,分为两半,各自递归调用排序子序列,然后再合并子序列。有两个注意点,在分割时,必须是[low,m]和[m + 1, high],这样如果是size为2,那么结果会被分割成两个siez为1.
//第二点是,在merge操作时,要新建一个缓存,存两个序列的较小值,为了保证稳定性,如果相等,应该取前面序列的元素。所以if条件是first <= second,then choose first。
//归并排序是稳定排序,复杂度nlgn。
public void MergeSort(int nums[]){
Msort(nums, 0, nums.length - 1);
}

private void Msort(int nums[], int low, int high){
if (low >= high) {
return;
}
int m = (low + high) / 2;
Msort(nums, low, m);
Msort(nums, m + 1, high);
Merge(nums, low, m, high);
}

private void Merge(int nums[], int low, int m, int high){
int[] cash = new int[high - low + 1];
int index = 0;
int first = low, second = m + 1;
while(first <= m && second <= high){
if (nums[first] <= nums[second]) {
cash[index++] = nums[first++];
}else {
cash[index++] = nums[second++];
}
}
if (first > m ) {
while(second <= high){
cash[index++] = nums[second++];
}
}
if (second > high ) {
while(first <= m){
cash[index++] = nums[first++];
}
}
for(int i = 0; i < cash.length; i++){
nums[low + i] = cash[i];
}
}
}

下面是另一种思路的快排,更好理解:

 

public class QS {

public static void main(String args[]){
QS qs = new QS();
int[] array = {6,9,30,6,-1,60,124};
qs.QSort(array);
print(array);
}

public static void print(int[] array){
String s = "[";
for(int i = 0; i < array.length; i++){
s += array[i] + " ";
}
System.out.println(s);
}

public void QSort(int[] array){
if(array == null)
return;
QSort(array, 0, array.length - 1);
}

private int partition(int[] array, int low, int high){
int pivot = array[low];
int last = low;
for(int i = low + 1; i <= high; i++){
if(array[i] <= pivot){
last ++;
swap(array, last, i);
}
}
swap(array, low, last);
return last;
}

private void QSort(int[] array, int low, int high){
if(low > high)
return;
int m = partition(array, low, high);
QSort(array, low, m - 1);
QSort(array, m + 1, high);
}

private void swap(int[] array, int i, int j){
int t = array[i];
array[i] = array[j];
array[j] = t;
}

}

一躺遍历完成划分,用last记录了小于等于pivot的最后一个元素的位置,紧挨着该位置的后面的元素比pivot大。整个过程就是一趟遍历,遇到小于等于pivot的就向后移动last,并且交换,否则就跳过。