八大排序--思路与代码

各项对比:

排序法平均时间最差情形稳定度额外空间备注
冒泡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;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值