排序算法
今天花了一下午重新复习了一边五大经典排序算法,思想理解还是挺简单,但是实现起来还是需要一点时间,有待提高;之前学习相关算法并没有记录笔记,复习的时候没有资料感觉又熟悉又陌生的感觉;所以打算特意记录下
冒泡排序
思想:冒泡排序的思想很简单,两两比较,每次将最大的拍到最后。
优化:当没有发生交换,其实已经排序完毕,不用在进行排序
代码:
/**
* 冒泡排序
* 原理:两两排序,每次将最大的一个推到上面,分为已排序区和待排序区
* 优化:在已经排好了的话就不会在进行交换,后面的比较也就没有意义,可以削去,可以作为终止条件
* @param arr 待排序的数组
* @return
*/
public static int[] buddling(int[] arr){
boolean flag = false;
if (arr==null||arr.length==1||arr.length==0){
return arr;
}
for (int i = 0; i < arr.length-1; ++i) {
for (int j = 0;j<arr.length-1-i;++j){
if (arr[j]>arr[j+1]) {
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
flag=true;
}
}
if (flag){
//发生过交换,不跳过
//改变状态
flag=false;
}else {
//没有发生过交换
break;
}
}
return arr;
}
插入排序
原理:分为已排序区和未排序区,在未排序区的元素插入到已排序区中的合适位置,然后后面的元素在移动
代码:
/**
* 插入排序:分为已排序和待排序,将待排序的放到已排序中合适的位置,之后元素都需要位移一位
*
* @param arr
* @return
*/
public static int[] insertSort(int[] arr){
if (arr==null||arr.length==1||arr.length==0){
return arr;
}
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j <= i; j++) {
if (arr[i]<arr[j]){
int temp = arr[j];
arr[j]=arr[i];
//j 之后的都往后移动
for (int k = j+1; k <= i; k++) {
int moveTemp = arr[k];
arr[k]=temp;
temp = moveTemp;
}
}
}
}
return arr;
}
选择排序
原理:也是分为已排序和未排序区间,每次在已排序区间找到最小的,插入到为排序区间中
代码:
/**
* 选择排序:每次选择最小的/最大的,下面是先的是选择最小的,也是分为两个排序区和待排序区
* @param arr
* @return
*/
public static int[] selectSort(int[] arr){
if (arr==null||arr.length==1||arr.length==0){
return arr;
}
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
int min = arr[minIndex];
for (int j = i; j < arr.length; j++) {
if (j==i){
continue;
}else {
if (arr[j]<min){
minIndex=j;
}
}
}
//进行交换
//直接使用min作为交换的中间变量
min=arr[minIndex];
arr[minIndex]=arr[i];
arr[i]=min;
}
return arr;
}
总结上面三种算法:实现都相对简单,时间复杂度都是O(n^2),实际开发中如果在大规模数据下,时间复杂度有点高;对这三种基础的排序算法来说,插入排序算是比较有用的;冒泡排序和插入排序对比,插入排序要优于冒泡排序,冒泡排序的数据交换比插入排序的移动复杂,冒泡需要三个操作,插入排序的数据移动只需要一个操作;选择排序就不用说了,他是不稳定的排序算法;
接下来的是时间复杂度为O(n*logn)的排序方法:快排与归并,两种都是利用了分治的思想,运用递归来实现
快速排序
原理:快排充分利用了思想,与上面三种基本算法不一样,利用分治,化大为小;快排的核心就是找到中间点和中止条件;如:挑选一个数组a,进行分组,小的在左,大的在右边,第一次默认要么最后一个要么第一个,之后在将左右分组递归进行这样的操作,直到达到递归条件
快排公式:
quick(start,end)=quick(start,mid-1)+quick(mid+1,end)
终止条件:
start>=end
/**
* 快速排序,水平分区,每次选择一个,进行分区
* @param arr
* @return
*/
public static void fastSort(int[] arr,int start,int end){
if (start>=end){
return;
}
//分区
int mid =partition(arr,start,end);
Sort.fastSort(arr,start,mid-1);
Sort.fastSort(arr,mid+1,end);
}
/**
* 将数组分区,小于比较元素的在做,大于比较元素的再有;比较元素默认选取数组最后一位
* @param arr 待分区数组
* @return 返回分界元素的索引
*/
private static int partition(int[] arr,int start,int end) {
//默认取最后一个作为比较元素
int i = start;//游标分割
boolean flag = false;
int compare = arr[end];
for (int j = start; j < end; j++) {
if (arr[j]<=compare){
Sort.swap(arr, i, j);
++i;
}
}
Sort.swap(arr, i, end);
return i;
}
public static void swap(int[] arr,int a,int b){
int temp = arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
归并排序
原理:归并排序的核心就是先递归分解,然后在递归回溯的时候两两部分分别排序合并,这样整个数组就有序了;核心代码就是将两个有序的数组有序合并merge函数
归并函数:
merge(start,end)=merge(merge(start,mid),merge(mid+1,end))
终止条件:
start>=end
实现:
/**
* 归并思路:m每次找到中间位置,分解,排序,合并
* @param arr
* @return
*/
public static int[] mergeSort(int[] arr){
if (arr==null||arr.length==1||arr.length==0){
return arr;
}
int mid=(arr.length-1)/2;
int[] a = Sort.mergeSortSub(arr,0,mid);
int[] b = Sort.mergeSortSub(arr,mid+1,arr.length-1);
//数组合并排序
return Sort.mergeSortArr(a,b);
}
public static int[] mergeSortSub(int[] arr,int start,int end){
if (start>=end){
int[]arrtemp = new int[1];
arrtemp[0]=arr[start];
return arrtemp;
}
int mid=(start+end)/2;
int[] a = Sort.mergeSortSub(arr,start,mid);
int[] b = Sort.mergeSortSub(arr,mid+1,end);
//数组合并
return Sort.mergeSortArr(a,b);
}
public static int[] mergeSortArr(int[] arrFirst,int[] arrSec){
int[] data = new int[arrFirst.length+arrFirst.length];
//将两个有序数组进行排序
int firstIndex = 0;
int secIndex = 0;
for (int i = 0; i < data.length; i++) {
if (firstIndex==arrFirst.length){
data[i]=arrSec[secIndex];
++secIndex;
continue;
}
if (secIndex==arrSec.length){
data[i]=arrFirst[firstIndex];
++firstIndex;
continue;
}
if (arrFirst[firstIndex]<=arrSec[secIndex]){
data[i]=arrFirst[firstIndex];
++firstIndex;
}else {
data[i]=arrSec[secIndex];
++secIndex;
}
}
return data;
}
在我看来归并就像是在垂直分解,快排横向分解,但是归并排序在空间复杂度上是O(n),而快排是O(1),归并并没有快排广泛应用;快排的平均复杂度是O(nlogn),但是又小概率复杂度会退化到O(n^2),但是我们可以通过合理的选择参照点的位置来避免