各项对比:
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
---|---|---|---|---|---|
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(nk) | 稳定 | O(n) | ||
希尔 | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
1、冒泡排序
每一轮两两比较(交换),找出一个最大值放在最后,第一轮比较x-1次,第二轮x-2次,直到最后一轮比较1次。算下来平均一次比较了[1+(x-1)]/2次,一共比较了x-1轮,所以时间复杂度为(1+x-1)*(x-1)/2 【也可以这样算:1+x-1+x-2+x-3+x-4+……=(1+x-1)*(x-1)/2 】 即O(n^2)
public class MaopaoSort {
public static void main(String[] args) {
int[] arr = new int[]{42, 4, 6, 7,43,432};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-1-i; j++) {
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
}
2、快速排序
取出第一个数,其他所有的数与它比较,比它小的放左边,比它大的放右边,第一轮比较了x-1次,(因为选择出第一个数也被算入,所以算比较了x次)。第一轮排完之后,这个数就被排好了,之后就不会改变了,第一轮排完之后将其他数分为左右两个部分,左边的一部分在第二轮又取出第一个数与其他数比较,右边一样。所以第二次比较了x-1次。
-----第一轮选择出了1个数,第二轮选择出来2个数,第三轮选择出来4个数,第k轮选择出来2^(k-1)个数,用等比公式求和得出n个数=2^k-1,即k=logn(k表示排了k轮)
-----第一轮比较了x-1次,第二轮比较了x-3次,第三轮x-7次,最后一轮(logn)也比较次数也在n的基础之上(n/2次),所以每一轮比较的时间复杂度是O(n)
每一轮的计算量的O(n),一共排了logn轮,所以时间复杂度为O(nlogn)
public class fastSort {
public static void main(String[] args) {
int[] arr=new int[]{43,2,67,76,9};
sort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr,int left,int right) {
//递归出口
if(left>=right){
return;
}
int base=arr[left];
int i=left;
int j=right;
//i不等于j时
while(i!=j){
while(arr[j]>=base&&i<j){
j--;
}
while(arr[i]<=base&&i<j){
i++;
}
//i和j交换位置
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
//i等于j时,i/j和基准数交换位置
arr[left]=arr[i];
arr[i]=base;
//递归关系
sort(arr,left,i-1);
sort(arr,i+1,right);
}
}
3、归并排序(和快排有点像)
先通过递归拆成一个一个的数,然后再合并,其核心就是合并有序数列,每次去最小值放到temp数组里面,最后再遍历temp数组
(看作是x个数)第一轮将每一个数看作是一组,一共有x组,第二轮将第一个数与第二个数进行两两合并(并比较),第三轮将前两个数与第3、4这一组的数合并(并比较)……
一共比较了n=logx轮,每次比较都小于x次(16个数第二轮比较8次,第三轮每一组四个数,一组最多比较3次,第三轮最多比较12次,第四轮每一组八个数,最多比较14次,第五轮每一组16个数,最多比较15次),所以每一轮排序的时间复杂度都为O(n)。即总的时间复杂度为O(nlogn)
public class MergeSort {
public static void main(String[] args) {
int[] arr=new int[]{432,3,6,12,90,5};
sort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr,int left,int right) {
//递归出口
if(left>=right){
return;
}
int i=(left+right)/2;
//递归关系
sort(arr,left,i);
sort(arr,i+1,right);
int[] temp=new int[right-left+1];
int s1=left;
int s2=i+1;
int index=0;
while(s1<=i&&s2<=right){
if(arr[s1]<=arr[s2]){//最小值放入
temp[index]=arr[s1];
index++;
s1++;
}else{
temp[index]=arr[s2];
index++;
s2++;
}
}
while(s1<=i){
temp[index]=arr[s1];
index++;
s1++;
}
while(s2<=right){
temp[index]=arr[s2];
index++;
s2++;
}
//数据放入
for (int j = 0; j < temp.length; j++) {
arr[j+left]=temp[j];
}
}
}
4、选择排序(堆排序)
【完全二叉树:数据从上到下,从左到右,每一层都必须是满的,最下边所有节点都是连续集中在最左边。对数据的有序与否没有要求。
大顶堆:在完全二叉树的基础上,每个结点的值都大于或等于其左右孩子结点的值。
小顶堆:在完全二叉树的基础上,每个结点的值都小于或等于其左右孩子结点的值。
如何构建大顶堆?判断父节点有没有孩子,如果有孩子,将最大孩子节点的值和父节点的值进行对比,如果孩子的值大于父节点的值,那么两两交换。(从下往上遍历)。为了保证构建完成大顶堆,最后一个两两交换完成之后,需要让父节点向下指向原来的子节点的位置(父子一起降级),有子大于父的情况继续交换位置,然后再继续往下走。】
堆排序的步骤:
a、构建完成大顶堆。
b、堆顶元素与堆底元素进行互换,堆底元素不再参与构建。
c、父节点指针指向根节点,和最大孩子节点的值进行对比,然后继续往下构建降级(也就是前面的构建大顶堆不需要从下往上遍历,只需要从上往下进行遍历,交换位置)
d、之后再重复此步骤,将堆顶元素与堆底元素进行互换,遍历,互换遍历(从最后一个数据往上走,注意同一排时是右到左)。 也就是重复构建大顶堆的过程。
最后,不再参与构建的值取出来就从大到小排列好了有序数组
a-b-c-b-c-b-c-……
时间复杂度:一共交换了n次,即n轮,第一步的大顶堆只构建了一次(忽略不计),往后的步骤都是主要在2、3步,每进行一轮的时间复杂度是logn(循环减半,完全二叉树在进行从上往下遍历时省略掉了没有指向的那部分),所以总的时间复杂度为O(nlogn)
public class DuiSort {
public static void main(String[] args) {
int[] arr = new int[]{2, 5, 34, 89, 9, 4, 32, 77};
for(int parent=arr.length-1;parent>=0;parent--){
sort(arr,parent,arr.length);
}
for(int i=arr.length-1;i>0;i--){
int temp=arr[0];
arr[0]=arr[i];
arr[i]=temp;
sort(arr,0,i);
}
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr,int parent,int length){
int child=parent*2+1;
while(child<length){
int rChild=child+1;
if(rChild<length&&arr[rChild]>arr[child]){
child=rChild;
}
if(arr[parent]<arr[child]){
int temp=arr[parent];
arr[parent]=arr[child];
arr[child]=temp;
parent=child;
child=child*2+1;
}else {
break;
}
}
}
}
5、插入排序
在数组中默认第一个数是已经排好序的数,将第二个数据插入到由第一个数据组成的数组当中并排序,然后将第三个数据……(与前面的一个个数进行比较,如果比前面的数小就进行位置交换)
插排的缺点:插入的数值越小,向前移动的次数明显增多,会增加计算机的性能开销。 --所以希尔排序就很大程度上减少了这种问题带来的开销。
时间复杂度:每个数都插了一遍,所以进行了n轮;每个数插入的时候都遍历了前面的整个数组,所以每一轮的时间复杂度是n,所以总的时间复杂度就是O(n^2)
public class InsertSort {
public static void main(String[] args) {
int[] arr=new int[]{5,7,4,2,0,3,1,6};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr) {
for(int i=0;i<arr.length;i++) {
for(int j=i-1;j>=0;j--) {
if(arr[j]>arr[j+1]) {
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
}
6、希尔排序
首先,第一轮将数组分为x/2组(例如八个数的序列将第一个与第五个分为一组,第二个与第六个分为一组……),然后每个小组内进行排序,第一轮的时间复杂度为n/2(O(n));第二轮将数组分为x/4组(八个数的序列将第一个、第三个、第五个分为一组)时间复杂度也在O(n)级别;第三轮将数组分为n/8组(八个数的序列整体分为一组,时间复杂度为O(n)
总共进行了logn轮(循环减半,logn),所以,总的时间复杂度为O(nlogn)
public class ShellSort {
public static void main(String[] args) {
int[] arr=new int[]{5,7,4,2,0,3,1,6};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr) {
for(int gap=arr.length/2;gap>0;gap/=2) {//分组,如果是八位数,先分成4组,再分成2组,再分成1组
for(int i=gap;i<arr.length;i++) {//分成4组时i从第五位数开始++
for(int j=i-gap;j>=0;j-=gap) {//j先跟着i向前移动,向后步长数能指向有数据的值时就向后移动
//j永远比i小步长个数,之后j再往前遍历,直到该组排好序
if(arr[j]>arr[j+gap]) {//j和j+步长位(第一次时j和i)交换位置
int temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
}
}
}
7、简单选择排序
首先找到待排序数组中的最小值与第一个数进行交换,然后从剩下的数中找到最小值与第二个数进行交换……
一轮遍历n/n-1/n-2……个数,时间复杂度为O(n);一共进行了n轮,总的时间复杂度为O(n^2)
public class SearchSort {
public static void main(String[] args) {
int[] arr=new int[]{5,7,4,2,0,3,1,6};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void selectSort(int[] arr) {
for(int i=0;i<arr.length;i++) {
int minIndex=i;//minIndex代表实际最小值的地址
int min=arr[i];//min代表假设的最小值,即第一个
for(int j=i+1;j<arr.length;j++) {
if(min>arr[j]) {
min=arr[j];
minIndex=j;
}
}
//交换
arr[minIndex]=arr[i];
arr[i]=min;
}
}
}
8、基数排序(桶排序)
个位--十位--百位--……
先按个位依次放入桶中:一共有10个桶,分别为0123456789,个位为1的放如1桶中,每个数字放好之后按从0到9按序取出来(从下往上的顺序取);之后再如此步骤按十位大小放入桶中并取出,再按百位大小……最终从小到大就排序好了。
时间复杂度:进行了k轮排序,k表示最大值的位数,由于是不可预定的,所以不能省略,每一轮都对x个数进行了遍历,时间复杂度为O(n),所以总的时间复杂度为O(kn)
public class TongSort {
public static void main(String[] args) {
int[] arr=new int[]{7,43,111,46,90,9,432,54,6};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr){
int max=arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i]>max){
max=arr[i];
}
}
int maxLength=(max+"").length();
int[][] bucket=new int[10][arr.length];
int[] bucketElement=new int[10];
int n=1;
for (int m = 0; m < maxLength; m++) {
for (int i = 0; i < arr.length; i++) {
int element=arr[i]/n%10;
int count=bucketElement[element];
bucket[element][count]=arr[i];
bucketElement[element]++;
}
int index=0;
for (int j = 0; j < bucketElement.length; j++) {
if(bucketElement[j]>0){
for (int l = 0; l < bucketElement[j]; l++) {
arr[index]=bucket[j][l];
index++;
}
}
bucketElement[j]=0;
}
n=n*10;
}
}
}