目录
一.插入排序
将待排序的数据按照其大小逐个插入到一个已经排好序的有序序列中,直到所有记录插入完成为止。
1.直接插入排序
效率: 在数据为逆序排列时,时间复杂度最大,O(n^2),当数据接近有序时,时间复杂度最小O(n),所以当数据不多,且基本趋于有序的时候,直接插入非常快。
稳定性:稳定
一个本身是稳定的排序,可以把它变为不稳定的。
//直接插入排序
//时间复杂度:
//最坏情况 逆序 O(n^2)
//最好情况:有序 O(n)
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 {
array[j + 1] =tmp ;
break;
}
}
array[j + 1] = tmp;
}
}
测算耗时:
2.希尔排序
也叫做缩小增量排序:直接插入排序的优化
指定gap分组(缩小增量)5/4/3
每次分组排序后会把大的数往后排,小的数往前排。
希尔排序相比于直接插入排序的区别是:
1.将数根据步长进行了分组(希尔排序步长为一时就是直接插入排序)
2.不同的分组中依次进行了直接插入排序
因此希尔排序就是有步长的循环直接插入排序
代码只需要将直接插入排序的步长进行修改并且加上循环操作即可:
public static void shell(int[]array,int gap){
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j = i - gap;
for (; j >=0; j=j-gap) {
if (array[j] > tmp) {
array[j + gap] = array[j];
}else {
array[j+gap]=tmp;
break;
}
}
array[j + gap] = tmp;
}
}
public static void shellSort(int[]array){
int gap=array.length;
while(gap>1){
gap=gap/2;
shell(array,gap);
}
}
public static void main(String[] args) {
int[] array = {1, 2, 19, 34, 29, 15,37,8};
shellSort(array);
System.out.println(Arrays.toString(array));
}
希尔排序步长的确定方法目前没有准确的规定,怎样取值使其的时间复杂度最小未知。
目前得到的较为准确的时间复杂度为:O(n^1.3)
二.选择排序
1.直接选择排序
每一次从待排序的数据元素中选出最小(或者最大)的一个元素,存放在序列的起始位置,直到全部待排序元素排完。
特点:好理解但是效率低,实际很少用。
(1)一次确定一个小最值
时间复杂度:看每次j要移动的次数
(N-1)+(N-2)+.......+1=[(N-1+1)*(N-1)]/2
时间复杂度:O(N^2)
空间复杂度:O(N)
稳定性:不稳定
思路:
1.用i下标遍历数组,用minIndex储存最小值的下标,minIndex初始为i
2.j从i后面一个开始遍历 ,遇到比minIndex储存的值更小的值,更新minIndex
3.等到j遍历完,交换i与minIndex存储的值
//选择排序
//一次只确定一个最小的
public static void selectSort(int[]array) {
for (int i = 0; i < array.length; i++) {
int j = i + 1;
int minIndex = i;
for (; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
if (i != minIndex) {
swap(array, minIndex, i);
}
}
}
private static void swap(int[] array,int i,int j){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
(2)一次确定一个最大值,一个最小值
思路:
1.用left和right分别代表数组的最左边和最右边下标,初始默认最小值为left,最大值为right
2.用j从left+1的位置开始遍历,当array[j]<array[minIdex],更新minIndex的值为j,当array[j]>array[minIndex],更新maxIndex的值为j
3.当j遍历完,交换array[left]与array[minIdex],交换array[right]与array[maxIndex]
4.left++,right--,直到相遇,全部排序完成
需要特别注意的点,当一组数据最的大值在最前面时
代码如下:
public static void selectSort2(int[]array) {
int left = 0;
int right = array.length - 1;
while (left < right) {
int minIndex = left;
int maxIndex = right;
int j=left+1;
for (; j <= right; j++) {
if (array[j] < array[minIndex]) {
minIndex= j;
}
if (array[j] > array[maxIndex]) {
maxIndex = j;
}
}
swap(array,left,minIndex);
if(maxIndex==left){
maxIndex=minIndex;
}
swap(array,right,maxIndex);
left++;
right--;
}
}
2.堆排序
思路:
1.建立大根堆
1)从下往上找每课子树的父亲节点;parent--;
2)找到该父亲节点后,向下调整,确定每一棵树的所有子树也都是大根堆;
3)向下调整每棵树:孩子节点与父亲节点的比较和交换;
整体代码如下:
//堆排序
//时间复杂度O(n*log2(n))
//空间复杂度O(1)
//不稳定的算法
public static void heapSort(int[]array){
createBigHeap(array);//O(n)
int end= array.length-1;
while(end>0){
swap(array,0,end);
shiftDown(array,0,end);
end--;
}
}
public static void createBigHeap(int[] array){
int parent=(array.length-1-1)/2;
for (; parent>=0 ; parent--) {
shiftDown(array,parent,array.length);
}
}
public static void shiftDown(int[] array,int parent,int len){
int child=2*parent+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 main(String[] args) {
int[] array={9,2,5,1,7,3};
heapSort(array);
System.out.println(Arrays.toString(array));
}
三.交换排序
1.冒泡排序
思路:
1.每趟排序都从0开始;
2.一趟排序完成可以确定一个最大的数在最后面
3.外层循环是一共比较的趟数(趟数=数据个数-1),内层循环是比较两个数的大小(内层循环的次数=数据个数-1-外层循环的趟数,因为一趟下来确定一个数,一趟下来,第二趟就少比一次,以此类推)
//冒泡排序法
//不考虑优化的时间复杂度O(n^2);优化后最好的情况可以达到0(n)
//空间复杂度O(1)
//稳定性:稳定
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]){
int tmp=array[j];
array[j]=array[j+1];
array[j+1]=tmp;
flg=true;
}
if(flg==false){
break;
}
}
}
}
2.快速排序
思路:
1)一次排序确定一个基准,基准左边的数都比它小,基准右边的数都比它大。
2)依次对基准左部分和右部分 进行相同的操作,最后得到的可以类比成为一个有序的二叉树
//快速排序
public static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int start, int end) {
int pivot = partitionHoare(array, start, end);
//必须加上大于号,不然1、2、3、4、5、6这种情况时直接没有左树,或者没有右树
if (start >= end) {
return;
}
quick(array, start, pivot- 1);
quick(array, pivot + 1, end);
}
private static int partitionHoare(int[] array, int left, int right) {
int i=left;
int pivot = array[left];
while (left < right) {
while (left < right && array[right] >= pivot) {
//这里的left<right是为了防止所有值都比pivot大
right--;
}
while(left < right && array[left] <= pivot){
left++;
}
swap(array,left,right);
}
swap(array,left,i);//这里left和right是相等的,写left或者right都可以
return left;
}
空间复杂度:树的高度 log2(N)
稳定性:不稳定
缺陷:当数据是有序时,变成一个右子树,排序类似于冒泡排序,时间复杂度达到O(N^2)
3.快速排序优化
当数据趋于有序的情况下会出现一棵树没有左子树,划分不均匀,增加了时间和空间复杂度。对数据划分均匀是快速排序优化的目的。
(1)三数取中法:将其尽可能划分成一个均匀的二叉树
找到start midIndex end 三个下标,然后找到三个数值的中间值然后将其作为基准。
public static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
public static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int start, int end) {
//必须加上大于号,不然1、2、3、4、5、6这种情况时直接没有左树,或者没有右树
if (start >= end) {
return;
}
//在执行找找基准之前,先解决划分不均匀的问题
int index=findMidValueOfIndex(array, start, end);
swap(array,start,index);
int pivot = partitionHoare(array, start, end);
quick(array, start, pivot- 1);
quick(array, pivot + 1, end);
}
//优化
private static int findMidValueOfIndex(int[]array,int start,int end) {
//三位取中法
int midIndex = (start + end) / 2;
if (array[start] < array[end]) {
if (array[midIndex] < array[start]) {
return start;
} else if (array[midIndex] > array[end]) {
return end;
} else {
return midIndex;
}
}else {
if(array[midIndex]>array[start]){
return start;
}else if(array[midIndex]<array[end]){
return end;
}else {
return midIndex;
}
}
}
private static int partitionHoare(int[] array, int left, int right) {
int i=left;
int pivot = array[left];
while (left < right) {
while (left < right && array[right] >= pivot) {
//这里的left<right是为了防止所有值都比pivot大
right--;
}
while(left < right && array[left] <= pivot){
left++;
}
swap(array,left,right);
}
swap(array,left,i);//这里left和right是相等的,写left或者right都可以
return left;
}
(2)小区间使用插入排序
当一组数据排序往后时二叉树分支越来越多,递归的应用次数也开始变多,这个时候数据已经逐渐趋于有序了,所以这时小区间使用插入排序。
完整代码
private static void insertSort1(int[] array,int left,int right ) {
for (int i =left+1; i <=right; i++) {
int tmp = array[i];
int j = i - 1;
for (; j >=left; j--) {
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
array[j + 1] =tmp ;
break;
}
}
array[j + 1] = tmp;
}
}
//快速排序
//时间复杂度:N*log2(N)
//空间复杂度:log2(N)
public static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int start, int end) {
//必须加上大于号,不然1、2、3、4、5、6这种情况时直接没有左树,或者没有右树
if (start >= end) {
return;
}
//对start和end区间范围内使用插入排序
if(end-start+1<=15){
insertSort1(array,start,end);
return;
}
//在执行找找基准之前,先解决划分不均匀的问题
int index=findMidValueOfIndex(array, start, end);
swap(array,start,index);
int pivot = partitionHoare(array, start, end);
quick(array, start, pivot- 1);
quick(array, pivot + 1, end);
}
private static int findMidValueOfIndex(int[]array,int start,int end) {
//三位取中法
int midIndex = (start + end) / 2;
if (array[start] < array[end]) {
if (array[midIndex] < array[start]) {
return start;
} else if (array[midIndex] > array[end]) {
return end;
} else {
return midIndex;
}
}else {
if(array[midIndex]>array[start]){
return start;
}else if(array[midIndex]<array[end]){
return end;
}else {
return midIndex;
}
}
}
private static int partitionHoare(int[] array, int left, int right) {
int i=left;
int pivot = array[left];
while (left < right) {
while (left < right && array[right] >= pivot) {
//这里的left<right是为了防止所有值都比pivot大
right--;
}
while(left < right && array[left] <= pivot){
left++;
}
swap(array,left,right);
}
swap(array,left,i);//这里left和right是相等的,写left或者right都可以
return left;
}
(3)快速排序非递归
非递归,用到栈
public static void quickSort1(int[] array){
Stack<Integer> stack=new Stack<>();
int start=0;
int end=array.length-1;
int pivot=partitionHoare(array,start,end);
//这里用hoare法演示,不用挖坑法
//判断基准左边部分是不是至少有两个元素
if(pivot>start+1){
//把边界放入栈中
stack.push(start);
stack.push(pivot-1);
}
//判断基准右边部分是不是至少有两个元素
if(pivot<end-1){
stack.push(pivot+1);
stack.push(end);
}
while(!stack.isEmpty()){
end=stack.pop();
start=stack.pop();
//再对该部分找基准,重复上面代码操作
pivot=partitionHoare(array,start,end);
//判断基准左边部分是不是至少有两个元素
if(pivot>start+1){
//把边界放入栈中
stack.push(start);
stack.push(pivot-1);
}
//判断基准右边部分是不是至少有两个元素
if(pivot<end-1){
stack.push(pivot+1);
stack.push(end);
}
}
}
四.归并排序
//归并排序
public static void mergeSort(int[]array){
mergeSortChild(array,0,array.length-1);
}
public static void mergeSortChild(int[]array,int left,int right){
if(left>=right){
return;
}
//分解
int mid=(left+right)/2;
mergeSortChild(array,left,mid);
mergeSortChild(array,mid+1,right);
//合并
merge(array,left,mid,right);
}
private static void merge(int[]array,int left,int mid,int right) {
int s1 = left;
int e1 = mid;
int s2 = mid + 1;
int e2 = right;
int[] tmpArr = new int[right - left + 1];
int k = 0;
while (s1 <= e1 && s2 <= e2) {
if (array[s1] < array[s2]) {
tmpArr[k] = array[s1];
k++;
s1++;
} else {
tmpArr[k] = array[s2];
k++;
s2++;
}
}
while(s1<=e1){
tmpArr[k] =array[s2];
k++;
s1++;
}
while(s2<=e2){
k++;
s2++;
}
for (int i = 0; i < k; i++) {
array[i+left]=tmpArr[i];
}
}