1、插入排序
插入排序,一开始对于一个数组,默认第一个数是已经排好顺序了,
这里就会从i=1开始进行插入操作,令j = i-1=1-1=0,比较11和12的大小,11<12,也就是
j+1(1)下标对应的值比j(0)下标的值要小,那么就交换。此时j--,那么j就等于-1了,比0还要小,不满足循环条件,跳出,则第一轮
外层的循环就结束了。此时的数组为:
那么继续第二次循环,那么j = i-1=2-1=1,那么比较j+1和j对应值的大小,也就是arrs[j+1] 和arrs[j]进行比较
5比12要小,进行交换,然后继续j--等于0,继续比较,因为此时5和12交换了,此时j+1(1)和j(0)的比较就变成了5和11
的比较,5比11要小,继续交换,j--,小于0退出循环。第二轮外层循环结束。此时数组为:
第三轮循环,j=i-1=3-1=2,就是8和12比较,8比较小,交换,j--1,同样的,此时是8跟11比较,比较小,交换,此时j--,j=0了,
那么就是5和8比较,发现8是比较大的,那么直接推出循环。后面的过程类似。
可以看出,跳出循环有两种情况,一种是j<0的时候,一种是arrs[j+1]比arrs[j]大的时候。
总的代码给出:
@Test
public void insertSort() {
int[] arrs = {1,5,3,2,6,7,2,78};
//i=1,一开始,默认第一个数已经排好序了。
for(int i = 1; i < arrs.length; i++) {
for(int j = i-1; j >= 0; j--) {
//第一次比较是i和i-1进行比较,如果i比i-1
//位置大,那么要进行交换。交换后,那么原来要插入
//的数来到i-1的位置,j--,也就是说会比较i-1和
//i-1-1的位置,如果不满足,那么就跳出该循环,插入的数
//已经来到合适的位置。这里等于的时候,没有进行交换,则可以
//知道,该排序还是满足稳定的性质的。
if(arrs[j] > arrs[j+1]) {
swap(j,j+1,arrs);
}else {
break;
}
}
}
print(arrs);
}
private void print(int[] arrs) {
for(int i = 0; i < arrs.length; i++) {
System.out.println(arrs[i]);
}
}
2、归并排序
1)归并排序的时间复杂度为n * logn,在进行排序之前,先介绍下如何将两个排好顺序的数组合并层一个排好的数组。如下图,有两组数组,分别排好,同时标上标记。
首先是定义一个两个数组长度之和一样大小的数组,同时用i 和 j标记遍历的数组。比较arrs[j] 和 arrs[i],此时j对应的值比较小,所以将2放在下面的大数组中。同时j++;然后比较i=0 和 j=1对应的值的大小,此时相等,哈哈,这是我把i=0的数放到大数组中,说明相等的数,前面那个还是在前面,那么就是稳定的(归并排序可以做到稳定)。此时i++,然后比较i=1和j=1的值,此时j=1的值比较小,所以放入到大叔组中,j++。再比较,然后放5,i++;然后放6;j++;然后放7,i++,此时发现i 已经等于数组的长度了,越界了,则退出循环,然后将生于数组中的9拷入大数组中。.
代码如下:
@Test
public void merge() {
int[] arrs1 = {4,5,7};
int[] arrs2 = {2,4,6,9};
int lent1 = arrs1.length;
int lent2 = arrs2.length;
int lent = lent1 +lent2;
//先声明初始化一个大数组,长度为lent
int[] arrs = new int[lent];
int i = 0;//初始位置的标记
int j = 0;//初始位置的标记
int t = 0;//大数组的index
while(i < lent1 && j < lent2) {
//小于等于时,拷入,左边的数组,那么排序就是稳定的。
if(arrs1[i] <= arrs2[j]) {
arrs[t++] = arrs1[i++];
}else {
arrs[t++] = arrs2[j++];
}
}
while(i < lent1) {
arrs[t++] = arrs1[i++];
}
while(j < lent2) {
arrs[t++] = arrs2[j++];
}
print(arrs);//这个方法以后就不写了,前面都有,这个只是来看排好序是否是正确的。
}
接下来,进行归并的过程。归并采用递归的方式。举个例子,假设数组就只有两个数(为什么不是一个数,嘿嘿,其实一个数或者没有数的话,根本就不用排,直接返回就好了),那么递归的范围就是0到1,mergeSort(arrs,left,right),mergeSort(arrs,0,1),那么left =0,right = arrs.length-1.所以就会出现mid= (left +right)/2,那么mid=0,然后递归调用左边部分mergeSort(arrs,0,0),和右边部分mergeSort(arrs,1,1),然后merge(arrs,left,mid,right); merge(arrs,0,0,1)。后面都是一样的。注意的一点是,以前都是两个给定的数组,而这一次是在一个数组,用下标的方式进行merge而已,其实本质是一样的,但是会比较复杂。
@Test
public void testMerge() {
int[] arrs = {1,4,2,5,3,6,4};
sortMerge(arrs);
}
public void sortMerge(int[] arrs) {
int left = 0;
int right = arrs.length-1;
sortMerge(arrs,left,right);
print(arrs);
}
public void sortMerge(int[] arrs,int left,int right) {
if(left == right) {
return;
}
int mid = (left+right)>>1;//向右移动一位,其实是除于2,计算机中,为运算效率比较好。
sortMerge(arrs, left, mid);
sortMerge(arrs, mid+1, right);
merging(arrs,left,mid,right);
}
//这里其实就是上述的合并两个排好序的数组的方式,只不过要扣下
private void merging(int[] arrs, int left, int mid, int right) {
//要临时用一个数组来拷贝需要归并范围数据
//比如现在归并的范围是0,1,那么temp的长度
//等于2,就是1-0+1。
int[] temp = new int[right -left +1];
//这里要用lleft来记录传入的left,left不能拿来直接使用,
//因为要拷贝回arrs时,要使用的left
int lleft = left;
int rleft = mid +1;
int index = 0;//temp数组的标记。
while(lleft <= mid && rleft <= right) {
if(arrs[lleft] <= arrs[rleft]) {
temp[index++] = arrs[lleft++];
}else {
temp[index++] = arrs[rleft++];
}
}
while(rleft <= right) {
temp[index++] = arrs[rleft++];
}
while(lleft <= mid) {
temp[index++] = arrs[lleft++];
}
//这里是将merge范围里面的数,用已经排好temp来覆盖。
//假设是0,1范围,那么left=0开始,则arrs在这一次循环中
//0,1范围就会和temp一样,排好序。假设是从4,6范围,那么
//left=4开始,同样也能是的arrs的4,6范围跟此次循环后排好序
//的temp一样。所以arrs[left++]是从left开始,不是从0开始。
for(int t = 0;t < temp.length;t++) {
arrs[left++] = temp[t];
}
}
现在讲一下,归并排序的一些运用,比如求最小和。
在一个数组中,每一个数左边比当前数小的数加起来。
比如1,2,3,那么1比2小,1,2比3小,所以小和为4.
1)这个可以通过双层循环,每一个数都试用一次。比如遍历1,去跟2,3比;遍历2,去跟后面比,小于3.那么就是1+1+2=4.,
这个时间复杂读是n*n。
2)通过归并的方式,因为每一次分治后,都是最终都是要merge的,在merge的时候,可以比较前面一部分和后面一部分的值的大小。过程就是归并排序而已,只不过多了一些求和的操作。
@Test
public void getMinSum() {
int[] arrs = {1,3,4,2,5};
minSum(arrs);
}
private void minSum(int[] arrs) {
int left = 0;
int right = arrs.length -1 ;
int sumMerge = sumMerge(arrs,left,right);
System.out.println("the min sum is :"+sumMerge);
print(arrs);//为了看排序是否正确
}
private int sumMerge(int[] arrs, int left, int right) {
if(left < right) {
int mid = (left+right)>>1;
return sumMerge(arrs, left, mid)+sumMerge(arrs, mid+1, right)+mergingSum(arrs,left,mid,right);
}
return 0;
}
private int mergingSum(int[] arrs, int left, int mid, int right) {
int temp[] = new int[right -left+1];
int lleft = left;
int rleft = mid+1;
int result = 0;
int index =0;
while(lleft <= mid && rleft <= right) {
if(arrs[lleft] <= arrs[rleft]) {
result += arrs[lleft] * (right - rleft+1);
temp[index++] = arrs[lleft++];
}else {
temp[index++] = arrs[rleft++];
}
}
while(lleft <= mid) {
temp[index++] = arrs[lleft++];
}
while(rleft <= right) {
temp[index++] = arrs[rleft++];
}
for(int i = 0; i < temp.length; i++) {
arrs[left++] = temp[i];
}
return result;
}
3、快速排序
1) 在进行快速排序之前,了解下荷兰国旗问题。就是将数组根据某个数划分成三部分,前面部分比这个数小,中间部分跟数相等,后面部分比这个数大。如下图,假设现在按照8进行划分。
左边标记为A,less = left -1 右边标记为B,more = right,同时指定当前位置的编辑,cur = left
现在是要将这所有的数进行上述的划分,所以,left = 0,right = arrs.length -1 +1= 5
比较cur这个下标对应的和8,比8要小,那么++less位置和cur进行交换(这里刚巧是0和0交换),同时less=0,cur也要加1,
此时为:
此时比较11和8,比8要大,跟--more后的数交换,放在右边,那么11和18交换,此时more等于4
此时cur还是等于1,因为不知道18的情况,所以还是要进行比较的。18和8比,比较大,同样的,放到右边去,--more等于3
此时:
此时cur还是要进行比较,发现跟8相等,此时什么也不做,直接cur++,等于2
继续比较,cur=2,对应12,跟8比,比8大,那么cur和--more的值进行交换,此时more=2,跟cur相等,退出,完成。
@Test
public void divideTreePart() {
int[] arrs = {1,5,3,2,6,7,2,78};
int num = 3;
int left = 0;
int right = arrs.length-1;
int less = left -1;
int more = right+1;
int cur = left;
while(cur < more) {
if(arrs[cur] > num) {
//交换cur和右边部分的最近的一个数。
swap(cur,--more,arrs);
}else if(arrs[cur] < num){
//交换最左部分的最近一个数,如果没有中间数
//那么cur会和自己交换后加1。如果中间有数,那么,最中间部分
//最左边的数会和cur交换,然后cur+1,所以可以知道,使用这种方式进行
//进行排序的算法是不稳定的,下面的归并排序会用到这个方式,所以可以知道,
//快速排序是不稳定的。
swap(cur++,++less,arrs);
}else {
cur++;
}
}
print(arrs);
}