一.插入排序
插入排序的思想了类似于向一个有序的数组里面插入一个数。最初我们将一个无序的数组0~N-1的0~0部分视作有序,然后取1位置的数与0位置上的数进行比较,比0位置上数小就进行交换,比0位置上数大就不变,从而使0~1部分排成有序,以此类推。也就是说,取有序部分后边的数插入到那个有序的部分,将这个数和它前面的数依次进行比较,比前面的数小就进行交换,比前面的数大就已经找到了这个数的位置,直到最后位置的数也找到了正确的位置排序就结束。
代码实现:
/*
* 插入排序
* 时间复杂度:
* 是和数据的状况有关的算法流程,一律按照最差状况计算
* 最优:有序序列:O(N)
* 最差:逆序序列:O(N^2)
* 所以时间复杂度为:O(N^2)
*/
public static void sort(int[] arr) {
if(arr == null || arr.length < 2)
return;
//i位置上的数就是这次排序要排的数
for(int i = 1;i < arr.length;i++) {
for(int j = i-1;j >= 0 && arr[j] > arr[j+1];j--) {
swap(arr, j, j+1);
}
}
}
//进行交换的方法
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
二.归并排序
该算法是采用分治法的一个非常典型的应用,将已有序的两个子序列合并为一个有序的序列。 我们使用一个辅助数组,大小是两个子序列大小的和,分别定义两个记录两个子序列当前小标的变量,和一个记录辅助数组的下标的变量,取两个子序列中的数进行比较,较小的数拷贝进辅助数组中,并且将下标后移一位,较大的数所在序列记录下标的变量不变,辅助数组下标也后移一位。。。以此类推,如果有一个数组已经全部放入辅助数组中,就将另一个数组中还没有放入辅助数组的数一次全部放入。
代码实现:
/*
* 归并排序:
* 时间复杂度:O(N*logN),额外空间复杂度O(N)
*/
//将数组排成两个有序的数组,使用插排递归实现
public static void sortProcess(int[] arr, int L, int R) {
if(L == R)
return;
//使用右移的位运算取mid
int mid = L + ((L - R) >> 1);
sortProcess(arr, L, mid);
sortProcess(arr, mid+1, R);
merge(arr, L, mid, R);
}
//归并排序
public static void merge(int[] arr, int L, int mid, int R) {
//辅助数组
int[] help = new int[R-L+1];
int i = 0;
int p1 = L;
int p2 = mid + 1;
//两个有序部分的数组还有数没有排的时候
while(p1 <= mid && p2 <= R) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
//当其中一个数组的数已经全部放进去,将另外一个数组的没有放进去的部分放进去
while(p1 <= mid) {
help[i++] = arr[p1++];
}
while(p2 <= R) {
help[i++] = arr[p2++];
}
//将辅助数组中的数全部放进原数组中
for(i = 0;i < arr.length;i++) {
arr[L + i] = help[i];
}
}
三.归并排序的应用——小和问题
题目描述: 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数的小和。求一个数组的小和。例子:
数组:[1, 3, 4, 2, 5]
1左边比1小的数:没有;
3左边比3小的数:1;
4左边比4小的数:1, 3;
2左边比2小的数:1;
5左边比5小的数:1, 3, 4, 2;
所以小和为:1 + 1 + 3 + 1 + 1 + 3 + 4 + 2 = 16
分析:
我们可以把小和问题转化成:求每个数右边比自己大的个数 * 自己这个数的大小,比如题中:1的右边比1大的数一共有四个,一共计算了四次1;3的右边有两个比3大的数,一共计算了两次3;4的右边有一个比4大的数,一共计算了一次4;2的右边有一个比2大的数,一共计算了一次2;5的右边没有比它大的数。
那么我们该如何将这个问题转化成归并呢?
首先使用分治的思想将数组分为上图所示,先定义一个小和值sum = 0,然后在归并的时候,是从最下层进行归并的,只需判断左边集合中的数是否小于右边集合中的数:
所以最先归并的是1和3,1<3,sum += 1 * 1,第一个1是指较小的数为1,第二个1是指,有1个数比1大;
第二次归并的是[1, 3]和4,1<4,sum += 1 * 1;3 < 4,sum += 3 * 1;
第三次归并的是2和5,2 < 5,sum += 2 * 1;
第四次归并的是[1, 3, 4]和[2, 5],1 < 2,sum += 1 * 2;3 < 5,sum += 3 * 1;4 < 5,sum += 4 * 1
所以,小和问题其实就是一个归并排序的应用
部分代码如下:
//只是在原来归并的基础上添加了一个变量res存放小和的值,并在这个while语句中计算小和值
int res = 0;
while(p1 <= mid && p2 <= r) {
res += arr[p1] < arr[p2] ? arr[p1] * (r - p2 + 1) : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
------------------------------------------ 手动优雅的分割线~~ ---------------------------------------------
更新于20190326:
非常感谢博主追星花狸在评论中指出我的错误,在最后的小和问题上,在阐述过程的部分有误,希望没有误导之前看过这篇博客的盆友…现已更正并加入更多解释,欢迎讨论交流!!