上一节分析了一下冒泡排序,插入排序,选择排序。时间纷杂度都是O(n^2),这一节分析一下速度较快的排序算法,归并排序和快速排序
-
归并排序
归并排序的思想比较简单,分而治之;
- 把排序的数组平均分为左右两部分,递归此操作
- 然后对左右两部分进行排序。
4,6,3,7,2,8,1,9
分为两组
{4,6,3,7}-{2,8,1,9}
再分
{4,6}-{3,7}-{2,8}-{1,9}
再分
4-6-3-7-2-8-1-9
分解为单独的数据之后使用合并时排序
4-6合并不用交换位置
3-7合并不用交换位置
2-8合并不用交换位置
1-9合并不用交换位置
结果
{4,6}--{3,7}--{2,8}--{1,9}
再继续合并
{3,4,6,7}--{1,2,8,9}
再继续合并
1,2,3,4,6,7,8,9
这个过程可以看到是,先分解,再合并。
而且分解之后合并是嵌套执行的,可以使用之前我们学习的递归思想:
java实现
public class TarTest {
public static void main(String[] args) {
int[] arr = new int[]{4,2,7,3,9,5,8,1,6};
split(arr,0,arr.length-1);
for(int a :arr){
System.out.print(a);
}
}
private static void split(int[] arr,int p, int r){
if(p>=r){
//当分到长度为1的时候跳出循环
return;
}
//找到类中点
int middle = (p+r)/2;
//递归左半边
split(arr,p,middle);
//递归右半边
split(arr,middle+1,r);
//合并且排序两边数据
mergeSort(arr,p,middle,middle+1,r);
}
private static void mergeSort(int[] arr, int p, int lEnd, int rBegin, int r) {
//左边的和右边的合并,但是要注意排序,
int i=p,j=rBegin;
//临时数组
int[] temp = new int[r-p+1];
int k = 0;
//从最左边开始遍历两边的子数组
while(i <= lEnd && j <= r){
if(arr[i]<=arr[j]){
temp[k++] = arr[i++];
}else {
temp[k++] = arr[j++];
}
}
//如果左边还没遍历完
while (i <= lEnd){
temp[k++] = arr[i++];
}
//如果右边还没遍历完
while (j <= r){
temp[k++] = arr[j++];
}
//p到r的数据保证都是有序的,拷贝到原始数组里面
for(int s = 0;s<r-p+1;s++){
arr[p+s] = temp[s];
}
}
}
这段代码是比较初级的代码,最主要的逻辑在mergeSort方法里面,这里面需要断点调试,然后确定是否正确。但是代码可以调试,大家还是想一下这个设计思想。
在归并排序的平均复杂度是O(nlog(n));
-
快速排序
快速排序的思想也是比较简单,
- 在最初的数组中选取任意一个位置的数据(一般取数组的最后一位),以此数据为基数。从头和尾向中心遍历数组数据,如果左游标的指向的数小于等于基数,游标+1向右一位,如果右游标的指向的数大于等于基数,游标-1向左一位。
- 如果左游标<右游标,交换左右游标的数据
- 如果左游标等于大于右游标,跳出遍历
- 交换左游标和基数的数据
4,6,3,7,2,8,1,9
选中基数 9,从最左和最右向中间遍历数组
【4】,6,3,7,2,8,1,「9」
左边的游标指向的数据小于等于基数,左游标+1,右边的游标大于等于基数,右游标-1,
4,【6】,3,7,2,8,1,「9」
持续上面的过程
4,6,3,7,2,8,1,【「9」】
直到两个游标相撞,则交换左游标和基数的位置,返回左游标的位置
返回9所在的位置
然后分为左右两边两个数组进行上面的操作
4,6,3,7,2,8,1进行如上操作
java实现
public static void main(String[] args) throws ParseException {
int[] arr = new int[]{4,2,6,1,5,7,9,0,8,3};
quickSort(arr,0,arr.length-1);
for(int i :arr){
System.out.print(i);
}
}
// 快速排序
private static void quickSort(int[] arr,int p,int r){
if(p>=r){
return;
}
//根据基数排序左右两部分,然后返回基数的中间位置,以便与下面的划分
int index = selectNextIndex(arr,p,r);
quickSort(arr,p,index-1);
quickSort(arr,index+1,r);
}
private static int selectNextIndex(int[] arr, int p, int r) {
//选取基数
int key = arr[r];
//从开头和结尾相向遍历数组
int i = p,j= r;
while (i<j){
while (arr[i]<=key&&i<j){
i++;
}
while (arr[j]>=key&&i<j){
j--;
}
if(i<j){
int tep = arr[i];
arr[i] = arr[j];
arr[j] = tep;
}
}
//把基数换到合适的位置
arr[r] = arr[i];
arr[i] = key;
return i;
}
上面的算法写的也比较直观明了,但是要注意,遍历的前后断点
快速排序的时间复杂度也是O(nlog(n)),
相对于归并排序,大家一般都喜欢用快速排序,因为归并排序需要生命临时工作内存,temp,空间复杂度不是O(1)。快速排序的内存空间是O(1).
当时看了算法的课程,认为快排也不过如此,只有当我开始写代码的时候,才明白,纸上得来终觉浅,绝知此事需躬行。
多写,多练,不要马什么梅