带你整理面试过程中常考的九大排序算法

Posted 南淮北安

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你整理面试过程中常考的九大排序算法相关的知识,希望对你有一定的参考价值。

文章目录

一、二分插入排序

首先必须是排好序的数组,然后通过二分查找,找到合适的位置,插入

1. 原理

二分查找算法又叫作折半查找,要求待查找的序列有序,每次查找都取中间位置的值与待查关键字进行比较,如果中间位置的值比待查关键字大,则在序列的左半部分继续执行该查找过程,如果中间位置的值比待查关键字小,则在序列的右半部分继续执行该查找过程,直到查找到关键字为止,否则在序列中没有待查关键字

如图5-1所示,在有序数组[3,4,6,20,40,45,51,62,70,99,110]中查找key=20的数据,根据二分查找算法,只需查找两次便能命中数据。这里需要强调的是,二分查找算法要求要查找的集合是有序的,如果不是有序的集合,则先要通过排序算法排序后再进行查找。

2. 代码

public static int binarySearch(int[] arr, int key) 
        int left = 0;
        int right = arr.length - 1;
        int mid;
        while (left < right) 
            mid = left + (right - left)/2;
            if (arr[mid] == key) 
                return mid;
             else if (arr[mid] > key) 
                //如果中间位元素大于我们要找的关键字,则往左半区间查找
                right = mid - 1;
             else 
                //如果中间位元素小于我们要找的关键字,则往右半区间查找
                left = mid + 1;
            
        
        return -1;
    

参考:二分查找

while(low<mid);
mid = low + (mid-low)/2

二、冒泡排序

1. 原理

冒泡排序(Bubble Sort)算法是一种较简单的排序算法,它在重复访问要排序的元素列时,会依次比较相邻的两个元素,如果左边的元素大于右边的元素,就将二者交换位置,如此重复,直到没有相邻的元素需要交换位置,这时该列表的元素排序完成

该算法名称的由来是越大的元素会经过交换慢慢“浮”到数列的顶端(升序或降序排列),就如同水的气泡最终会上浮到顶端一样。

如图5-2所示为对数组[4,5,6,3,2,1]进行冒泡排序,每次都将当前数据和下一个数据进行比较,如果当前数据比下一个数据大,就将二者交换位置,否则不做任何处理。这样经过第1趟排序就会找出最大值6并将其放置在最后一位,经过第2趟排序就会找出次大的数据5放在倒数第二位,如此重复,直到所有数据都排序完成。

2. 代码

public static int[] bubbleSort(int[] arr) 
        int length = arr.length;
        int temp;
        //外层循环控制排序趟数
        for (int i = 0; i < length - 1; i++) 
            //内层循环控制每一趟排序多少次
            for (int j = 0; j < length - i - 1; j++) 
                if (arr[j] > arr[j + 1]) 
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                
            
        
        return arr;
    

外层循环和内层循环,外层循环控制排序的次数,内层循环控制每一趟排序多少次。在内层循环中比较当前数据和下一个数据的大小,如果当前数据大于下一个数据,就交换二者的位置,这样重复进行判断,直至整个排序完成,最终返回排序后的数组。

三、插入排序算法

1. 原理

插入排序(Insertion Sort)算法是一种简单、直观且稳定的排序算法。如果要在一个已排好序的数据序列中插入一个数据,但要求此数据序列在插入数据后仍然有序,就要用到插入排序法。

插入排序的基本思路是将一个数据插入已经排好序的序列中,从而得到一个新的有序数据,该算法适用于少量数据的排序,是稳定的排序方法。

插入排序算法的原理如图5-3所示,类似于扑克牌游戏的抓牌和整理过程。在开始摸牌时,左手是空的。接着,每次从桌上摸起一张牌时,都根据牌的大小在左手扑克牌序列中从右向左依次比较,在找到第一个比该扑克牌大的位置时就将该扑克牌插入该位置的左侧,这样依次类推,无论什么时候,左手中的牌都是排好序的。


如图5-4所示为插入排序算法的工作流程。输入原始数组[ 6, 2, 5, 8, 7 ],在排序时将该数组分成两个子集:一个是有序的L(left)子集,一个是无序的R(right)子集。初始时设L=[ 6 ], R = [ 2, 5, 8, 7 ]。在L里面只有一个元素6,本身就是有序的。接着我们每次都从R中拿出一个元素插入L中从右到左比自己大的元素后面,然后将L中比自己大的所有元素整体后移,这样就保证了L子集仍然是有序的。重复以上插入操作,直到R子集的数据为空,这时整个数组排序完成,排序的结果被保存在L子集中

2. 代码

 public static int[] insertSort(int[] arr) 
        for (int i = 1; i < arr.length; i++) 
            //插入的数
            int insertVal = arr[i];
            //被插入的位置,准备和前一个数进行比较
            int index = i - 1;
            //如果插入的数比被插入的数小
            while (index >= 0 && insertVal < arr[index]) 
                arr[index + 1] = arr[index];
                index--;
            
            //将插入的数放入合适的位置
            arr[index + 1] = insertVal;
        
        return arr;
    

以上代码定义了insertSort()用于插入排序,其中,insertVal用于从数组中取出待插入的数据,index是待插入的位置。在insertSort()中通过while循环从数组中找到比待插入数据大的数据的索引位置index,然后将该index位置后的元素向后移动,接着将待插入的数据插入index+1的位置,如此重复,直到整个数组排序完成。

四、快速排序算法

1. 原理

快速排序(Quick Sort)是对冒泡排序的一种改进,通过一趟排序将要排序的数据序列分成独立的两部分,其中一部分的所有数据比另一部分的所有数据都要小,然后按此方法对两部分数据分别进行快速排序,整个排序过程递归进行,最终使整个数据序列变成有序的数据序列。

快速排序算法的原理是:选择一个关键值作为基准值(一般选择第1个元素为基准元素),将比基准值大的都放在右边的序列中,将比基准值小的都放在左边的序列中。具体的循环过程如下。

它的过程就是,先从后往前,找到一个比它小的值与它位置交换,然后从前往后,找到一个位置比它大的,位置交换,这样一趟下来,整个序列左边都是比该基准值小的,右边都是比该基准值大的,重复这个过程分别比较它的左右两边的序列,直到整个数据序列有序。

如图5-5所示是对数组[6,9,5,7,8]进行快速排序。先以第1个元素6为基准值,从数组的最后一位从后向前比较(比较顺序为:8>6、7>6、5<6),找到第1个比6小的数据5,然后进行第1次位置交换,即将数据6(索引为0)和数据5(索引为2)交换位置,之后基准值6位于索引2处;接着从前向后比较(比较顺序为:5<6、9>6),找到第1个比6大的数据9,然后进行第2次位置交换,即将数据6(索引为2)和数据9(索引为1)交换位置,交换后6位于索引1处;这时高位和低位都在6处,第一次递归完成。在第一次递归完成后,基准值6前面的数据都比6小,基准值6后面的数据都比6大。重复执行上述过程,直到整个数组有序。

2. 代码

class Sort 
    public static int[] quickSort(int[] arr, int low, int high) 
        //从前往后的索引
        int start = low;
        //从后往前的索引
        int end = high;
        //基准值
        int key = arr[low];
        while (end > start) 
            //从后往前比较
            while (end > start && arr[end] >= key) 
                end--;
            
            //如果没有比基准值小的,则比较下一个,直到有比基准值小的,则交换位置,然后从前往后比较
            if (arr[end] <= key) 
                int temp = arr[end];
                arr[end] = arr[start];
                arr[start] = temp;
            
            //从前往后比较
            while (end > start && arr[start] <= key) 
                start++;
            
            //直到有比基准值大的,则交换位置
            if (arr[start] >= key) 
                int temp = arr[start];
                arr[start] = arr[end];
                arr[end] = temp;
            
        
        //递归左边序列
        if (start > low) 
            quickSort(arr, low, start - 1);
        
        //递归右边索引
        if (end < high) 
            quickSort(arr, end + 1, high);
        
        return arr;
    

五、希尔排序

1. 原理

希尔排序(Shell Sort)算法是插入排序算法的一种,又叫作缩小增量排序(Diminishing Increment Sort)算法,是插入排序算法的一种更高效的改进版本,也是非稳定排序算法。希尔排序算法将数据序列按下标的一定增量进行分组,对每组使用插入排序算法排序,随着增量逐渐减少,每组包含的关键词越来越多,在增量减至1时,整个文件被分为一组,算法终止。

希尔排序算法的原理是先将整个待排序的记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录基本有序时,再对全部记录依次进行直接插入排序

希尔排序算法的具体做法为:假设待排序元素序列有N个元素,则先取一个小于N的整数增量值increment作为间隔,将全部元素分为increment个子序列,将所有距离为increment的元素都放在同一个子序列中,在每一个子序列中分别实行直接插入排序;然后缩小间隔increment,重复上述子序列的划分和排序工作,直到最后取increment=1,将所有元素都放在同一个子序列中时排序终止。

由于开始时increment的取值较大,每个子序列中的元素较少,所以排序速度较快;到了排序后期,increment的取值逐渐变小,子序列中的元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。

例如,对数组[21,25,49,26,16,8]的排序过程如下。
(1)第1趟排序。第1趟排序的间隔为“increment=N/3+1=3”,它将整个数据列划分为间隔为3的3个子序列,然后对每个子序列都执行直接插入排序,相当于对整个序列都执行了部分排序,如图5-6所示。

(2)第2趟排序。第2趟排序的间隔为“increment= increment/3+1=2”,将整个元素序列划分为两个间隔为2的子序列分别进行排序,如图5-7所示。

(3)第3趟排序。第3趟排序的间隔为“increment= increment/3+1=1”,在增量为1时,说明整个数组已经完成排序。

2. 代码

public class ShellSort 
    public static void main(String[] args) 
        int[] arr = 5, 1, 7, 3, 1, 6, 9, 4;
        shellSort(arr);
        for (int i : arr) 
            System.out.print(i + "\\t");
        
    

    private static void shellSort(int[] arr) 
        //step:步长
        for (int step = arr.length / 2; step > 0; step /= 2) 
            //对一个步长区间进行比较 [step,arr.length)
            for (int i = step; i < arr.length; i++) 
                int value = arr[i];
                int j;
                //对步长区间中具体的元素进行比较
                for (j = i - step; j >= 0 && arr[j] > value; j -= step) 
                    //j为左区间的取值,j+step为右区间与左区间的对应值。
                    arr[j + step] = arr[j];
                
                //此时step为一个负数,[j + step]为左区间上的初始交换值
                arr[j + step] = value;
            
        
    

六、归并排序

1. 原理

归并排序算法是基于归并(Merge)操作的一种有效排序算法,是采用分治法(Divide and Conquer)的典型应用。

归并排序算法将待排序序列分为若干个子序列,先对每个子序列进行排序,等每个子序列都有序后,再将有序子序列合并为整体的有序序列。若将两个有序表合并成一个有序表,则称之为二路归并

归并排序的原理是先将原始数组分解为多个子序列,然后对每个子序列进行排序,最后将排好序的子序列合并起来。如图5-8所示为对数组[4,1,3,9,6,8]进行归并排序,先经过两次分解,将数组分解成4个子序列,然后对子序列数组进行排序和归并,最终得到排好序的数组[1,3,4,6,8,9]。

2. 代码

class MSort 
    public static void mergeSort(int[] arr, int low, int high) 
        int mid = low + (high - low) / 2;
        if (high > low) 
            //左边归并排序
            mergeSort(arr, low, mid);
            //右边归并排序
            mergeSort(arr, mid, high);
            //左右归并
            merge(arr, low, mid, high);
        
    

    public static void merge(int[] arr, int low, int mid, int high) 
        //临时数组
        int[] tempArr = new int[arr.length];
        //左指针
        int i = low;
        //右指针
        int j = mid + 1;
        //记录临时数组下标
        int k = 0;
        //把较小的数字移动到临时数组中
        while (i <= mid && j <= high) 
            if (arr[i] < arr[j]) 
                tempArr[k++] = arr[i++];
             else 
                tempArr[k++] = arr[j++];
            
        
        //把左边剩余的数移入临时数组
        while (i <= mid) 
            tempArr[k++] = arr[i++];
        
        //把右边剩余的数移入临时数组
        while (j <= high) 
            tempArr[k++] = arr[j++];
        
        //把新数组中的数覆盖arr数组
        for (int l = 0; l < arr.length; l++) 
            arr[low + l] = tempArr[l];
        
    

七、桶排序

桶排序(Bucket Sort)算法也叫作箱排序算法,它将数组分到有限数量的桶中,对每个桶再进行排序(有可能使用其他排序算法或以递归方式继续使用桶排序进行排序),最后将各个桶合并。

桶排序算法的原理是先找出数组中的最大值和最小值,并根据最大值和最小值定义桶,然后将数据按照大小放入桶中,最后对每个桶进行排序,在每个桶的内部完成排序后,就得到了完整的排序数组

int bucketCount = (int)Math.floor((maxValue-minValue)/bucketSize)+1;

如图5-9所示为对数组[3,6,5,9,7,8]进行桶排序,首先根据数据的长度和min、max创建三个桶,分别为0~3、4~7、8~10;然后将数组的数据按照相应的大小放入桶中;接着将桶内部的数据分别进行排序;最后将各个桶进行合并,便得到了完整排序后的数组。

八、基数排序

基数排序(Radix Sort)算法是桶排序算法的扩展,它将数据按位切割为不同的数字,位数不够的补0,然后在每个位数上分别进行比较,最终得到排好序的序列

基数排序算法的原理是将所有待比较数据统一为同一长度,在位数不够时前面补零,然后从低位到高位根据每个位上整数的大小依次对数据进行排序,最终得到一个有序序列

九、堆排序

1. 原理

堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点

什么是堆?

堆是一个树形结构,其实堆的底层是一棵完全二叉树。而完全二叉树是一层一层按照进入的顺序排成的。按照这个特性,我们可以用数组来按照完全二叉树实现堆。

大顶堆:

根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大顶堆。大顶堆要求根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值

小顶堆:

根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者,称为小顶堆。小堆堆要求根节点的关键字既小于或等于左子树的关键字值,又小于或等于右子树的关键字值。

(1)构建初始堆,将待排序列构成一个大顶堆(或者小顶堆),升序大顶堆,降序小顶堆;
(2)将堆顶元素与堆尾元素交换,并断开(从待排序列中移除)堆尾元素。
(3)重新构建堆。
(4)重复2~3,直到待排序列中只剩下一个元素(堆顶元素)。

时间复杂度:堆排序方法对记录数n较大的文件排序是很好的,但当n较小时,不提倡使用,因为初始建堆和调整建新堆时要进行反复的筛选。堆排序时间复杂度为O(nlog2n)。

2. 代码

class HSort 
    public static void heapSort(int[] arr) 
        //构建大顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) 
            //从第一个非叶子节点,从下到上,从左到右调整结构
            adjustHeap(arr, i, arr.length);
        
        //交换堆顶元素与末尾元素,同时调整堆结构
        for (int j = arr.length - 1; j > 0; j--) 
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        
    

    //调整堆
    public static void adjustHeap(int[] arr, int i, int length) 
        //先取出当前元素 i
        int temp = arr[i];
        //从i节点的左子节点开始,也就是2*i+1,开始调整
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) 
            //如果左子节点小于右子节点,则k指向右子节点
            if (k + 1 < length && arr[k] < arr[k + 1]) 
                k++;
            
            //如果子节点大于父节点,将子节点值赋值给父节点
            if (arr[k] > temp) 
                arr[i] = arr[k];
                i = k;
             else 
                break;
            
        
        //将temp放到最终的位置
        arr[i] = temp;
    

    //交换元素
    public static void swap(int[] arr, int a, int b) 
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    

具体可参考:图解堆排序

十、总结

1. 算法分类

(1)插入排序:直接插入排序,希尔排序
(2)交换排序: 冒泡排序,快速排序
(3)选择排序:直接选择排序,堆排序
(4)归并排序:归并排序
(5)基数排序:基数排序

2. 性能分析

(1) O ( n 2 ) O(n^2) O(n2):直接插入排序,选择排序,冒泡排序

在数据规模较小时(9W内),直接插入排序,选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。性能为 O ( n 2 ) O(n^2) O(n2)的算法基本上是相邻元素进行比较,基本上都是稳定的。

(2) O ( n l o g n ) O(nlogn) O(nlogn):快速排序,归并排序,希尔排序,堆排序

其中,快排是最好的,其次是归并和希尔,堆排序在数据量很大时效果明显(堆排序适合处理大数据)。<

以上是关于带你整理面试过程中常考的九大排序算法的主要内容,如果未能解决你的问题,请参考以下文章

手撕九大排序算法——面试必备!!!

五种排序算法整理 二(堆排序,快速排序插入排序选择排序冒泡排序)

带你整理面试过程中关于 JVM 的运行内存划分垃圾回收算法和 4种引用类型的相关知识点

带你整理面试过程中关于 JVM 中分代收集算法分区收集算法和垃圾收集器的相关知识

Java面试题中常考的容易混淆的知识点区别

带你整理面试过程中关于一致性Hash算法的相关知识点