排序算法总览
算法 | 时间复杂度 | 空间复杂度 | 稳定性 |
冒泡排序 | O(n^2) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(logn) | 不稳定 |
直接选择排序 | O(n^2) | O(1) | 不稳定 |
直接插入排序 | O(n^2) | O(1) | 稳定 |
希尔排序 | O(nlogn~n^2) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(1) | 稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
计数排序 | O(n+k) | O(n+k) | 稳定 |
桶排序 | O(n) | O(n+bucket_num) | 稳定 |
基数排序 | O(n*bucket_num) | O(n*bucket_num) | 稳定 |
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
一、内部排序:所有数据已经读入内存,在内存中进行排序的算法,排序过程中不需要对磁盘进行读写
1、冒泡排序
基本思想:重复的遍历待排序的一组数字,依次比较两个相邻的元素,若它们的顺序错误则将它们调换一下位置,直至没有元素再需要交换为止,完成一次即可将最大元素放在最右边。
public class BubbleSort {
public static int[] bubbleSort(int[] array){
if(array.length==0) return array;
for(int i=0;i<array.length;i++){
for(int j=0;j<array.length-i-1;j++){
//将最大的元素交换到最右边
if(array[j+1]<array[j]){
int temp=array[j+1];
array[j+1]=array[j];
array[j]=temp;
}
}
}
return array;
}
}
2、快速排序
基本思想:选择一个元素作为基准,通过一趟排序将待排序列表分割成独立的两部分,其中一部分的所有元素都比另一部分小,然后再按此方法将独立的两部分分别继续重复进行此操作,这个过程我们可以通过递归实现,从而达到最终将整个列表排序的目的
public class QuickSort {
public static void quickSort(int[] array,int low,int high){
if(low<high){
//寻找基准数据的正确索引
int index=getIndex(array,low,high);
//分治
quickSort(array,low,index-1);
quickSort(array,index+1,high);
}
}
public static int getIndex(int[] array,int low,int high){
//基准数据
int tmp=array[low];
while(low<high){
//当队尾的元素大于等于基准数据时,向前挪动high指针
while(low<high&&array[high]>=tmp){
high--;
}
// 如果队尾元素小于tmp了,需要将其赋值给low
array[low]=array[high];
// 当队首元素小于等于tmp时,向前挪动low指针
while(low<high&&array[low]<=tmp){
low++;
}
// 当队首元素大于tmp时,需要将其赋值给high
array[high]=array[low];
}
// 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
array[low]=tmp;
return low;
}
}
3、直接选择排序
基本思想:先在待排序列表中找到最小(大)的元素,把它放在起始位置作为已排序序列;然后,再从剩余待排序序列中找到最小(大)的元素放在已排序序列的末尾,以此类推,直至完毕
public class SelectionSort {
public static int[] selectionSort(int[] array){
if(array.length==0) return array;
for(int i=0;i<array.length;i++){
int minIndex=i;
for(int j=i;j<array.length;j++){
if(array[j]<array[minIndex])
minIndex=j;
}
int temp=array[minIndex];
array[minIndex]=array[i];
array[i]=temp;
}
return array;
}
}
4、直接插入排序
基本思想:对于未排序元素,在已排序序列中从后向前扫描,找到相应位置把它插入进去;在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为新元素提供插入空间
public class InsertionSort {
public static int[] insertionSort(int[] array){
if(array.length==0) return array;
int current;
for(int i=0;i<array.length-1;i++){
current=array[i+1];
int preIndex=i;
while(preIndex>=0&¤t<array[preIndex]){
array[preIndex+1]=array[preIndex];
preIndex--;
}
array[preIndex+1]=current;
}
return array;
}
}
5、希尔排序
基本思想:将待排序列表按下标的一定增量分组(比如增量为2时,下标1,3,5,7为一组,下标2,4,6,8为另一组),各组内进行直接插入排序;随着增量的越来越小,每组所包含的数字序列越来越多,当增量减至1时,整个序列被分成一个组,排序就完成了
public class ShellSort {
public static int[] shellSort(int[] array){
int len=array.length;
int temp,gap=len/2;
while(gap>0){
for(int i=gap;i<len;i++){
temp=array[i];
int preIndex=i-gap;
while(preIndex>=0&&temp<array[preIndex]){
array[preIndex+gap]=array[preIndex];
preIndex-=gap;
}
array[preIndex+gap]=temp;
}
gap/=2;
}
return array;
}
}
6、归并排序
基本思想:递归的将两个已排序的序列合并成一个序列
public class MergeSort {
public static int[] mergeSort(int[] array){
if(array.length<2) return array;
int mid=array.length/2;
int[] left= Arrays.copyOfRange(array,0,mid);
int[] right=Arrays.copyOfRange(array,mid,array.length);
return merge(mergeSort(left),mergeSort(right));
}
public static int[] merge(int[] left,int[] right){
int[] result=new int[left.length+right.length];
for(int index=0,i=0,j=0;index<result.length;index++){
if(i>=left.length)
result[index]=right[j++];
else if(j>=right.length)
result[index]=left[i++];
else if(left[i]>right[j])
result[index]=right[j++];
else
result[index]=left[i++];
}
return result;
}
}
7、堆排序
基本思想:将待排序列表构造成一个最大堆,堆顶元素即为最大值,将堆顶元素与堆尾元素交换,堆大小减1,重复直至堆大小为1
public class HeapSort {
static int len;
public static int[] heapSort(int[] array){
len=array.length;
if(len<1) return array;
buildMaxHeap(array);
while(len>0){
int temp=array[len-1];
array[len-1]=array[0];
array[0]=temp;
len--;
adjustHeap(array,0);
}
return array;
}
public static void buildMaxHeap(int[] array){
//从最后一个非叶子节点开始向上构造最大堆
//for循环这样写会更好一点:i的左子树和右子树分别2i+1和2(i+1)
len= array.length;
for (int i = (len/2- 1); i >= 0; i--) {
adjustHeap(array, i);
}
}
public static void adjustHeap(int[] array, int i) {
int maxIndex = i;
int left=i * 2 + 1;
int right=i * 2 + 2;
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if (left < len && array[left] > array[maxIndex])
maxIndex = left;
//如果有右子树,且右子树大于父节点,则将最大指针指向右子树
if (right < len && array[right] > array[maxIndex])
maxIndex = right;
//如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
if (maxIndex != i) {
int temp=array[maxIndex];
array[maxIndex]=array[i];
array[i]=temp;
adjustHeap(array, maxIndex);
}
}
}
二、外部排序:内存中无法保存全部数据,需要进行磁盘访问,每次读入部分数据到内存进行排序
8、计数排序
基本思想:对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。
public class CountingSort {
public static int[] countingSort(int[] array){
if(array.length==0) return array;
int bias,min=array[0],max=array[0];
for(int i=1;i<array.length;i++){
if(array[i]>max)
max=array[i];
if(array[i]<min)
min=array[i];
}
bias=0-min;
int[] bucket=new int[max-min+1];
Arrays.fill(bucket,0);
for(int i=0;i<array.length;i++){
bucket[array[i]+bias]++;
}
int index=0,i=0;
while(index<array.length){
if(bucket[i]!=0){
array[index]=i-bias;
bucket[i]--;
index++;
}else{
i++;
}
}
return array;
}
}
9、桶排序
基本思想:桶排序又叫箱排序,它是计数排序的升级版,利用了函数的映射关系将数据分到有限数量的桶里,每个桶再分别进行排序(使用别的排序方法或者递归的继续使用桶方法排序)
public class BubbleSort {
public static int[] bubbleSort(int[] array){
if(array.length==0) return array;
for(int i=0;i<array.length;i++){
for(int j=0;j<array.length-i-1;j++){
if(array[j+1]<array[j]){
int temp=array[j+1];
array[j+1]=array[j];
array[j]=temp;
}
}
}
return array;
}
}
10、基数排序
基本思想:将所有待比较正整数统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始进行基数为10的计数排序,一直到最高位计数排序完后,数列就变成一个有序序列(利用了计数排序的稳定性)
public class RadixSort {
public static int[] radixSort(int[] arr){
if(arr==null||arr.length<2) return arr;
//1.计算出最大数的位数
int max=arr[0];
for(int i=1;i<arr.length;i++){
max=Math.max(max,arr[i]);
}
int maxDigit=0;
while(max!=0){
max/=10;
maxDigit++;
}
int mod=10,div=1;
ArrayList<ArrayList<Integer>> buckerList=new ArrayList<>();
for(int i=0;i<10;i++){
buckerList.add(new ArrayList<Integer>());
}
for(int i=0;i<maxDigit;i++,mod*=10,div*=10){
for(int j=0;j<arr.length;j++){
int num=(arr[j]%mod)/div;
buckerList.get(num).add(arr[j]);
}
int index=0;
for(int j=0;j<buckerList.size();j++){
for(int k=0;k<buckerList.get(j).size();k++){
arr[index++]=buckerList.get(j).get(k);
}
buckerList.get(j).clear();
}
}
return arr;
}
}