十大排序算法

一、冒泡排序
谁大谁上,每一轮都把最大的顶到天花板,效率太低O(n²)。

就是从数组中依次取出元素与数组剩下的元素相比较,当前面的元素比后面的元素大就交换。

public class demo0 {
    public static void main(String[] args) {
        int[] arr = {1,99,24,63,4,34,35,6,32,3};
        Bubbling(arr);
        System.out.println(Arrays.toString(arr));//打印结果
    }
    public static void Bubbling(int[] arr){
        //第一层循环控制要比的数,
        for (int i = 0; i < arr.length-1; i++) {
             //每循环一次就会有一个排好的数不用比较,比较数就减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;
                }
            }
        }
    }
}

二、选择排序

效率较低,但经常用它内部的循环方式来找最大值和最小值 O(n²)。

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,99,24,63,4,34,35,6,32,3};
        selectSort(arr);//排序
        System.out.println(Arrays.toString(arr));//打印结果
    }

    //思路:
    //选出一个最小值与第一个元素交换,选出第二小值与第二个元素交换。。。。
    public static void selectSort(int[] arr){
        for (int j = 0; j < arr.length; j++) {
            int min = j; //最小值下标
            //遍历数组获取最小值
            for (int i = j+1; i < arr.length; i++) {
                if (arr[i] < arr[min]){
                    min = i;
                }
            }
            //将最小值与第一个元素相换
            int temp = arr[j];
            arr[j] = arr[min];
            arr[min] = temp;
        }
    }
}
三、插入排序
平均效率低,但是在序列基本有序时,它很快O(n²)。

思路:把小的往左边移动
首先,依次取出 1-arr.length的元素与 数组0至i-1相比较,谁小谁就往左边移动。
将取出的元素保存至temp,好进行比较和赋值。
循环将1-arr.length的元素temp与 数组0至i-1相比较,当0至i-1小于等于temp不进行交换,但大于temp时进行交换。
public class demo2 {
    public static void main(String[] args) {
        int[] arr = {-1, 0, 1, 10, 2, 9, 3, 8, 4, 7, 5, 6, -1, 0, 1};
        insertSort1(arr);
        System.out.println(Arrays.toString(arr));
    }


    public static void insertSort1(int[] arr) {
        // 首先,依次取出 1-arr.length的元素与 数组0-i相比较
        for (int i = 1; i < arr.length; i++) {
            int temp = arr[i]; //保存取出的元素,好进行比较和赋值
            int j = i - 1;

            // j要大于等于0,j小于0,数组下标越界,j就是从 i-1遍历到0,temp就是要和i-1至0相比较
            // 交换位置的另一个条件就是大于temp,小于等于temp就不叫唤位置。
            while (j >= 0 && arr[j] > temp) {
                arr[j + 1] = arr[j];
                j--;
            }
            //当0至i-1小于等于temp不进行交换,arr[j + 1] = temp;,
            //当0至i-1大于temp,这里相当于arr[j] = temp;
            arr[j + 1] = temp; //不交换位置
        }
    }

    public static void insertSort2(int[] arr) {
        // 首先,依次取出 1-arr.length的元素与 数组0至i-1相比较
        for (int i = 1; i < arr.length; i++) {
            int tmp = arr[i];
            boolean isInsert=false;

            for (int j = i-1; j >=0 ; j--) {
                //如果遇到比自己大的,大的值就后移
                if (tmp < arr[j]){
                    arr[j+1]=arr[j];
                }else {//如果遇到比自己小的,就在后方插入
                    arr[j+1]=tmp;
                    isInsert=true;
                    break;
                }
            }
            //如果一直没插入,就插入在首位
            if (!isInsert){//isInsert==false
                arr[0]=tmp;
            }
        }
    }
}

四、希尔排序

分组插入排序,最开始以间隔为长度一半分组,每组插入排序,然后间隔再一半再分组,直到间隔为1,
里面的插入相当于把普通插入排序1改成间隔,但是第二个for里面不改,是i++
不是把每个组拿出来,往自己组做插入排序
5往9插,4往8插,3往7插,2往6插,1往5和9插,来实现每间隔组插入排序
public class demo3 {
    public static void main(String[] args) {
        int[] arr = {9,9,3,4,6,7,3,8};
        shellSort(arr); //排序
        System.out.println(Arrays.toString(arr));//打印结果
    }
    
    public static void shellSort(int[] arr){
        //不断的缩小增量
        for (int interval=arr.length/2; interval > 0; interval/=2) {
            //增量为interval的 插入排序
            for (int i = interval; i < arr.length; i++) {
                int temp = arr[i];
                int j = i-interval;
                while (j>=0 && temp<arr[j]){
                    arr[j+interval] = arr[j];
                    j-=interval;
                }
                arr[j+interval]=temp;
            }
        }
    }
}

五、快速排序

是软件工业中最常见的常规排序法,其双向指针扫描和分区算法是核心,往往用于解决类似问题,特别地partition算法用来划分不同性质的元素,partition->selectK,也用于著名的top问题。
O(NlgN),但是如果基准数(主元) 不是中位数的话,特别地如果每次主元都在数组区间的一侧,复杂度将退化为N²。

快速排序算法的优化:
三点取中法:在数组中取左中右三个值,取中间值作为基准数
绝对中值法:
小数据量用插入排序:当待排序数组小于等于8的时候,使用插入排序

一遍单向扫描法
    /** 基准数 == 主元
     * 一遍扫描法的思路是,用两个指针将数组划分为三个区间(小于基准数区间 sp-> 未知区间 <-bigger 大于基准数区间)
     *
     * 使用两个指针,一个扫描指针(sp),一个尾指针(bigger),
     * 扫描指针确认元素小于基准数,当元素大于基准数就将该元素与尾指针的元素交换。
     * 扫描指针左边的元素确认小于基准数,尾指针右边的元素确认大于基准数。
     *
     * 当结束后,基准数将数组分为两个部分,基准数也归位。
     * 在通过相同的排序思想,分别对基准数两边的区域进行快速排序,左边对[left, 基准数-1]子数组排序,右边则是[基准数+1, right]子数组排序。
     * 利用递归算法,对分治后的子数组进行排序。
     *
     * 排完序后,不用合并数组,因为数组右侧的数就是比数组左侧的要大。
     */

public class _5快速排序 {
    public static void main(String[] args) {
        int[] arr = {12,2,31,4,5,6,7,8,9,10,23,4,54,32,45};

        QuickSort(arr,0,arr.length-1); //p 扫描指针,q 尾指针
        System.out.println(Arrays.toString(arr));

    }

    //将大于基准数的元素与尾指针的元素交换
    static void swap(int arr[],int sp,int bigger){
        int temp = arr[sp];
        arr[sp] = arr[bigger];
        arr[bigger] = temp;
    }

    //快速排序,注重分区思想
    static void QuickSort(int arr[], int p, int q){
        //当p指针小于q指针就进行排序,当p指针大于等于q指针就结束排序
        if(p<q){
            int base = partition2(arr,p, q);
            QuickSort(arr, p,base -1); // 基准数左侧子数组
            QuickSort(arr,base +1, q); //基准数右侧子数组
        }
    }


    static int partition(int arr[],int p,int q){
        int base  = p; //直接取第一个数作为基准数
        int sp = p+1; //扫描指针
        int bigger=q; //尾指针

        while (sp <= bigger){ //扫描指针小于等于尾指针就进行扫描
            if(arr[sp] <= arr[p]){ //扫描指针的元素小于等于基准数,就不变,将扫描指针指向下一位
                sp++;
            }else{ //扫描指针的元素大于基准数,交换 扫描指针元素 和 尾指针元素,并将尾指针向前移一位
                swap(arr, sp, bigger);
                bigger--;
            }
        }
        //将最后尾指针上的值与基准数做交换,此时基准数就找到了临界点的位置k,位置k两边的数组都比当前位置k上的基准值或都更小或都更大。
        swap(arr, base , bigger);
        return bigger; //将基准数返回,进行子数组区域划分
    }
}

   

//双向扫描法
    /**
     * 双向扫描的思路是,头尾指针往中间扫描,从左找到大于主元的元素,从右找到小于等于主元的元素二者交换,继续扫描,直到左侧无大元素,右侧无小元素.
     */

    static int partition2(int arr[], int _left, int _right){
        int base = _left; //取第一个数作为就基准数
        int left = _left+1; //头指针
        int right = _right; //尾指针

        while(left <= right){ //当头指针小于等于尾指针进行扫描
            //当小于等于尾指针 并且头指针的元素小于等于基准数,就将头指针指向下一位
            //要是头指针大于尾指针,或者头指针元素大于基准数,就退出循环,交换头指针和尾指针元素
            while(left<=right && arr[left]<=arr[base]) {
                left++;
            }
            //当小于等于尾指针 并且尾指针元素大于基准数,就将尾指针指向前一位
            //要是头指针大于尾指针,或者尾指针元素小于等于基准数,就退出循环,交换头指针和尾指针元素
            while(left<=right && arr[right]>arr[base]){
                right--;
            }
            //第一个循环,头指针会找到一个大于基准数的元素,并退出循环,进入第二循环,寻找小于基准数的元素,找到后退出循环,交换两个元素。

            //判断头指针,是否小于尾指针,小于则交换。
            if(left < right){
                swap(arr,right,left);
            }
        }

        //将基准数与尾指针元素交换
        swap(arr,base,right);
        return right; //返回基准数。
    }

六、归并排序,
归并重视子问题的解的合并,空间换时间——逆序对数
分解:将n个元秦分成各含n/2个元素的子序列;
解决:对两个子序列递归地排序;
合并:合并两个已排序的子序列以得到排序结果
public class _6归并排序 {
    public static void main(String[] args) {
        int[] arr = {12,2,31,4,5,6,7,8,9,10,23,4,54,32,45};
        int[] arrCopy = arr.clone(); //arrCopy为拷贝数组,arr为原数组
        MergeSort(arrCopy, arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

    //归并排序:入口
    static void MergeSort(int[] arrCopy,int[] arr,int start,int end){ //arrCopy 拷贝数组,arr 原数组,start 头指针,end 尾指针
        //arrCopy为拷贝,arr为要排序数组,nowArr是一个新的空数组
        int[] nowArr = new int[arr.length];

        if(start == end) { //当头指针等于尾指针,将
            arr[start] = arrCopy[start];
        }else{
            //中间下标,将arrCopy[s..e] 平分为arrCopy[s..m] 和arrCopy[m+1..e]
            int min = (start + end) / 2;

            // 分区:使用min,将arrCopy不断划分,成arrCopy[start ..min],arrCopy[min+1 ..end]
            MergeSort(arrCopy, nowArr, start, min); //递归地将arrCopy[s..m]归并为有序的nowArr[s..e]
            MergeSort(arrCopy, nowArr, min+1, end); // 递归地将arrCopy[m+1.. e]归并为有序的nowArr[m+1..e]

            // 归并:将nowArr[s.. m] 和nowArr[m+1.. e]归并到 arr[s..e    ]
            Merge(nowArr, arr, start, min, end);
        }
    }

    //归并
    static void Merge(int[] nowArr,int[] arr, int start,int min,int end){
        //将nowArr左右半有序。归并到arr
        int x, y; //x为arr合并位置下标,y为右边最小元素下标,start为左边最小元素下标

        //当头指针 小于等于中间下标 且右边最小元素下标 小于等于尾指针,开始循环
        for(x=start,y=min+1; start<=min&&y<=end; ++x){
            //当左边最小元素下标上的值 小于右边最小元素下标上的值,就将左边最小元素下标上的值赋到arr数组上,并将指针指向下一位。
            //否则就是右边最小元素下标上的值 大于等于左边最小元素下标上的值,就将右边最小元素下标上的值赋到arr数组上,并将指针指向下一位。
            if(nowArr[start] < nowArr[y]){
                arr[x] = nowArr[start++];
            }else{
                arr[x] = nowArr[y++];
            }
        }

        //开始头指针 小于等于中间下标
        while(start <= min){
            arr[x++] = nowArr[start++];
        }

        while (y <= end){
            arr[x++] = nowArr[y++];
        }
    }
}

七、堆排序

1、堆的概念:
二叉树 --满足二叉堆的性质-> 二叉堆
二叉堆是完全二叉树或者是近似完全二叉树。
完全二叉树:每一个父节点下面都有两个子节点。
近似完全二叉树:左侧是排满的,右边有空缺。

二叉堆满足二个特性:
    1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
    2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
任意节点的值都大于其子节点的值—大顶堆
任意节点的值都小于其子节点的值—小顶堆

堆排序
    1.堆化,反向调整使得每个子树都是大顶或者小顶堆。
    2.按序输出元素:把堆顶和最末元素对调,然后调整堆顶元素。
public class _7堆排序 {
    public static void main(String[] args) {
        //数组第一位预留,不参与排序
        int arr[] = new int[]{0, 49, 38, 65, 97, 76, 13, 27, 49};
        HeapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    //堆化
    static void HeapSort(int[] H){
        //H.length-1相当于H的length
        for(int i=(H.length-1)/2; i>0; i--){//把H.r[1.. H.length]建成大顶堆
            HeapAdjust(H, i,H.length-1);//从(H.length-1)/2位最后一个父亲节点,依次往前调整
        }
        for (int i=H.length-1; i >1 ; i--) {
            _5快速排序.swap(H,1,i);//将堆顶(1)记录和当前未经排序子序列H[1.. i]中最后一个记录相互交换
            HeapAdjust(H,1,i-1);//将H[1..i-1]重新调整为大顶堆
        }
    }


    static void HeapAdjust(int[] H, int s, int m){ // H 堆,s 中间数,m 堆的长度
//    已知s.. m中记录的关键字除H[s]之外均满足堆的定义,本函数调整H[s]的关键字,使H[s..m]成为一个大顶堆
        int rc=H[s];//先把父亲节点值存到rc中
        for (int j = 2*s; j <=m ; j*=2) {//j为s的左孩子,循环一次后,j*2,变成最大孩子的左孩子
            if(j<m&&H[j]<H[j+1]){//如果右孩子大于左孩子,j++,j就为最大孩子的坐标
                j++;
            }
            if(rc>=H[j]){//父元素已经是两个孩子中最大的了,则for循环退出
                break;
            }
            H[s]=H[j];//把最大孩子的值赋值给父亲节点
            s=j;//s保存当前最大孩子的下标
        }
        H[s]=rc;//把父亲值赋值给最后一次孩子下标
    }


    //递归调整
    static void HeapAdjust2(int A[],int i,int n){//i为父节点,本函数使A[i..n]成为大顶堆
        int left=2*i;
        int right=2*i+1;
        if(left>n){//左孩子已经越界返回
            return;
        }
        int max=left;
        if(right<=n&&A[right]>A[left]){
            max=right;
        }//max指向左右孩子中较大的那个
        if(A[i]>=A[max]){//如果A[i]把;两个孩子都大,不同调整
            return;
        }
        _5快速排序.swap(A,max,i); //否则,找到两个孩子中较大的,和i交换
        HeapAdjust2(A,max,n);  //大孩子那个位置的值发生了变化,i变更为大孩子那个位置,递归调整
    }
}

8、计数排序,空间换时间
可以说是最快的:O(N+k),k=maxOf(sourceArr),
用它来解决问题时必须注意如果序列中的值分布非常广(最大值很大,元素分布很稀疏),空间将会浪费很多
所以计数排序的适用范围是:序列的关键字比较集中,已知边界,且边界较小。
public class demo14 {
    public static void main(String[] args) {
        int[] arr = {9,8,55,66,81,99,88,38,66,18};
        count(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void count(int[] arr){
        //第一步找出数组中最大值
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (max < arr[i]){
                max = arr[i];
            }
        }

        //第二步创建辅助数组
        int[] helpArr = new int[max+1];

        //第三步进行计数
        for (int i = 0; i < arr.length; i++) {
            helpArr[arr[i]]+=1;
        }

        //第四步进行回填
        int count = 0;
        for (int i = 1; i < helpArr.length; i++) {
            while (helpArr[i] > 0) { //当辅助数组的值大于0,就进行回填
                arr[count++] = i; //
                helpArr[i]--;
            }
        }
    }
}

九、桶排序

先分桶,再用其他排序方法对桶内元素排序,按桶的编号依次检出。(分配-收集)
用它解决问题必须注意序列的值是否均匀地分布在桶中。如果不均匀,那么个别桶中的元素会远多于其他桶,桶内排序用比较排序,极端情况下,全部元素在一个桶内,还是会退化成NlgN
其时间复杂度是:时间复杂度: O(N+C),其中C=N*(logN-logM),约等于N*lgN,N是元素个数,M是桶的个数。

十、基数排序

kN级别(k是最大数的位数) 是整数数值型排序里面又快又稳的,无论元素分布如何,只开辟固定的辅助空间(10个桶) 对比桶排序,基数排序每次需要的桶的数量并不多。而且基数排序几乎不需要任何“比较”操作,而桶排序在桶相对较少的情况下, 桶内多个数据必须进行基于比较操作的排序。因此,在实际应用中,对十进制整数来说,基数排序更好用。

思路:

将整数按位数切割成不同的数字,然后按每个位数分别比较。
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。
这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
public class demo15 {
    public static void main(String[] args) {
        int arr[] = { 53, 3, 542, 748, 14, 214};
        radixSort(arr);
        System.out.println("基数排序后 " + Arrays.toString(arr));

    }

    //基数排序方法
    public static void radixSort(int[] arr) {
        //根据前面的推导过程,我们可以得到最终的基数排序代码

        //1. 得到数组中最大的数的位数
        int max = arr[0]; //假设第一数就是最大数
        for(int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        //得到最大数是几位数
        int maxLength = (max + "").length();

        //定义一个二维数组,存放表示10个桶,每一个桶的大小是 arr.length
        int[][] bucket = new int[10][arr.length];

        //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
        //可以这里理解
        //比如:arrCount[0] , 记录的就是  bucket[0] 桶的放入数据个数
        int[] arrCount = new int[10];


        //这里我们使用循环将代码处理
        for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
            //(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
            for(int j = 0; j < arr.length; j++) {
                //取出每个元素的对应位的值
                int number = arr[j] / n % 10;
                //放入到对应的桶中
                bucket[number][arrCount[number]] = arr[j];
                arrCount[number]++;
            }
            //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            //遍历每一桶,并将桶中是数据,放入到原数组
            for(int k = 0; k < arrCount.length; k++) {
                //如果桶中,有数据,我们才放入到原数组
                if(arrCount[k] != 0) {
                    //循环该桶即第k个桶(即第k个一维数组), 放入
                    for(int l = 0; l < arrCount[k]; l++) {
                        //取出元素放入到arr
                        arr[index++] = bucket[k][l];
                    }
                }
                //第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
                arrCount[k] = 0;

            }
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值