一、插入排序
1.直接插入排序-原理
整个区间被分为 :有序区间 无序区间
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入2.代码实现
//直接插入排序
public static void insertSort(int[] array){
for(int i=1;i<array.length;i++){
int tmp=array[i];
int j=i-1;
for(;j>=0;j--){
if(array[j]>tmp){
array[j+1]=array[j];
}else{
break;//前面都是有序的
}
}
array[j+1]=tmp;
}
}
3.性能分析
时间复杂度
最坏情况:O(n^2)----数组逆序的情况下
最好情况:O(n)----数组有序的情况下
特点:越有序越快
空间复杂度:O(1)
稳定性:稳定的
(一个稳定的排序可以实现为不稳定的排序,但是一个不稳定的排序是不能实现为稳定的排序的)
如何快速判断一个排序是否稳定?
就看该排序是否发生跳跃式的交换(技巧,不是概念)
判断稳定性(概念):看两个相同数的排序前和排序后的位置是否发生了结构顺序的改变
4.折半插入排序
在有序区间选择数据应该插入的位置时,因为区间的有序性,可以利用折半查找的思想。public static void bsInsertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int v = array[i];
int left = 0;
int right = i;
// [left, right)
// 需要考虑稳定性
while (left < right) {
int m = (left + right) / 2;
if (v >= array[m]) {
left = m + 1;
} else {
right = m;
}
}
// 搬移
for (int j = i; j > left; j--) {
array[j] = array[j - 1];
}
array[left] = v;
}
}
二、希尔排序
1.原理
希尔排序本身是直接插入排序的一种优化
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1 时,所有记录在统一组内排好序。 当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。2.代码实现
//希尔排序
public static void shellSort1(int[] array){
int[] drr={5,3,1};//增量的序列,这个不好求。假如有10000个序列的话,很麻烦
for(int i=0;i<drr.length;i++ ){
shell(array,drr[i]);
}
}
public static void shellSort(int[] array) {
int gap = array.length;
while (gap > 1) {
shell(array, gap);
gap = (gap / 3) + 1; // OR gap = gap / 2;
}
shell(array, 1);
}
public static void shell(int[] array,int gap){
for(int i=gap;i<array.length;i++){//i++每一组都能排序
int tmp=array[i];
int j=i-gap;
for(;j>=0;j-=gap){
if(array[j]>tmp){
array[j+gap]=array[j];
}else{
break;
}
}
array[j+gap]=tmp;
}
}
3.性能分析
时间复杂度:O(n^1.3) ~ O(n^1.5),希尔排序的时间复杂度与所取的增量有关
空间复杂度:O(1)
稳定性:不稳定
三、选择排序
1.直接选择排序-原理
每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素 排完 。2.代码实现
//选择排序
public static void selectSort(int[] array){
for(int i=0;i<array.length-1;i++){
for(int j=i+1;j<array.length;j++){
if(array[i]>array[j]){
swap(array,i,j);
}
}
}
}
//交换数组中的两个元素
public static void swap(int[] array,int i,int j){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
3.性能分析
时间复杂度
最坏情况:O(n^2)
最好情况:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
4.双向选择排序
每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完public static void selectSortOP(int[] array) {
int low = 0;
int high = array.length - 1;
// [low, high] 表示整个无序区间
// 无序区间内只有一个数也可以停止排序了
while (low <= high) {
int min = low;
int max = low;
for (int i = low + 1; i <= max; i++) {
if (array[i] < array[min]) {
min = i;
}
if (array[i] > array[max]) {
max = i;
}
}
swap(array, min, low);
if (max == low) {
max = min;
}
swap(array, max, high);
}
}
private void swap(int[] array, int i, int j) {
int t = array[i];
array[i] = array[j];
array[j] = t;
}
四、堆排序
1.原理
基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数(大堆堆顶元素就是最大元素)。注意:升序建大堆,降序建小堆
2.代码实现
//堆排序
public static void heapSort(int[] array){
//建立一个大根堆
creatHeap(array);
int end=array.length-1;
while(end>0){ //O(n*logn)
swap(array,0,end);
shiftDown(array,0,end);
end--;
}
}
//建大根堆 O(n)
public static void creatHeap(int[] array){
for(int parent=(array.length-1-1)/2;parent>=0;parent--){
shiftDown(array,parent,array.length);
}
}
//向下调整 O(logn)
public static void shiftDown(int[] array,int parent,int len){
int child=parent*2+1;
while(child<len){
if(child+1<len && array[child]<array[child+1]){
child++;
}
if(array[child]>array[parent]){
swap(array,child,parent);
parent=child;
child=2*parent+1;
}else{
break;
}
}
}
//交换数组中的两个元素
public static void swap(int[] array,int i,int j){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
3.性能分析
时间复杂度:O(n*logn)
空间复杂度:O(1)
稳定性:不稳定
五、冒泡排序
1.原理
在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序2.代码实现
//冒泡排序
public static void bubbleSort(int[] array){
for(int i=0;i<array.length-1;i++){
boolean flg=false;
for(int j=0;j<array.length-1-i;j++){
if(array[j]>array[j+1]){
swap(array,j,j+1);
flg=true;
}
}
if(!flg){
break;//判断数组是否已经有序
}
}
}
//交换数组中的两个元素
public static void swap(int[] array,int i,int j){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
3.性能分析
时间复杂度
最坏情况:O(n^2)-----不优化情况下不管有序无序都是
最好情况:O(n)-----优化之后
空间复杂度:O(1)
稳定性:稳定的
六、快速排序
1.原理
1. 从待排序区间选择一个数,作为基准值 (pivot) ; 2. Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可 以包含相等的)放到基准值的右边; 3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1 ,代表已经有序,或者小区间 的长度 == 0 ,代表没有数据。2.代码实现
//快速排序
public static void quickSort(int[] array){
quick(array,0,array.length-1);
}
public static void quick(int[] array,int low,int high){
//结束条件
if(low>=high){
return;
}
//优化
//1.三数取中法----避免出现单分支情况(单分支可能会导致栈溢出错误)
mid_three(array,low,high);
//2.如果排序基本有序,可以采用直接插入排序
if((high-low+1)<=100){
//直接插入排序
insertSort1(array,low,high);
}
//随机选取基准法
int pivot=partition(array,low,high);
quick(array,low,pivot-1);
quick(array,pivot+1,high);
}
//固定位置选取基准
//找基准(挖坑法)
public static int partition(int[] array,int start,int end){
int tmp=array[start];
while(start<end){
while(start<end&&array[end]>=tmp){//必须加“=”,不然会死循环
end--;
}
array[start]=array[end];
while(start<end&&array[start]<=tmp){
start++;
}
array[end]=array[start];
}
array[start]=tmp;
return start;
}
//Hoare法
public static int partition1(int[] array,int start,int end){
int i=start;
int j=end;
int tmp=array[start];
while(i<j){
while(i<j&&array[j]>=tmp){
j--;
}
while(i<j&&array[i]<=tmp){
i++;
}
swap(array,i,j);
}
swap(array,i,start);
return i;
}
//几数取中法(三数取中法)
public static void mid_three(int[] array,int left,int right){
int mid=(left+right)/2;//left+(right-left)/2
if(array[left]>array[right]){
swap(array,left,right);
}//array[left]<=array[right]
if(array[mid]>array[left]){
swap(array,mid,left);
}//array[mid]<=array[left]
if(array[mid]>array[right]){
swap(array,mid,right);
}//array[mid]<=array[right]
}
//在某个区间直接插入排序
public static void insertSort1(int[] array,int low,int high){
for(int i=low+1;i<=high;i++){
int tmp=array[i];
int j=i-1;
for(;j>=low;j--){
if(array[j]>tmp){
array[j+1]=array[j];
}else{
break;
}
}
array[j+1]=tmp;
}
}
非递归快速排序(分治)
//非递归快速排序
public static void quickSort2(int[] array){
quick2(array,0,array.length-1);
}
public static void quick2(int[] array,int low,int high){
Stack<Integer> stack=new Stack<>();
int pivot=partition(array,low,high);
//入基准值左边首尾下标
if(pivot>low+1){//基准值左边的数大于一个
stack.push(low);
stack.push(pivot-1);
}
//入基准值右边的首尾下标
if(pivot<high-1){//基准值右边的数大于一个
stack.push(pivot+1);
stack.push(high);
}
while(!stack.isEmpty()){
//弹出下标找基准值
high=stack.pop();
low=stack.pop();
pivot=partition(array,low,high);
//继续入基准值左边右边首尾下标
if(pivot>low+1){
stack.push(low);
stack.push(pivot-1);
}
if(pivot<high-1){
stack.push(pivot+1);
stack.push(high);
}
}
}
//找基准(挖坑法)
public static int partition(int[] array,int start,int end){
int tmp=array[start];
while(start<end){
while(start<end&&array[end]>=tmp){//必须加“=”,不然会死循环
end--;
}
array[start]=array[end];
while(start<end&&array[start]<=tmp){
start++;
}
array[end]=array[start];
}
array[start]=tmp;
return start;
}
3.性能分析
时间复杂度:O(n*logn)(比堆排序用时少-》系数小)
最坏情况:O(n*logn)-----每次能够均与地分割待排序序列
最好情况:O(n^2)----单分支情况(一直在一边递归)
空间复杂度:
最坏情况:O(n)---单分支情况
最好情况:O(logn)
稳定性:不稳定
七、归并排序
1.原理
归并排序 是建立在归并操作上的一种有效的排序算法 ,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子 序列段间有序。若将两个有序表合并成一个有序表,称为二路归并2.代码实现
//递归排序
public static void mergeSort(int[] array){
mergeSortIn(array,0,array.length-1);
}
public static void mergeSortIn(int[] array,int low,int high){
int mid=(low+high)/2;
if(low>=high){
return;
}
mergeSortIn(array,low,mid);
mergeSortIn(array,mid+1,high);
merge(array,low,mid,high);
}
public static void merge(int[] array,int low,int mid,int high){
int s1=low;
int e1=mid;
int s2=mid+1;
int e2=high;
//申请一个临时数组存放比较之后的元素
int[] arr=new int[high-low+1];
int k=0;
while(s1<=e1&&s2<=e2){//判断要合并的数组里是否有元素
if(array[s1]<=array[s2]){
arr[k++]=array[s1++];
}else{
arr[k++]=array[s2++];
}
}
while(s1<=e1){//第二个数组为空时
arr[k++]=array[s1++];
}
while (s2<=e2){//第一个数组为空时
arr[k++]=array[s2++];
}
//写回数据到原来的数组
for (int i = 0; i < k; i++) {
array[i+low]=arr[i];//i+low保证右边数组放回时不会覆盖左边已经放好的数组
}
}
非递归的归并排序
//非递归归并排序
public static void mergeSortNor(int[] array){
int gap=1;
while(gap<=array.length){
for(int i=0;i< array.length;i+=2*gap){
int low=i;
int mid=i+gap-1;
int high=mid+gap;
//防止mid越界
if(mid>=array.length){
mid=array.length-1;
}
//防止high越界
if(high>=array.length){
high=array.length-1;
}
merge(array,low,mid,high);
}
gap=gap*2;
}
}
3.性能分析
时间复杂度: O(n*logn)
空间复杂度:O(n)
稳定性:稳定的排序
总结:
排序方法 | 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(n) | O(n^1.3-1.5) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n*logn) | O(n*logn) | O(n*logn) | O(1) | 不稳定 |
快速排序 | O(n*logn) | O(n*logn) | O(n^2) | O(logn)-O(n) | 不稳定 |
归并排序 | O(n*logn) | O(n*logn) | O(n*logn) | O(n) | 稳定 |