【求职·算法基础】八大排序算法

本文详细介绍了八大排序算法:冒泡排序、选择排序、插入排序、归并排序、快速排序(荷兰国旗和分治法)、堆排序以及桶排序的原理、手写思路和Java代码实现。通过对比不同排序算法的复杂度,帮助读者深入理解排序算法的内在逻辑。


经典排序算法总结

八大排序算法原理、手写思路、java代码实现

小贴心:对数器的使用
1、对数器:重复多次(eg. 5000次),每次随机生成随机长度的矩阵,用正确方法排序的结果和自己排序的结果进行比较,每次完全一致说明排对了,反之排序不对。
2、过程:自己预先定义最原始(暴力解法,即保证排序正确既可,不考虑时间空间复杂度)的方法对数组arr进行排序,排序后的数组为arr1;然后和自己设计的排序算法排序的结果arr2 进行比较。

1、BubbleSort 冒泡排序

1)原理

1、给定数组arr,定位到最后一个位置数num,用num之前的每个数和num比较,比num大则和num交换,使得第一轮找到数组最大数并放在最后位置。
2、num向前移一位,再用其前的数和其比较,最后得到数组第二大的数,放在倒数第二位置。
3、就酱循环
4、直到到达第一个数,即排序好。

2)手写思路

3)代码

  public static  void bubbleSort(int[] arr){
        if(arr == null || arr.length <2){
            return ;
        }
        for (int i = arr.length-1; i>0 ; i--) {
        //从后往前排
            for (int j = 0; j < i; j++) {
                if(arr[j] > arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
    }

2、SelectionSort 选择排序

1)手写思路

2)代码

public static  void selectionSort(int[] arr){
        if(arr == null || arr.length <2){
            return;
        }
        for (int i = 0; i < arr.length-1; i++) {
            int minIndex = i;
            for (int j = i+1; j < arr.length; j++) {
                minIndex = arr[j] < arr[minIndex] ? j:minIndex;
            }
            swap(arr,i,minIndex);
        }
    }

3、InsertSort 插入排序

1)原理

1、首先假设第i个数(i=0)排好
2、从i=0开始,让arr[i]和其左边的数(遇到边界则i++)依次比较,若小于,则交换。

2)手写思路

在这里插入图片描述

3)代码

       public static  void insertSort(int[] arr){
        if(arr == null || arr.length <2){
            return ;
        }
        for (int i = 1; i<arr.length ; i++) {
            for (int j = i-1; j>=0 && arr[j] > arr[j+1]; j--) {
                //j>= 0 是边界,不要越界
                swap(arr,j,j+1);
            }
        }
    }

4、Merge Sort 归并排序

1)原理

1、数组首先找中点进行二分,二分后再二分,直到不能二分只剩一个数
2、从上面的那一个数开始向上归并(即和其他的数按照大小放置在一起)

2)手写思路

1、二分和归并过程

2、归并原理 例如:图中L和R归并

3)代码

public static void mergeSort(int[] arr){
        if(arr == null || arr.length<2){
            // 如果空数组或只有1个数根本不用排序
            return;
        }
        sortProcess(arr,0,arr.length-1);
    }

    public static void sortProcess(int[] arr,int L,int R){
        if(L == R){ // 说明只剩下1个数了,不需要再分
            return ;
        }
        //分成两半
        int mid = L + ((R-L) >> 2); //L和R 中点位置,(L+R)/2
        //左边排序
        sortProcess(arr,L,mid); //T(N/2)
        //右边排序
        sortProcess(arr,mid+1,R);//T(N/2)
        //
        merge(arr,L,mid, R); //O(N)
        //T(N) = 2 T(N/2) + O(N) = NlogN
    }

    //对指定长度,L到R之间进行排序
    public static void merge(int[] arr, int L ,int mid, int R){
        int[] help = new int[R-L + 1];  //辅助数组存放 共有R-L个数
        int i = 0 ;
        int p1 = L;  // 指针指向L
        int p2 = mid +1; // 指针指向mid+1

        while(p1<=mid && p2<= R){
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
            //此过程 若<,则help[i]=arr[p1], 然后 p1后移,i后移;
            //      若>,则help[i]=arr[p2], 然后 p2后移,i后移;
        }

        // 上述循环跳出时 说明p1 和p2 中必且只有一个已经越界(遍历完),
        // 下面2个while只会执行其中1个

        // 当p2越界,说明p2执行完,直接将p1剩下的放在后面
        while(p1<=mid){
            help[i++] = arr[p1++];
        }
        // 当p1越界,说明p1执行完,直接将p2剩下的放在后面
        while(p2<=R){
            help[i++] = arr[p2++];
        }
        for (int j = 0; j < help.length; j++) {
            arr[L+j] = help[j]; //将排序好的数组覆盖到原来的无序数组
        }

补:大雪菜
思路:
1)确定分界点:中间点 mid=(L+R)/2
2)递归排序左右两半
3)归并:合二为一
import java.util.*;
import java.io.*;

public class Main{
    
    public static void main(String[] args) throws IOException{
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(in.readLine());
        int[] arr = new int[n];
        String[] strs = in.readLine().split(" ");
        for(int i =0;i<n;i++){
            arr[i] = Integer.parseInt(strs[i]);
        }
        
        merge_sort(arr,0,n-1);
        
        for(int i=0;i<n;i++){System.out.print(arr[i]+" ");}
    }
    
    public static void merge_sort(int[] arr, int L,int R){
        if(L>=R) return;
        
        int mid =L + ((R- L) >> 1);
        merge_sort(arr,L,mid);
        merge_sort(arr,mid+1,R);
        
        int k=0;
        int i = L;
        int j = mid+1;
        int[] help = new int[R-L+1];
        while(i<=mid && j<=R){
            if(arr[i]<=arr[j]) help[k++] = arr[i++];
            else help[k++] = arr[j++];
        }
        while(i<=mid){ help[k++] = arr[i++];}//右半已经循环完
        while(j<=R){ help[k++] = arr[j++]; }//左半已经循环完
        
        for (i = L, j = 0; i <= R; i++, j++)
            arr[i] = help[j];//j=将排序好的数组覆盖到原来的无序数组
    }
}

5、QuickSort 快速排序(1荷兰国旗)

1)原理

1、给定长度的序列进行操作,从L到R
2、给定数字num,比num小的放在num左边,等于放在中间,大的放在右边

2)手写思路

3)代码

 public static  int[] netherLandsFlag(int[] arr,int L,int R,int num){
        //对给定长度的序列进行操作,从L到R,给定数字num,比num小的放在num左边,等于num放中间,大的放在右边
        int less = L-1; //小于num区域指向L-1,每多一个则less右移
        int more = R+1; // 大于num区域指向R+1,每多一个则more左移
        int cur = L; // 当前指针
        while(cur < more){ //如果当前指针和more相撞 表示完成
            if(arr[cur] < num){ //小于num 放在小于区域(当前数和less指向的数做交换,从less+1开始
                swap(arr,++less,cur++); // less先+1,然后cur和其交换,然后cur右移,继续判断下一个
                                         // 因为小于区域和中间紧挨着(cur是从L开始),所以交换过来的数要不=num,要不是其本身
                                         // 故交换来的数大小已知,无需判断,所以cur直接右移
            }else if(arr[cur] > num){ //大于num,放在大于区域即数组最右边
                                      // more先-1,然后和cur交换,把交换过来的数判断是否大于小于等于num
                                      // 因为大区域和中间属于待定区,这些数的大小未知,所以需要判断,即cur不移动
                swap(arr,--more,cur);
            }else{//如果遇到=num,则不用动,继续判断下一个
                cur++;
            }
        }
        return new int[] {less+1,more-1}; //返回等于num数集的起始位置,即知道小于区域、等于区域、大于区域的位置
    }

6、QuickSort 快速排序(2)

1)原理

1、借鉴上面荷兰国旗的思想,只是把荷兰国旗当中给定的数num变为数组中的数,在快排中一般num设定为当前序列的最后一个数
2、比荷兰国旗多一步:每一轮放置好后,应该再把选定的数即num放在该放的位置(按照顺序放在数组中)

2)手写思路

3)代码

public static void quickSort(int[] arr){
        if(arr == null || arr.length<2){
            return;
        }
        quickSort(arr,0,arr.length-1);
    }

    public static void quickSort(int[] arr,int L ,int R){
        if(L<R){
            //swap(arr,L+(int)(Math.random()*(R-L+1)),R);
            // 加上它即为随机快排:从L到R上任选一个数和R位置的数交换,
            // 也就是说R位置上的数是随机的。但后续过程还是和R位置上的数进行比较,也就是说随机选了个数作划分
            int[] p = partition(arr,L,R);//P[0]:等于区域第一个,p[1]:等于区域最后一个
            quickSort(arr,L,p[0]-1);//小于区域排序
            quickSort(arr,p[1]+1,R);//大于区域排序
        }
    }
    public static int[] partition(int[] arr,int L, int R){
        int less = L-1;
        int more = R;//此时是从最后一个数开始,不是R-1
        int cur = L;
        while(cur < more){
            if(arr[cur]<arr[R]){
                swap(arr,++less,cur++);
            }else if(arr[cur]> arr[R]){
                swap(arr,--more,cur);
            }else{
                cur++;
            }
        }
        swap(arr,more,R); //此轮划分完大中小区域后,将选定的数(最后一个)和大于区域第一个数作交换
        // 因为每次和选定的数(最后一个)作比较,大于它的放在大于区域,那么划分完后的大于区域第一个数必定大于选定的数
        // 把他俩交换,即把一开始选定的数放到正确的位置(等于区域最后一个)
        //printArray(arr);
        return new int[]{less+1,more};//返回等于区域的开始位置less+1,以及结束位置more,此时的more即为交换来的R



    }

补充—高效简洁版本(分治):

思路:双指针

i、j 轮流向中间走去。i从左到右走,当遇到>=num的数停下来;j开始向内走,当遇到<=num的数,停下来;交换i和j指向的数;循环执行…
在这里插入图片描述

过程:

1、随意找其中一个数num作为分界点:一般为数组左、中、右,即arr[L]、arr[(L+R)/2]、arr[R]
2、重新划分区间:将数组中<=num的放在num左边,>=num的放在num右边
3、递归处理左右两端

代码:

import java.io.*;

public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(input.readLine());
        int[] arr = new int[n];
        
        String[] strs = input.readLine().split(" ");
        //input.readLine()把一行的数据当成一个字符串,然后.split(" ")把字符串按照空格分成小的字符串数组
        for(int i=0;i<n;i++){arr[i]=Integer.parseInt(strs[i]);}
        
        quickSort(arr,0,n-1);
        
        for(int i=0;i<n;i++){System.out.print(arr[i]+ " ");}
    }
    

    public static void quickSort(int[] arr, int L, int R){
        if(L>=R) return;//边界,只有一个数或没有数
        
        int compare = arr[L];//假设指定的数,可以是arr[R]\arr[L+R>>1]\任意
        int i=L-1;//i前面的数都比compare要小,因为后面先执行i++,所以开始位于边界外
        int j=R+1;//j后面的数都比compare要大
        
        while(i<j){
        //这个i<j每次循环只判断一次,而不是说,里面的循环体执行着执行着遇到i>=j就不执行了,而是等他执行完下一次不进行循环了
            while(arr[++i]<compare);//先执行i++,再比较;i遇到>compare的才停下来
            while(arr[--j]>compare);//先执行j--,再比较;j遇到<compare的才停下来
            if(i<j){//如果两指针还没有相遇,则交换两指针的数
                int tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
        }
        quickSort(arr,L,j);//左半边排序
        quickSort(arr,j+1,R);//右半边排序
    }
}

7、HeapSort 堆排序(待更新)

1)原理

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序

数组和二叉树的关系

在这里插入图片描述

2)代码

 public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        //让整个数组变为大根堆
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
            // 依次扩大堆的大小,从0扩到len,即0~i 之间形成大根堆
        }
        //上述排成大根堆后 说明堆顶的位置是最大值,即数组最大元素位于索引=0处(第一个)

        int size = arr.length;//此时堆的大小即为数组的大小
        swap(arr, 0, --size);//把堆顶(数组最大值)放到堆的最后位置,然后堆的大小-1
        while (size > 0) {
            heapify(arr, 0, size);//把上面缩小的堆重新调成大根堆
            swap(arr, 0, --size);//把此时的堆顶放在当前堆的最后位置,然后堆大小-1
        }
    }

    //让数组变为大根堆————往上比
    public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index - 1) / 2]) {//大于父节点值
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    //发生改变的数组重新调为大根堆————往下沉
    public static void heapify(int[] arr, int index, int size) {
        int left = index * 2 + 1;//左孩子
        while (left < size) {
            int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //left+1相当于右孩子,右孩子不出界的情况下,找到左孩子和右孩子较大的那一个,返回其索引
            largest = arr[largest] > arr[index] ? largest : index;
            //上面找到的较大的左/右孩子,将其和它的父节点比较大小
            if (largest == index) {
                break;//父节点值是最大的,即大于左右节点
            }
            swap(arr, largest, index);
            //否则交换父节点和更大的那个子节点
            index = largest;
            left = index * 2 + 1;
        }
    }

8、桶排序(例:MaxGap)(待更新)

1)原理

2)代码

//找到每个非空和前一个非空桶之间的最大差值
    public static int maxGap(int[] arr){
        if(arr == null || arr.length<2){
            return 0;
        }
        int len = arr.length;
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;

        for (int i = 0; i < len; i++) {
            min = Math.min(min,arr[i]);
            max = Math.max(max,arr[i]);
        }
        if(min == max){//说明数组里的数都是同一个数
            return 0; }
        //桶里只记录 1、最小值 2、最大值 3、当前桶里是否有数
        boolean[] hasNum = new boolean[len+1];
        int[] mins = new int[len+1];
        int[] maxs = new int[len+1];
        int bid = 0;//桶的编号,从0开始

        //把数组的数依次放入对应的桶里
        for (int i = 0; i < len; i++) {
            bid = bucket(arr[i],len,min,max);
            //更新桶的最小值与最大值
            mins[bid] = hasNum[bid] ? Math.min(mins[bid],arr[i]) : arr[i];
            maxs[bid] = hasNum[bid] ? Math.max(maxs[bid],arr[i]) : arr[i];
            hasNum[bid] = true;//此时,桶内进数了 状态变为true
        }

        int res = 0; //记录最大差值
        int lastMax = maxs[0]; // 初始化前桶的最大值
        int i = 1;
        for ( ;  i<= len ; i++) { // 从i=1 开始的桶计算与前筒的差值
            if(hasNum[i]){//如果是空的直接跳过
                res = Math.max(res,mins[i] - lastMax);
                //后非空桶的最小值减去前非空桶最大值(数组相邻两数的差值),赋给res
                lastMax = maxs[i];//桶向后移动
            }
        }
        return res;
    }

    // 数num应该入几号桶
    public static int bucket(long num, long len ,long min, long max){
        // 防止(num-min)*len 溢出  转为long型
        return (int)((num-min)*len/(max-min));
    }

表格总结复杂度

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值