七大基于比较的排序算法
1. 排序的概念及引用
1.1排序的概念
排序
:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性
:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持 不变,即在原序列中,r[i]=r[j]
,且
r[i]
在
r[j]
之前,而在排序后的序列中,
r[i]
仍在
r[j]
之前,则称这种排序算法是稳 定的;否则称为不稳定的。
内部排序
:数据元素全部放在内存中的排序。
外部排序
:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
1.2 常见的排序算法
2.常见算法排序的实现
2.1插入排序
2.1.1 基本思想
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到
一个新的有序序列
2.1.2直接插入排序
当插入第
i(i>=1)
个元素时,前面的
array[0],array[1],…,array[i-1]
已经排好序,此时用
array[i]
的排序码与
array[i- 1],array[i-2],…的排序码顺序进行比较,找到插入位置即将
array[i]
插入,原来位置上的元素顺序后移
public static void InsertSort(int[] array) {//插入排序 for (int i = 1; i < array.length; i++) { int tmp = array[i];//记录下要排序的值 int j; for (j = i - 1; j >= 0; j--) {//从i-1下标开始 if (tmp < array[j]) {//如果小于就覆盖 array[j + 1] = array[j]; } else {//当不小于时说明此时j位于tmp理应所在位置的前一个位置停止循环 break; } } array[j + 1] = tmp;//将tmp覆盖放回j+1的位置 } }
直接插入排序的特性总结:
1.
元素集合越接近有序,直接插入排序算法的时间效率越高
2.
时间复杂度:
O(N^2)
3.
空间复杂度:
O(1)
,它是一种稳定的排序算法
4.
稳定性:稳定
2.1.3.希尔排序(缩小增量排序)
希尔排序是对插入排序的进一步优化,通过分组的形式调换数字,将数组变的逐渐有序
先选定一个整数,把待排序文件中所有记录分成多个组,
所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达
=1
时,所有记录在统一组内排好序
。
public static void Shellsort(int[] array) {//希尔排序(缩小增量排序) int grap = array.length / 2; while (grap >= 1) { shell(array, grap); grap /= 2; } }
public static void shell(int[] array, int grap) {//主体仍为插入排序 for (int i = grap; i < array.length; i++) { int tmp = array[i]; int j; for (j = i - grap; j >= 0; j -= grap) { if (tmp < array[j]) { array[j + grap] = array[j]; } else { break; } } array[j + grap] = tmp; } }
1.
希尔排序是对直接插入排序的优化。
2.
当
gap > 1
时都是预排序,目的是让数组更接近于有序。当
gap == 1
时,数组已经接近有序的了,这样就会很 快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3.希尔排序由于grap的取值可能不同故时间复杂度不容易计算
2.2选择排序
2.2.1基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元 素排完
2.2.2选择排序
public static void Selectionsort1(int[] array) {//选择排序 for (int i = 0; i < array.length; i++) { int minIndex = i; for (int j = i + 1; j < array.length; j++) { if (array[j] < array[minIndex]) { minIndex = j; } } swap(array, minIndex, i); } } public static void swap(int[] array, int i, int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; }
还可以同时找最小值与最大值
public static void Selectionsort2(int[] array) { int left = 0; int right = array.length - 1; while (left < right) { int minIndex = left; int maxIndex = left; for (int j = left + 1; j < right; j++) { if (array[j] < array[minIndex]) { minIndex = j; } if (array[j] > array[maxIndex]) { maxIndex = j; } } swap(array, left, minIndex);//注意当left与maxIndex相等时, // 此时交换就把最大值换到minIndex上了 这时需要将maxIndex修正为最大值所在的minIndex上 if (left == maxIndex) { maxIndex = minIndex; } swap(array, right, maxIndex); left++; right--; } }
1.
直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
2.
时间复杂度:
O(N^2)
3.
空间复杂度:
O(1)
4.
稳定性:不稳定
2.2.3 堆排序
堆排序
(Heapsort)
是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆 来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
public static void Heapsort(int[] array) {//堆排序 creatHerap(array); int end=array.length-1; while(end>0){ swap(array,0,end); shiftDown(array,0,end); end--; } } public static void creatHerap(int []array) {//建立对应所需根堆 for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) { shiftDown(array, parent, array.length); } } public static void shiftDown(int[] array, int parent, int usedSize) { int child = 2 * parent + 1; while (child < usedSize) { if (child + 1 < usedSize && array[child] < array[child + 1]) { child++; } if (array[parent] <array[child]) { swap(array, child, parent); parent = child; child = 2 * parent + 1; } else{ break; } } }
1.
堆排序使用堆来选数,效率就高了很多。
2.
时间复杂度:
O(N*logN)
3.
空间复杂度:
O(1)
4.
稳定性:不稳定
2.3 交换排序
基本思想:
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特 点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
2.3.1冒泡排序
public static void bubbleSort(int[]array){//冒泡排序 for (int i = 0; i < array.length; i++) { for (int j = 0; j < array.length-1-i; j++) { if(array[j]>array[j+1]){ swap(array,j+1,j); } } } }
1.
冒泡排序是一种非常容易理解的排序
2.
时间复杂度:
O(N^2)
3.
空间复杂度:
O(1)
4.
稳定性:稳定
2.3.2快速排序
基本思想:
任取待排序元素序列中的某元
素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有
元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
public static void quickSort(int[]array){//快速排序 // quick(array,0,array.length-1); quickNor(array,0,array.length-1); } public static void quickNor(int[]array,int start,int end){//快速排序非递归实现 int partition=Partition2(array,start,end); Queue<Integer> stack=new ArrayDeque<Integer>(); if(partition-start>1){ stack.offer(start); stack.offer(partition-1); } if(end-partition>1){ stack.offer(partition+1); stack.offer(end); } while(!stack.isEmpty()){ int left= stack.poll(); int right=stack.poll(); partition=Partition2(array,left,right); if(partition-left>1){ stack.offer(left); stack.offer(partition-1); } if(right-partition>1){ stack.offer(partition+1); stack.offer(right); } } } public static void quick(int[]array,int start,int end){ if(start>=end){ return ; } if(end-start+1<=5){//当拆分至较小个数时采用插入排序 InsertSort(array); } // int index=quickoptimize(array,start, end);//三数优先优化快速排序 // swap(array,start,index); int partition=Partition2(array,start,end); quick(array,start,partition-1); quick(array,partition+1,end); } public static int quickoptimize(int[]array,int left,int right){ int mid =(left+right)/2; if(array[left]>array[right]){ if(array[mid]>array[left]){ return left; } else if(array[mid]<array[right]){ return right; } else{ return mid; } } else{ if(array[mid]>array[right]){ return right; } else if(array[mid]<array[left]){ return left; } else{ return mid; } } } public static int Partition(int[]array,int left,int right){//Hoare版 int tmp=array[left]; int tmpleft=left;//保留基值 所在下标 while(left<right){ while(left<right&&array[right]>=tmp){ right--; } while(left<right&&array[left]<=tmp){ left++; } swap(array,left,right);// } swap(array,tmpleft,left);//将初始基值与相遇点交换 return left; } public static int Partition2(int[]array,int left,int right){//挖坑法(最常用) int tmp=array[left]; while(left<right){ while(left<right&&array[right]>=tmp){ right--; } swap(array,left,right); while(left<right&&array[left]<=tmp){ left++; } swap(array,left,right); } array[left]=tmp; return left; }
1.
快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫
快速
排序
2.
时间复杂度:
O(N*logN)
3.
空间复杂度:
O(logN)
4.
稳定性:不稳定
2.4归并排序
基本思想:
归并排序(
MERGE-SORT
)是建立在归并操作上的一种有效的排序算法
,
该算法是采用分治法(
Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使 子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
分为拆解与合并两部分
2.4.1归并排序
public static void mergeSort(int[]array){//归并排序 //merge(array,0,array.length-1); mergeNor(array); } public static void merge(int[] array, int left, int right){ if(left>=right){ return ; } int mid=(left+right)/2; merge(array,left,mid); merge(array,mid+1,right); //将数组拆分完毕 //以下开始合并 mergeconquer(array,left,mid,right); } public static void mergeNor(int []array){//归并排序的非递归形式实现 int grap=1; while(grap<=array.length){ for(int i=0;i<array.length;i+=2*grap){ int left=i; int mid=left+grap-1; if(mid>=array.length){ mid=array.length-1; } int right=mid+grap; if(right>=array.length){ right=array.length-1; } mergeconquer(array,left,mid,right); } grap*=2; } } public static void mergeconquer(int []array,int left,int mid,int right) {//合并两个有顺序的数组 int len = right - left + 1; int[] tmp = new int[len]; int k = 0; int s1 = left; int e1 = mid; int s2 = mid + 1; int e2 = right; while (s1 <= e1 && s2 <= e2) { if (array[s1] <= array[s2]) { tmp[k++] = array[s1++]; } else { tmp[k++] = array[s2++]; } } while (s1 <= e1) { tmp[k++] = array[s1++]; } while (s2 <= e2) { tmp[k++] = array[s2++]; } for(int i=0;i<k;i++){//最关键 保证 值不被覆盖 array[i+left]=tmp[i]; } }
2.4.2 归并排序总结
1.
归并的缺点在于需要
O(N)
的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
2.
时间复杂度:
O(N*logN)
3.
空间复杂度:
O(N)
4.
稳定性:稳定
2.5计数排序
计数排序只使用于一组处于一定范围内的数据
// 计数排序 public static void countSort(int[] array){ //求取最大值与最小值 int maxValue=array[0]; int minValue=array[0]; for(int i=0;i<array.length;i++){ if(array[i]>maxValue){ maxValue=array[i]; } if(array[i]<minValue){ minValue=array[i]; } } //设计数组计数 int len=maxValue-minValue+1; int []tmp=new int[len]; int i=0; while(i<array.length){ tmp[array[i]-minValue]++; i++; } //排序 int index=0; for(int j=0;j<tmp.length;j++){ while(tmp[j]!=0){ array[index]=minValue+j; tmp[j]--; index++; } } }