2 十大经典排序算法

清华架构师马士兵老师呕心沥血打造算法与数据结构(十大经典排序算法)全套视频教程:https://www.bilibili.com/video/BV1Wq4y1Z7yi/?spm_id_from=333.337.search-card.all.click

0 前言

【1预告】
为什么讲这门课?
面试我最强
你值得拥有

什么人可以听这门课?
逻辑能力
任何一门语言基础,最好是java、c++、c中的一种

什么人应该听?
需要做面试题
想进大厂
提升内功

【2算法基本概念】
什么是数据结构?
Data Structure
存储数据的不同方式

什么是算法?
同一问题的不同的解决方法
算法往往是针对特定数据结构的

如何测算算法的优劣?
时间测算,1.计算算法时间差,2.幅度不够循环来凑
空间测算

Big O
时间-问题(数据)规模:
1.不考虑必须要做的操作,例如:循环、赋初值、程序初始化
2.不考虑常数项,例如:2n -》 n
3.不考虑低次项,例如:n2+n -》 n2
O:
1.时间 -》 规模
2.用计算时间差的方式测算
3.数组、链表

【3排序算法-宋词记忆法】

重要的排序:插入排序、堆排序、归并排序、快速排序
《忆排序 面试我最强》
选泡插,
快归堆希桶计基,
恩方恩老恩一三,
对恩加K恩乘K,
不稳稳稳不稳稳,
不稳不稳稳稳稳。


【4如何写算法程序】
1.由简单到复杂
验证一步走一步
多打印中间结果
2.先局部后整体
没思路时先细分
3.先粗糙后精细
变量更名
语句合并
边界处理

1 选择排序

【选择排序】

package com.liweixiao;

import static com.liweixiao.UtilTools.print;
import static com.liweixiao.UtilTools.swap;

/**
 * @author:LiWeixiao
 * @date:2023/3/6
 * @description:选择排序
 */
public class SelectionSort {
    public static void main(String[] args) {
        int[] arr={5,3,6,8,1,7,9,10,2,4};

        for (int i = 0; i < arr.length-1; i++) {
            int minPos=i;
            for (int j = i+1; j < arr.length; j++) {
                minPos= arr[j]<arr[minPos] ? j : minPos;
            }

            swap(arr,i,minPos);

            System.out.println("\n经过第"+i+"轮循环:");
            print(arr);
        }
    }
    public static void sort(int[] arr) {
        for (int i = 0; i < arr.length-1; i++) {
            int minPos=i;
            for (int j = i+1; j < arr.length; j++) {
                minPos= arr[j]<arr[minPos] ? j : minPos;
            }

            swap(arr,i,minPos);
        }
    }
}

【常用工具】

package com.liweixiao;

/**
 * @author:LiWeixiao
 * @date:2023/3/6
 * @description:常用工具
 */
public class UtilTools {
    //循环打印方法
    static void print(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
    }

    //交换方法
    static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
}

【验证算法-对数器】
如何验算你的算法是否正确?
1.肉眼观察
2.产生足够多随机样本
3.用确定正确的算法计算样本结果
4.对比被验证算法的结果

package com.liweixiao;

import java.util.Arrays;
import java.util.Random;

/**
 * @author:LiWeixiao
 * @date:2023/3/6
 * @description:对数器
 */
public class DataChecker {
    //随机生成数组
    static int[] generateRandomArray(){
        Random r=new Random();
        int[] arr=new int[10000];
        for (int i = 0; i < arr.length; i++) {
            arr[i]=r.nextInt(10000);
        }
        return arr;
    }

    //对数
    static void check(){
        /*Arrays.sort耗时4
        选择排序耗时37
        冒泡排序耗时135
        插入排序耗时73
        希尔排序耗时196
        Knuth排序耗时109*/
        
        boolean same=true;
        for (int times = 0; times < 1000; times++) {
            int[] arr1 = generateRandomArray();
            int[] arr2=new int[arr1.length];
            System.arraycopy(arr1,0,arr2,0,arr1.length);

            Arrays.sort(arr1);
            //SelectionSort.sort(arr2);
            //BubbleSort.sort(arr2);
            //InsertionSort.sort(arr2);
            //ShellSort.sort(arr2);
            //ShellSort.sortKnuth(arr2);
            //MergeSort.sort(arr2,0,arr2.length-1);
            QuickSort.sort(arr2,0,arr2.length-1);

            for (int i = 0; i < arr2.length; i++) {
                if(arr1[i] != arr2[i]) {
                    same=false;
                }
            }
        }

        System.out.println(same == true ? "right" : "wrong");
    }

    public static void main(String[] args) {
        check();
    }
}

2 冒泡排序

package com.liweixiao;

import static com.liweixiao.UtilTools.print;
import static com.liweixiao.UtilTools.swap;

/**
 * @author:LiWeixiao
 * @date:2023/3/6
 * @description:冒泡排序
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr={9,3,1,4,6,8,7,10,5,2};
        sort(arr);
        print(arr);
    }

    static void sort(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            boolean flag=true;
            for (int j = 0; j < arr.length-1-i; j++) {
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                    flag=false;
                }
            }

            if(flag){
                break;
            }
        }
    }
}

3 插入排序【重要】

package com.liweixiao;

/**
 * @author:LiWeixiao
 * @date:2023/3/6
 * @description:插入排序
 */
public class InsertionSort {
    public static void main(String[] args) {
        int[] arr={9,3,1,4,6,8,7,10,5,2};
        sort(arr);
        UtilTools.print(arr);
    }

    static void sort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j > 0; j--) {
                if(arr[j]<arr[j-1]){
                    UtilTools.swap(arr,j,j-1);
                }
            }
        }
    }
}

【简单排序算法总结】
冒泡,基本不用,太慢
选择,基本不用,不稳
插入,样本小且基本有序的时候效率比较高

4 堆排序【重要】

二叉树,大顶堆,小顶堆
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列。
https://www.bilibili.com/video/BV1fp4y1D7cj/?spm_id_from=333.337.search-card.all.click

下标为i的节点的父节点下标:(i-1)/2【整数除法】
下标为i的节点的左孩子下标:i2+1
下标为i的节点的右孩子下标:i
2+2

步骤:
1.建堆
2.排序,最后一个和第一个交换(最大),最后一个变成最大的,去掉最后一个,重复1、2

package com.liweixiao;

/**
 * @author:LiWeixiao
 * @date:2023/3/8
 * @description:堆排序
 */
public class HeapSort {
    public static void main(String[] args) {
        int[] arr={9,3,1,4,6,8,7,10,5,2};
        sort(arr);
        UtilTools.print(arr);
    }

    //堆排序
    static void sort(int[] arr){
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            heapify(arr,i,arr.length);
        }

        for (int j = arr.length-1; j > 0; j--) {
            int temp=arr[j];
            arr[j]=arr[0];
            arr[0]=temp;
            heapify(arr,0,j);
        }
    }

    //建堆
    static void heapify(int[] arr,int i,int length){
        int temp=arr[i];

        for(int j=i*2+1;j<length;j=j*2+1){
            if(j+1<length && arr[j]<arr[j+1]){
                j++;
            }
            if(arr[j]>temp){
                arr[i]=arr[j];
                i=j;
            }else {
                break;
            }
        }
        
        arr[i]=temp;
    }
}

5 希尔排序

D.L Shell
间隔,改进的插入排序

package com.liweixiao;

/**
 * @author:LiWeixiao
 * @date:2023/3/6
 * @description:希尔排序
 */
public class ShellSort {
    public static void main(String[] args) {
        int[] arr={9,3,1,4,6,8,7,10,5,2};
        sort(arr);
        UtilTools.print(arr);
    }

    static void sort(int[] arr){
        for (int gap=arr.length/2; gap >0 ; gap /=2) {
            for (int i = gap; i < arr.length; i++) {
                for (int j = i; j > gap-1; j-=gap) {
                    if(arr[j]<arr[j-gap]){
                        UtilTools.swap(arr,j,j-gap);
                    }
                }
            }
        }
    }
}

【Knuth排序】
唐纳德·克努特Donald Ervin Knuth
h=1
h=3*h+1

static void sortKnuth(int[] arr){
    int h=1;
    while (h <= arr.length/3){
        h=3*h+1;
    }
    for (int gap=h; gap >0 ; gap =(gap-1)/3) {
        for (int i = gap; i < arr.length; i++) {
            for (int j = i; j > gap-1; j-=gap) {
                if(arr[j]<arr[j-gap]){
                    UtilTools.swap(arr,j,j-gap);
                }
            }
        }
    }
}

6 归并排序【重要】

【1递归】方法套方法

package com.liweixiao;

/**
 * @author:LiWeixiao
 * @date:2023/3/7
 * @description:递归算法
 */
public class Test {
    public static void main(String[] args) {
        System.out.println(f(10));
    }

    static int f(int i){
        if(i<1){return 0;}
        if(i==1){return 1;}
        return i+f(i-1);
    }
}

【2归并排序】
步骤:
1.分成最小单元进行比较
2.合并算法,2个指针
3.最后剩余的数组,复制

package com.liweixiao;

/**
 * @author:LiWeixiao
 * @date:2023/3/7
 * @description:归并排序
 */
public class MergeSort {
    public static void main(String[] args) {
        int[] arr={9,3,1,4,6,8,7,10,5,2};
        sort(arr,0,arr.length-1);
        UtilTools.print(arr);
    }

    static void sort(int[] arr,int left,int right){
        if(left==right){ return;}
        //分成两半
        int mid=left+(right-left)/2;
        //左边排序
        sort(arr,left,mid);
        //右边排序
        sort(arr,mid+1,right);

        merge(arr,left,mid+1,right);
    }

    static void merge(int[] arr,int leftPtr,int rightPtr,int rightBound){
        int mid=rightPtr-1;
        int[] temp=new int[rightBound-leftPtr+1];

        int i=leftPtr;
        int j=rightPtr;
        int k=0;

        while (i<=mid && j<=rightBound){
            temp[k++] =arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i<=mid){ temp[k++]=arr[i++]; }
        while (j<=rightBound) {temp[k++]=arr[j++];}
        for (int m = 0; m < temp.length; m++) {
            arr[leftPtr+m]=temp[m];
        }
    }
}

【3】java对象排序,多种语言应用
快速排序不稳定,对象排序一般要求稳定。
Arrays.sort,基本数据类型数组使用的是双轴快速排序,对象使用的是TimSort排序。
TimSort是一个混合、稳定的排序算法,mergeSort归并排序和binarySort二分插入排序算法的混合体。

7 快速排序【重要】

【快速排序】单轴,选1个数
第1种,搞个小区,扩展,大数和小数交换
第2种,搞个小区和大区,2端同时扩展
第3种,两边同时找,找到后大数和小数直接交换,最后选择数和插入位置数交换
本文采用第三种

上图有问题

package com.liweixiao;

/**
 * @author:LiWeixiao
 * @date:2023/3/7
 * @description:快速排序
 */
public class QuickSort {
    public static void main(String[] args) {
        int[] arr={9,3,1,4,6,8,7,10,5,2};
        sort(arr,0,arr.length-1);
        UtilTools.print(arr);
    }

    static void sort(int[] arr,int leftBound,int rightBound){
        if(leftBound >= rightBound) {return;}
        int mid=partition(arr,leftBound,rightBound);
        sort(arr,leftBound,mid-1);
        sort(arr,mid+1,rightBound);
    }

    static int partition(int[] arr,int leftBound,int rightBound){
        int pivot=arr[rightBound];
        int left=leftBound;
        int right=rightBound-1;

        while (left<=right){
            while (left<=right && arr[left]<=pivot){left++;}
            while (left<=right && arr[right]>pivot){right--;}

            if(left<right){
                UtilTools.swap(arr,left,right);
            }
        }
        UtilTools.swap(arr,left,rightBound);
        return left;
    }
}

【调试Bug】
1.通读程序
2.输出中间值
3.剪功能,定位小功能
4.再加功能
5.最后的办法,对照正确的代码,对编码能力没有提升

【快排改进】
双轴快排,选2个数
第一个区域放小于小数,第二个区域放大于等于小数 小于等于大数,第三个区域放大于大数

【Arrays.sort】
pair insertion sort双插入排序
源码比较复杂
35岁干什么,成为大牛;成不了大牛,至少是一小片的小牛。

8 计数排序

非比较排序,桶思想的一种。
【算法思想】
量大但是范围小。例如:某大型企业数万人员工年龄排序;如何快速得知高考名次(腾讯面试)。

package com.liweixiao;

import java.util.Arrays;

/**
 * @author:LiWeixiao
 * @date:2023/3/8
 * @description:计数排序
 */
public class CountSort {
    public static void main(String[] args) {
        int[] arr={9,3,1,4,6,8,7,0,5,2,3,1,4,7,0,5,7,0,5,3,1,4,6,8,7,0,5,2};
        int[] result = sort(arr);
        UtilTools.print(result);
    }

    static int[] sort(int[] arr){
        int[] result=new int[arr.length];
        int[] count=new int[10];

        for (int i = 0; i < arr.length; i++) {
            count[arr[i]]++;
        }
        //System.out.println(Arrays.toString(count));

        /*//不稳定算法
        for (int i = 0,j=0; i < count.length; i++) {
            while (count[i]-- >0){
                result[j++] =i;
            }
        }*/

        //稳定算法
        for (int i = 1; i < count.length; i++) {
            count[i]=count[i]+count[i-1];
        }
        //System.out.println(Arrays.toString(count));
        for (int i = arr.length - 1; i >= 0; i--) {
            result[--count[arr[i]]] =arr[i];
        }
        
        return result;
    }
}

【总结】
计数排序是非比较排序
适用于特定问题,也就是对源数据有要求
O,时间复杂度n+k,空间复杂度n+k

9 基数排序

非比较排序,桶思想的一种,多关键字排序。
【算法思想】
多关键字排序

package com.liweixiao;

import java.util.Arrays;

/**
 * @author:LiWeixiao
 * @date:2023/3/8
 * @description:基数排序
 */
public class RadixSort {
    public static void main(String[] args) {
        int[] arr={421,240,115,532,305,430,124};
        int[] result = sort(arr);
        UtilTools.print(result);
    }

    static int[] sort(int[] arr){
        int[] result=new int[arr.length];
        int[] count=new int[10];

        for (int i = 0; i < 3; i++) {
            int division=(int)Math.pow(10,i);
            for (int j = 0; j < arr.length; j++) {
                int num=arr[j]/division % 10;
                count[num]++;
            }

            //采用计数排序
            for (int m = 1; m < count.length; m++) {
                count[m]=count[m]+count[m-1];
            }

            for (int n = arr.length-1; n >= 0; n--) {
                int num=arr[n]/division % 10;
                result[--count[num]] =arr[n];
            }

            System.arraycopy(result,0,arr,0,arr.length);
            Arrays.fill(count,0);
        }
        return result;
    }
}

【总结】
1.本质上是一种多关键字排序
2.有低位优先和高位优先两种,LSD MSD(Least Significant Digit first),MSD属于分治的思想
3.百度百科的程序有问题

10 桶排序

【总结】
1.不太重要,理解就好
2.重点了解基数排序和计数排序

【应用场景】

(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。

快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。但前面介绍的从单个记录起进行两两归并的排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子序列,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。

posted @ 2023-02-28 14:22  LiWeixiao  阅读(40)  评论(0编辑  收藏  举报