基本思想:将待排序的元素看做是“气泡”,比较相邻两个元素进行交换,每次遍历都会将当前最大(最小)的元素像“气泡”一样,浮到一端。
实现过程:
代码:
public static void bubbleSort(int[] array){
for (int i = 0; i < array.length-1; i++) {
boolean flg=false;
for (int j = i; j < array.length-i-1 ; j++) {
if(array[j]>array[j+1]){
swap(array,j,j+1);
flg=true;
}
}
if(flg==false){
break;
}
}
}
}
基本思想
基本思想:任取待排序元素序列中的某元素作为基准值,通过一趟排序将待排序集合分割成两个子序列,左子序列中的所有元素均小于基准值,右子序列中的所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素在相应的位置上。
public static void quickSort(int[] array){
//快速排序入口
quick(array,0,array.length-1);
}
public static void quick(int[] array,int start,int end){
//递归结束条件
if (start>=end){
return;
}
//划分区间
int pivot=partition(array,start,end);
quick(array,start,pivot-1);
quick(array,pivot+1,end);
}
实现过程:
思路:
//Hoare版分割
public static int partitionHoare(int[] array,int left,int right){
int tmp=array[left];
int tmpleft=left;
//直到两指针相遇结束循环
while(left<right){
//right寻找比tmp小的元素
//所以只要大于等于tmp,那么right向左移动
while(left<right && array[right]>=tmp){
right--;
}
//left寻找比tmp大的元素
//所以只要小于等于tmp,那么left向右移动
while(left<right && array[left]<=tmp){
left++;
}
//将左右指针的元素进行交换
swap(array,left,right);
}
//将基准数与指针相遇的地方交换,划分左右区间
swap(array,left,tmpleft);
return left;
}
可能有的疑惑:
答案是不可以的。
如上图所示,从前往后找可能会使left比right更先找到比基准值大的值,然后将他们进行交换,导致左边区间可能出现比基准值大的元素
如果要排序的数组是[6,1,4,2],left下标只要没有比tmp下标的元素来的大,那么就要一直向右移动,甚至移到right的右边。right也同理。
如果数组中有相同的元素,而内层循环条件是array[left]<tmp,array[right]>tmp,那么将会造成死循环。
实现过程:
思路:
代码:
public static int partition(int[] array,int left,int right){
int tmp=array[left];
while(left<right){
//右指针寻找比tmp小的值给左指针
while(left<right && array[right]>=tmp){
right--;
}
//将right下标元素给left下标元素
array[left]=array[right];
//左指针寻找比tmp大的值给右指针
while(left<right && array[left]<=tmp){
left++;
}
//将left下标元素给right下标元素
array[right]=array[left];
}
//将基准数给left或者right下标
array[left]=tmp;
return left;
}
实现过程:
思路:
public static int partition2(int[] array,int left,int right){
int prev=left;
int cur=left+1;
while(cur<=right){
if(array[cur]<array[left] && array[++prev]!=array[cur]){
swap(array,cur,prev);
}
cur++;
}
swap(array,prev,left);
return prev;
}
1. 三数取中法选key
public static void quick(int[] array,int start,int end){
//递归结束条件
if (start>=end){
return;
}
//找到中间元素下标
int midIndex=getMid(array,start,end);
//将中间元素与首元素进行比较
swap(array,midIndex,start);
//划分区间
int pivot=partition2(array,start,end);
quick(array,start,pivot-1);
quick(array,pivot+1,end);
}
private static int getMid(int[] array, int left,int right){
int mid=(left+right)/2;
//如果left下标小于right下标元素
if(array[left]<array[right]){
//比较left与mid
if(array[left]>array[mid]){
return left;
}else if(array[right]<array[mid]){
//比较right与mid
return right;
}else{
return mid;
}
//如果left下标大于right下标元素
}else{
//比较left与mid
if(array[left]<array[mid]){
return left;
}else if(array[right]>array[mid]){
//比较right与mid
return right;
}else{
return mid;
}
}
}
2.递归到小的子区间时,可以考虑使用插入排序
public static void quick(int[] array,int start,int end){
//递归结束条件
if (start>=end){
return;
}
if(end-start+1<=7){
insertSortRange(array,start,end);
}
int midIndex=getMid(array,start,end);
swap(array,midIndex,start);
//划分区间
int pivot=partition2(array,start,end);
quick(array,start,pivot-1);
quick(array,pivot+1,end);
}
private static void insertSortRange(int[] array,int start,int end){
//这里end取闭区间
for (int i = start+1; i <= end; i++) {
//将下标i中的元素给tmp;
int tmp=array[i];
int j = i-1;
for (; j >=start; j--) {
//将j中的元素与tmp中的元素进行比较
//如果j中的元素大于tmp中的元素,那么将j中的元素放到j+1的元素中
if(tmp<array[j]){
array[j+1]=array[j];
}else{
//否则说明j+1是适合tmp元素的位置,将其插入
array[j+1]=tmp;
break;
}
}
array[j+1]=tmp;
}
}
实现过程:
思路:
代码:
public static void quickSort(int[] array){
//快速排序入口
quickNor(array,0,array.length-1);
// quick(array,0,array.length-1);
}
public static void quickNor(int[] array,int left,int right){
Deque<Integer> stack=new ArrayDeque<>();
//划分区域
int pivot=partition(array,left,right);
//将两边的左右区间放入栈中
if(pivot-1>left){
stack.push(left);
stack.push(pivot-1);
}
if(pivot+1<right){
stack.push(pivot+1);
stack.push(right);
}
//只要栈不为空,就代表还不是整体有序
while(!stack.isEmpty()){
//弹出右边界,在弹出左边界
right=stack.pop();
left=stack.pop();
//将左右边界区间在进行分割
pivot=partition(array,left,right);
//如果满足条件,在进行入栈
if(pivot-1>left){
stack.push(left);
stack.push(pivot-1);
}
if(pivot+1<right){
stack.push(pivot+1);
stack.push(right);
}
} }
//挖坑法
public static int partition(int[] array,int left,int right){
int tmp=array[left];
while(left<right){
//右指针寻找比tmp小的值给左指针
while(left<right && array[right]>=tmp){
right--;
}
//将right下标元素给left下标元素
array[left]=array[right];
//左指针寻找比tmp大的值给右指针
while(left<right && array[left]<=tmp){
left++;
}
//将left下标元素给right下标元素
array[right]=array[left];
}
//将基准数给left或者right下标
array[left]=tmp;
return left;
}
快速排序总结
基本思想:
归并排序是一种高效的基于分治策略的排序算法。 分治策略:将一个数组分成两个子数组,然后递归的对这两个子数组进行排序,最后将排好序的子树组合并成一个完整的排序数组。
实现过程:
思路:
分解:
合并:
代码:
public static void mergeSort(int[] array){
mergeSortTmp(array,0,array.length-1);
}
//分解
public static void mergeSortTmp(int[] array,int left,int right){
if(left>=right){
return;
}
//分解:
int mid=(left+right)/2;
mergeSortTmp(array,left,mid);
mergeSortTmp(array,mid+1,right);
//合并
merge(array,left,mid,right);
}
//合并
public static void merge(int[] array,int left,int mid,int right){
int[] tmp=new int[right-left+1];
int k=0;
int s1=left;
int s2=mid+1;
while(s1<=mid && s2<=right){
if(array[s1]<=array[s2]){
tmp[k++]=array[s1++];
}else{
tmp[k++]=array[s2++];
}
}
//放入剩余的元素
while(s1<=mid){
tmp[k++]=array[s1++];
}
while(s2<=right){
tmp[k++]=array[s2++];
}
//保证数组有序
for (int i = 0; i < k; i++) {
array[i+left]=tmp[i];
}
}
排序 | 最好 | 平均 | 最坏 | 空间复杂度 | 时间复杂度 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n*log(n)) | O(n*log(n)) | O(n*log(n)) | O(1) | 不稳定 |
快速排序 | O(n*log(n)) | O(n*log(n)) | O(n^2) | O(log(n))~O(n) | 不稳定 |
归并排序 | O(n*log(n)) | O(n*log(n)) | O(n*log(n)) | O(n) | 稳定 |