看了这些排序算法,再也不用担心我的面试了

 

常用的排序算法

1、排序算法的概念及分类

在学习排序算法前,我们先了解什么是排序,排序有哪些分类

1.1、排序的概念

先看下百度百科的定义

排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整“有序”的记录序列。分内部排序外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程。

说了那么多,总结下这段话

  • 什么是排序?

排序是将一组数据按照指定的顺序进行排列的过程

  • 排序的种类?

内排序:将处理的所有的数据加载到内存中进行排序

外排序:不能全部加载到内存,需要借助于外部存储器进行排序

NOTE:因为外部排序用的并不多,我们主要学习内部排序中的一些排序算法

1.2 内排序中的具体的排序算法

学习内部排序算法时,我们会分析排序算法的步骤,然后实现每一种排序算法的核心代码,最后将会对排序算法做个总结,对排序算法的时间复杂度、空间复杂度进行比较。

2、排序算法的实现

2.1交换排序

交换排序有种排序算法,分别为冒泡排序快速排序

2.1.1冒泡排序

  • 冒泡排序概念

冒泡排序是一种交换排序,它的基本思想是两两比较相邻元素的关键字,如果是反序则交换,直到没有反序的记录为止。

我们先使用最笨的方法来代码实现冒泡排序

  • 普通的冒泡排序

/*
*假设序列中有N个元素,则需要进行N-1轮,每轮N-1次
*/
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]) {
                    //swap函数是自己编写的,用于交换元素
                    swap(arr, j, j + 1);
                }
            }
        }

我们发现编写这样的代码会存在这样一种情况,当没有数据交换时,此时序列已是有序序列,我们就不需要在判断后面的数据了,即当序列已经有序时,不需要再对后面的数据进行判断。

  • 优化的冒泡排序

//定义一个标志变量,用于退出
boolean flag = true;
        for (int i = 0; i < arr.length - 1&&flag; i++) {
            flag=false;
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //比较元素大小并重置标志变量
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    flag = true;
                }
            }
        }

2.1.2快速排序

  • 概念

快速排序是对冒泡排序的一种改进,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有的数据比另一部分的所有数据都要小,然后按此方法对这个两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序的递归实现

public static void quickSort(int[] arr,int left,int right){
        int l=left;
        int r=right;
        int temp=0;
    //以中间数值作为基准数
        int privot=arr[(right+left)/2];
       while (l<r){
           while (arr[l]<privot){
                l++;
           }
           while (arr[r]>privot){
               r--;
           }
           if(l>=r){
               break;
           }
           //左部分的数值大于有右部分,进行交换
           temp=arr[l];
           arr[l]=arr[r];
           arr[r]=temp;

           if(arr[l]==privot){
               r--;
           }
           if(arr[r]==privot){
               l++;
           }
       }
       if(l==r){
           l+=1;
           r-=1;
       }
       //左递归
        if (left<r){
            quickSort(arr,left,r);

        }
        //右递归
        if(right>l){
            quickSort(arr,l,right);
        }
    }

2.2选择排序

选择排序有两种算法,分别为简单选择排序堆排序

2.2.1简单选择排序

  • 概念

该方法是设所排序序列的记录个数为n。i取1,2,…,n-1,从所有n-i+1个记录(Ri,Ri+1,…,Rn)中找出排序码最小的记录,与第i个记录交换。执行n-1趟 后就完成了记录序列的排序。

即通过n-i次关键字间的比较,从n-i+1(包括自己)个记录中选出关键字最小的记录,并和第i个记录交换。

代码实现

for (int i = 0; i < arr.length - 1; i++) {
            int minSize = i;
            int min = arr[minSize];
            for (int j = i + 1; j <= arr.length - 1; j++) {
                if(min>arr[j]){
                    minSize=j;
                    min=arr[j];
                }
            }
    //只有当下标不一致才发生交换
            if(i!=minSize){
                arr[minSize]=arr[i];
                arr[i]=min;
            }
        }

2.2.1堆排序

学习堆排序需要有二叉树的知识

集合算法基础--二叉树(上)

集合算法基础--二叉树(下)

堆排序描述:

堆排序是利用堆这种数据结构而设计的一种排序算法。它的最好、最坏和平均时间复杂度都是O(nlogn)

堆是这样一种数据结构

  • 大顶堆:每个结点的值大于等于其左右孩子的结点的值

  • 小顶堆:每个结点的值小于等于其左右孩子结点的值

数组和二叉树可以相互转换,我们可以利用数组的存储结构,二叉树的逻辑结构进行排序

排序步骤:

  • 将无序序列构建成一个堆,根据升序或者降序构造大顶堆或者小顶堆(以大顶堆为例)

  • 将堆顶元素与末尾元素交换,将最大的元素放置在数组的尾端

  • 重新调整以满足堆结构,继续交换堆顶元素与数组末尾元素,直到整个序列有序

图解:

1、构造大顶堆

2、交换堆顶和末尾的元素

3、将数组长度减1,继续构造新的大顶堆。

代码实现

public class HeapSort {
    public static void main(String[] args){
        int[] arr={4,6,8,5,9,-44,11,98,3};

        //将无序序列构建成一个堆,根据升序或者降序的需求构造大顶堆或者小顶堆
        //arr.length-1/2是为了找到最后一个非叶子结点
        for (int i=arr.length/2-1;i>=0;i--){
            adjustHeap(arr,i,arr.length);
        }
        //将堆顶元素与末尾元素交换,将最大的元素放置在数组的尾端
        //重新调整以满足堆的结构,继续交换堆顶元素与数组末尾元素,直到整个序列有序
        for(int j=arr.length-1;j>0;j--){
            int temp;
            //交换
            temp=arr[j];
            arr[j]=arr[0];
            arr[0]=temp;
            adjustHeap(arr,0,j);
        }
        System.out.println(Arrays.toString(arr));
    }

    /**
     * @param arr 数组(二叉树)
     * @param i 传进的非叶子结点坐标
     * @param length 数组的长度
     */
    public static void adjustHeap(int[] arr,int i,int length){
        int temp=arr[i];
        for(int k=i*2+1;k<length;k=k*2+1){
            //找到最大的子结点
            if(k+1<length&&arr[k]<arr[k+1]){
                k++;
            }
            if(temp<arr[k]){
                arr[i]=arr[k];
                i=k;//将i等于非叶子结点的左子结点坐标
            }
            //将非叶子结点的值赋给与它交换的子结点
            arr[i]=temp;
        }
    }
}

2.3插入排序

插入排序有两种,分别为直接插入排序希尔排序

2.3.1直接插入排序

  • 概念

把n个待排序的元素看成一个有序列表和一个无序表,开始时有序表只包含一个元素,无序表中包含n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

代码实现

int insertScrpt=0;//要插入的位置
        int insertValue=0;//插入的值
        for(int i=1;i<=arr.length-1;i++){
            insertScrpt=i-1;
            insertValue=arr[i];
            while (insertScrpt>=0&&insertValue<arr[insertScrpt]){
                arr[insertScrpt+1]=arr[insertScrpt];//向后移
                insertScrpt--;
            }
            if(insertScrpt+1!=i){
                arr[insertScrpt+1]=insertValue;
            }
        }

2.3.2希尔排序

  • 概念

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量的逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个序列被分成一组,算法终止。

代码实现

int temp = 0;
        for (int increment = arr.length / 2; increment > 0; increment = increment / 2) {
            for (int i = increment; i < arr.length; i++) {
                for (int j = i - increment; j >= 0; j -= increment) {
                    if (arr[j] > arr[j + increment]) {
                        temp = arr[j];
                        arr[j] = arr[j + increment];
                        arr[j + increment] = temp;
                    }
                }
            }
        }

2.4、归并排序

  • 概念

归并排序是利用归并的思想进行排序的方法,采用经典的分治策略(分治是将问题分解成一些小问题然后递归求解,然后就将各个阶段的答案修补在一起)

网上找的图解,这幅图画的挺详细的,直接就引用了下来。

这种结构很像一个完全二叉树,我们采用递归的方法去实现,也可以采用迭代方法去实现,分阶段可以理解为递归拆分子序列的过程。

  • 序列的拆分

    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            //向左递归分解
            mergeSort(arr, left, mid, temp);
            //向右递归分解
            mergeSort(arr, mid + 1, right, temp);
            //合并
             merge(arr, left, mid, right, temp);
        }
    }
  • 序列的合并

/**
     * 合并
     * @param arr   原始数组,待排序的数组
     * @param left  数组的左边坐标
     * @param mid   数组的中间坐标
     * @param right 数组的右边坐标
     * @param temp  临时数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int temp[]) {
        int i = left;//初始化i,左边序列的初始索引
        int j = mid + 1;//初始化j,右边序列的初始索引
        int t = 0; //临时变量的索引
        //1、将拆开的左右数组中的元素分别比较大小,并放入临时数组,直至某一数组被放完
        while (i <= mid && j <= right) {
            if (arr[i] < arr[j]) {
                temp[t] = arr[i];
                i++;
                t++;
            } else {
                temp[t] = arr[j];
                j++;
                t++;
            }
        }

        //2、将剩下数组的元素全部移动到临时数组中
        while (i <= mid) {
            temp[t] = arr[i];
            i++;
            t++;
        }
        while (j <= right) {
            temp[t] = arr[j];
            j++;
            t++;
        }
        //3 将临时数组的元素合更新到原数组
        t = 0;
        int tempLeft = left;
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t++;
            tempLeft++;
        }
    }

2.5、基数排序

  • 概念

将所有待比较数值统一为同样的数位长度,数位较短的数前面补零,然后,从最低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

  • 算法描述

基数排序就是分离出数字的每一位,根据每一位的大小放入指定的桶中,桶用数组来表示,共分成编号为0-9的桶。如249,分离个位时,249放入编号为9的桶中,分离十位时,246放入编号为4的桶中,分离千位时,249放入编号为0的桶中。存在负数的序列不能使用基数排序。

 public static void radisSort(int[] arr){
        //找出最大数并确定位数
        int max=arr[0];
        for(int i=1;i<arr.length;i++){
            if(max<arr[i]){
                max=arr[i];
            }
        }
        int maxLength=(max+"").length();
        //定义桶,为了防止元素溢出,需要将每个桶的容量都定义为:arr.length
        int[][] bucket=new int[10][arr.length];
        //定义一个数组bucketElementCount[],用来记录每个桶中放入的元素的数量
        int[] bucketElementCount=new int[10];
        /**
         * 将数据元素放入桶中
         */
        for(int l=0,n=1;l<maxLength;n=n*10,l++){
            for(int i=0;i<arr.length;i++){
                int endDigital=arr[i]/n%10;
                bucket[endDigital][bucketElementCount[endDigital]]=arr[i];
                bucketElementCount[endDigital]++;
            }
            /**
             * 取出元素
             */
            int index=0;
            for(int i=0;i<bucket.length;i++){
                if(bucketElementCount[i]!=0){
                    for(int j=0;j<bucketElementCount[i];j++){
                        arr[index]=bucket[i][j];
                        index++;
                    }
                }
            //将bucketElemnetCount清零
                bucketElementCount[i]=0;
            }
            System.out.println(Arrays.toString(arr));
        }

    }

3、排序算法总结

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n2)O(n)O(n2)O(1)稳定
简单选择排序O(n2)O(n2)O(n2)O(1)稳定
直接插入排序O(n2)O(n)O(n2)O(1)稳定
希尔排序O(nlogn)~O(n2)O(n1.3)O(n2)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定

精选文章

集合算法基础--链表
集合算法基础--二叉树(上)
集合算法基础--二叉树(下)

参考文献

[1]程杰.大话数据结构

[2]马克.艾伦.维斯.数据结构与算法分析

[3]https://www.cnblogs.com/chengxiao/p/6129630.html

[4]https://www.cnblogs.com/chengxiao/p/6194356.html

在公众号回复success领取独家整理的学习资源

看了这篇文章,你是否「博学」了

「扫码关注我」,每天博学一点点。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值