java八股文面试题基础篇--二分查找算法冒泡排序选择排序插入排序希尔排序快速排序(代码片段)

CodeJiao CodeJiao     2023-01-22     187

关键词:

1. 二分查找算法

要求

  • 能够用自己语言描述二分查找算法
  • 能够手写二分查找代码
  • 能够解答一些变化后的考法

1.1 二分查找算法介绍

二分查找也是一种在数组中查找数据的算法。它只能查找已经排好序的数据。二分查找通过比较数组中间的数据与目标数据的大小,可以得知目标数据是在数组的左边还是右边。因此,比较一次就可以把查找范围缩小一半。重复执行该操作就可以找到目标数据,或得出目标数据不存在的结论。

现在我们来试试查找6这个元素。

首先找到数组中间的数字,此处为5。

将5和要查找的数字6进行比较。


把不需要的数字移出查找范围。

在剩下的数组中找到中间的数字,此处为7。

比较7和6。
把不需要的数字移出查找范围。

在剩下的数组中找到中间的数字,此处为6。

6=6,成功找到目标数字。


1.2 二分查找说明


1.3 算法描述

  1. 前提:有已排序数组 array(假设已经做好)

  2. 定义左边界 lIndex、右边界 rIndex,确定搜索范围,循环执行二分查找(3、4两步)

  3. 获取中间索引 mIndex = Floor((lIndex+rIndex) /2)

  4. 中间索引的值 array[mIndex] 与待搜索的值 target 进行比较

    ① array[mIndex] == target 表示找到,返回中间索引

    ②array[mIndex] > target,中间值右侧的其它元素都大于 target,无需比较,中间索引左边去找,mIndex - 1 设置为右边界,重新查找

    ③ array[mIndex] < target,中间值左侧的其它元素都小于 target,无需比较,中间索引右边去找, mIndex + 1 设置为左边界,重新查找

  5. 当 lIndex > rIndex 时,表示没有找到,应结束循环


1.4 算法实现

    /**
     * 二分查找算法
     *
     * @param array  待查找的数据
     * @param target 目标元素
     * @return target在array中的索引值,如果没有匹配的元素则返-1。
     */
    public static int binarySearch(int[] array, int target) 
        int lIndex = 0, rIndex = array.length - 1, mIndex;
        while (lIndex <= rIndex) 
            /*
             * 无符号右移动1位,相当于 /2。
             * 这样可以防止lIndex + rIndex溢出整数最大值。
             * 因为移位运算符是接近于计算机硬件的,所以相比于 / 效率也会有所提高。
             */
            mIndex = (lIndex + rIndex) >>> 1;
            if (target == array[mIndex]) 
                return mIndex;
             else if (target < array[mIndex]) 
                rIndex = mIndex - 1;
             else 
                lIndex = mIndex + 1;
            
        
        // 如果没有匹配的元素则返-1
        return -1;
    

测试代码:

    public static void main(String[] args) 
        int[] array = 1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50;
        int target = 8;
        int idx = binarySearch(array, target);
        System.out.println(idx);
    

运行结果:


1.5 相关面试题


1.6 二分查找小结


2. 冒泡排序(稳定)

要求

  • 能够用自己语言描述冒泡排序算法
  • 能够手写冒泡排序代码
  • 了解一些冒泡排序的优化手段

2.1 冒泡排序介绍


在序列的最右边放置一个天平,比较天平两边的数字。如果右边的数字较小,就交换这两个数字的位置。

由于6<7,所以交换这两个数字。
完成后,天平往左移动一个位置,比较两个数字的大小。此处4<6,所以无须交换。

继续将天平往左移动一个位置并比较数字。重复同样的操作直到天平到达序列最左边为止。

不断对数字进行交换,天平最终到达了最左边。通过这一系列操作,序列中最小的数字就会移动到最左边。

最左边的数字已经归位。

将天平移回最右边,然后重复之前的操作,直到天平到达左边第2个位置为止。

当天平到达左边第2个位置时,序列中第2小的数字也就到达了指定位置。

将天平再次移回最右边,重复同样的操作直到所有数字都归位为止。

排序中……
排序中……

排序完成。


2.2 冒泡排序说明


2.3 算法描述

  1. 依次比较数组中相邻两个元素大小,若 array[j] > array[j+1],则交换两个元素,两两都比较一遍称为一轮冒泡,结果是让最大的元素排至最后。
  2. 重复以上步骤,直到整个数组有序。

2.4 算法实现

    /**
     * 冒泡排序
     *
     * @param array 待排序的数组
     */
    public static void bubbleSort(int[] array) 
        int n = array.length - 1;
        while (true) 
            int last = 0; // 表示最后一次交换索引位置
            for (int i = 0; i < n; i++) 
                // 如果 array[i] > array[i + 1] 则交换这2个元素的位置 目的是把较大的元素冒泡到后面
                if (array[i] > array[i + 1]) 
                    int temp = array[i];
                    array[i] = array[i + 1];
                    array[i + 1] = temp;
                    last = i;
                
            
            // last 则位最近一次冒泡最后交换位置的起始索引i,因为i后面都是排好序的 所以不需要排序
            n = last;
            // 如果上一次没有发生交换,则数组已经排好顺序,可以退出外层循环了
            if (n == 0) 
                break;
            
        
    

测试代码:

    public static void main(String[] args) 
        int[] array = 1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50;
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    

运行结果:


3. 选择排序(不稳定)

要求

  • 能够用自己语言描述选择排序算法
  • 能够比较选择排序与冒泡排序
  • 理解非稳定排序与稳定排序

3.1 选择排序介绍

选择排序就是重复 从待排序的数据中寻找最小值,将其与序列最左边的数字进行交换 这一操作的算法。在序列中寻找最小值时使用的是线性查找。

对数字1~9进行排序。

使用线性查找在数据中寻找最小值,于是我们找到了最小值1(线性查找的详细说明在3-1节)。

将最小值1与序列最左边的6进行交换,最小值1归位。不过,如果最小值已经在最左端,就不需要任何操作。

在余下的数据中继续寻找最小值。这次我们找到了最小值2。

将数字2与左边第2个数字6进行交换,最小值2归位。

重复同样的操作直到所有数字都归位为止。

排序完成。


3.2 选择排序说明


3.3 选择排序算法描述

  1. 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集

  2. 重复以上步骤,直到整个数组有序


3.4 选择排序代码实现

    /**
     * 选择排序算法实现
     *
     * @param array 待排序数组
     */
    public static void selection(int[] array) 
        // 选择排序需要排序 n - 1 轮
        for (int i = 0; i < array.length - 1; i++) 
            /*
             * i 代表每轮选择最小元素要交换到的目标索引
             * m 代表每轮最小元素的索引
             */
            int m = i;
            for (int j = i + 1; j < array.length; j++) 
                /*
                 * j 元素比  元素还要小, 更新 m
                 */
                if (array[m] > array[j]) 
                    m = j;
                
            
            /*
             * 优化点:为减少交换次数,每一轮可以先找最小的索引,在每轮最后再交换元素
             */
            if (m != i) 
                int temp = array[m];
                array[m] = array[i];
                array[i] = temp;
            
        
    

测试代码:

    public static void main(String[] args) 
        int[] array = 1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50;
        selection(array);
        System.out.println(Arrays.toString(array));
    


3.5 选择排序与冒泡排序比较

  1. 二者平均时间复杂度都是 O ( n 2 ) O(n^2) O(n2)
  2. 选择排序一般要快于冒泡,因为其交换次数少。
  3. 但如果集合有序度高,冒泡优于选择。
  4. 冒泡属于稳定排序算法,而选择属于不稳定排序。
    • 稳定排序指,按对象中不同字段进行多次排序,不会打乱同值元素的顺序。
    • 不稳定排序则反之。

4. 插入排序(稳定)

要求

  • 能够用自己语言描述插入排序算法
  • 能够比较插入排序与选择排序

4.1 插入排序介绍

插入排序是一种从序列左端开始依次对数据进行排序的算法。在排序过程中,左侧的数据陆续归位,而右侧留下的就是还未被排序的数据。插入排序的思路就是从右侧的未排序区域内取出一个数据,然后将它插入到已排序区域内合适的位置上。

此处对数字1~9进行排序。
首先,我们假设最左边的数字5已经完成排序,所以此时只有5是已归位的数字。

接下来,从待排数字(未排序区域)中取出最左边的数字3,将它与左边已归位的数字进行比较。若左边的数字更大,就交换这两个数字。重复该操作,直到左边已归位的数字比取出的数字更小,或者取出的数字已经被移到整个序列的最左边为止。
由于5>3,所以交换这两个数字。

对数字3的操作到此结束。此时3和5已归位,还剩下右边7个数字尚未排序。

接下来是第3轮。和前面一样,取出未排序区域中最左边的数字4,将它与左边的数字5进行比较。

由于5>4,所以交换这两个数字。交换后再把4和左边的3进行比较,发现3<4,因为出现了比自己小的数字,所以操作结束。

于是4也归位了。此时3、4、5都已归位,已排序区域也得到了扩大。

遇到左边的数字都比自己小的情况时……

不需要任何操作即可完成排序。
重复上述操作,直到所有数字都归位。

对所有数字的操作都结束时,排序也就完成了。


4.2 插入排序说明


4.3 插入排序算法描述

  1. 将数组分为两个区域,排序区域和未排序区域,每一轮从未排序区域中取出第一个元素,插入到排序区域(需保证顺序)。

  2. 重复以上步骤,直到整个数组有序。


4.4 插入排序代码实现

    /**
     * 插入排序代码实现
     *
     * @param a 待排序的数组
     */
    public static void insertSort(int[] a) 
        // i 代表待插入元素的索引
        for (int i = 1; i < a.length; i++) 
            // temp 代表待插入的元素值
            int temp = a[i];
            int j = i;
            while (j >= 1) 
                // a[j-1] 是a[i]上一个元素索引,如果 > temp,后移
                if (temp < a[j - 1]) 
                    a[j] = a[j - 1];
                    j--;
                 else 
                    // 如果 a[j-1] 已经 <= t, 则 j 就是插入位置
                    break;
                
            
            a[j] = temp;
        
    

测试代码:

    public static void main(String[] args) 
        int[] array = 1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50;
        insertSort(array);
        System.out.println(Arrays.toString(array));
    

运行结果:


4.5 插入排序与选择排序比较

  1. 二者平均时间复杂度都是 O ( n 2 ) O(n^2) O(n2)
  2. 大部分情况下,插入都略优于选择
  3. 插入属于稳定排序算法,而选择属于不稳定排序

提示

插入排序通常被同学们所轻视,其实它的地位非常重要。小数据量排序,都会优先选择插入排序


5. 希尔排序(插入排序的改进算法)

要求

  • 能够用自己语言描述希尔排序算法

5.1 算法描述

  1. 首先选取一个间隙序列,如 (n/2,n/4 … 1),n 为数组长度

  2. 每一轮将间隙相等的元素视为一组,对组内元素进行插入排序,目的有二

    ① 少量元素插入排序速度很快

    ② 让组内值较大的元素更快地移动到后方

  3. 当间隙逐渐减少,直至为 1 时,即可完成排序


5.2 代码实现

private static void shell(int[] a) 
    int n = a.length;
    for (int gap = n / 2; gap > 0; gap /= 2) 
        // i 代表待插入元素的索引
        for (int i = gap; i < n; i++) 
            int t = a[i]; // 代表待插入的元素值
            int j = i;
            while (j >= gap) 
                // 每次与上一个间隙为 gap 的元素进行插入排序
                if (t < a[j - gap])  // j-gap 是上一个元素索引,如果 > t,后移
                    a[j] = a[j - gap];
                    j -= gap;
                 else  // 如果 j-1 已经 <= t, 则 j 就是插入位置
                    break;
                
            
            a[j] = t;
            System.out.println(Arrays.toString(a) + " gap:" + gap);
        
    


6. 快速排序

要求

  • 能够用自己语言描述快速排序算法
  • 掌握手写单边循环、双边循环代码之一
  • 能够说明快排特点
  • 了解洛穆托与霍尔两种分区方案的性能比较

6.1 快速排序介绍

快速排序算法首先会在序列中随机选择一个基准值(pivot),然后将除了基准值以外的数分为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成以下形式。

[比基准值小的数] 基准值 [比基准值大的数]

接着,对两个“[ ]”中的数据进行排序之后,整体的排序便完成了。对“[ ]”里面的数据进行排序时同样也会使用快速排序。

下面我们就来看看快速排序的步骤。
在序列中随机选择一个基准值。这里选择了4。

将其他数字和基准值进行比较。小于基准值的往左移,大于基准值的往右移。

首先,比较3和基准值4。

因为3<4,所以将3往左移。

接下来,比较5和基准值4。

因为5>4,所以将5往右移。
对其他数字也进行同样的操作,最终结果如上图所示。


把基准值4插入序列。这样,4左边就是比它小的数字,右边就是比它大的数字。

分别对左边和右边的数据进行排序后,就能完成整体的排序。


随机选择一个基准值。这次选择6。

把其余数据分别和基准值6进行比较,小于基准值的就往左移,大于的就往右移。

完成了大小比较和位置移动。

和前面一样,对左右两边分别进行排序,进而完成整体排序。但是此时左边只有5,所以已经是排序完成的状态,不需要任何操作。而右边就和前面一样,先选出基准值。

选择8作为基准值。

将9和7分别与基准值8进行比较后,两个数字的位置便分好了。8的两边都只有一个数据,因此不需要任何操作。这样7、8、9便完成排序了。


回到上一行,由于7、8、9完成了排序,所以5、6、7、8、9也完成了排序。
于是,最初选择的基准值4的右边排序完毕。

左边也以相同的操作进行排序,整体的排序工作也就完成了。


6.2 快速排序说明




6.3 算法描述

  1. 每一轮排序选择一个基准点(pivot)进行分区
    1. 让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区
    2. 当分区完成时,基准点元素的位置就是其最终位置
  2. 在子分区内重复以上过程,直至子分区元素个数少于等于 1,这体现的是分而治之的思想 (divide-and-conquer
  3. 从以上描述可以看出,一个关键在于分区算法,常见的有洛穆托分区方案、双边循环分区方案、霍尔分区方案

6.4 快速排序代码实现

单边循环快排(lomuto 洛穆托分区方案)

  1. 选择最右元素作为基准点元素

  2. j 指针负责找到比基准点小的元素,一旦找到则与 i 进行交换

  3. i 指针维护小于基准点元素的边界,也是每次交换的目标索引

  4. 最后基准点与 i 交换,i 即为分区位置

public static void quick(int[] a, int l, int h) 
    if (l >= h) 
        return;
    
    int p = partition(a, l, h); // p 索引值
    quick(a, l, p - 1); // 左边分区的范围确定
    quick(a, p + 1, h); // 左边分区的范围确定


private static int partition(int[] a, int l, int h) 
    int pv = a[h]; // 基准点元素
    int i = l;
    for (int j = l; j < h; j++) 
        if (a[j] < pv) 
            if (i != j) 
                swap(a, i, j);
            
            i++;
        
    
    if (i != h) 
        swap(a, h, i);
    
    System.out.println(Arrays.toString(a) + " i=" + i);
    // 返回值代表了基准点元素所在的正确索引,用它确定下一轮分区的边界
    return i;

双边循环快排(不完全等价于 hoare 霍尔分区方案)

  1. 选择最左元素作为基准点元素
  2. j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i,j 相交
  3. 最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置

要点

  1. 基准点在左边,并且要先 j 后 i

  2. while( i < j && a[j] > pv ) j--

  3. while ( i< j && a[i] <=pv ) i++

private static void quick(int[] a, int l, int h) 
    if (l >= h) 
        return;
    
    int p = partition(a, l, h);
    quick(a, l, p - 1);
    quick(a, p + 1, h);


private static int partition(int查看详情  

笔试算法题:冒泡排序(bubblesort),二分查找,二叉树三种遍历(代码片段)

https://img.136.la/20210614/6f4c925813974a33a92fc2f0fdea0310.jpg冒泡排序:基本思想:冒泡排序,类似于水中冒泡,较大的数沉下去,较小的数慢慢冒起来,假设从小到大,即为较大的数慢慢往后排,较小的数慢慢往前排。直观表达,每一趟遍历... 查看详情

java八股文面试题(重点)

Java面试题大全(2020版)JAVA面试八股文Java八股文2021互联网大厂面试问题集合《剑指offer》Java版全系列题解(2021版,持续更新!)2020最新-精选基础算法100题(面试必备)Java基础知识面试题(202... 查看详情

java八股文面试题(重点)

Java面试题大全(2020版)JAVA面试八股文Java八股文2021互联网大厂面试问题集合《剑指offer》Java版全系列题解(2021版,持续更新!)2020最新-精选基础算法100题(面试必备)Java基础知识面试题(202... 查看详情

⭐算法入门⭐《二分枚举》中等02——leetcode面试题10.09.排序矩阵查找(代码片段)

文章目录一、题目1、题目描述2、基础框架3、原题链接二、解题报告1、思路分析2、时间复杂度3、代码详解三、本题小知识四、加群须知一、题目1、题目描述  给定m×nm\\timesnm×n矩阵,每一行、每一列都按升序排列,... 查看详情

14java常用类(stringbuffer)排序算法(冒泡排序选择排序插入排序快速排序)查找算法(二分查找)

统计大串中小串出现的次数(新的解决方案)classMyTest{publicstaticvoidmain(String[]args){Stringsource="woyaoxuejava,xihuanjava,aijava,javajavawozuiai";Stringtarget="java";intlength=source.length();Strin 查看详情

java基础冒泡选择排序二分查找

冒泡排序的思路就是前一个和后一个进行比较,如果大的就交换位置 大的数字后浮 如 12   8  5  31第一轮 8 5 12 31第二轮 5 8  12 31........代码如下 package 查看详情

面试之基础算法题:求一个数字在给定的已排序数组中出现的起始终止索引号(java版)

题目给定一个升序的整数数组,查找某一个值在数组中出现的索引号,例如,输入数组​​[2,3,3,4,4,5]​​​;查找的数是3,则返回​​[1,2]​​。时间复杂度要求为O(logN)。思路基本上大致思考一番,就知道可以用二分查找法求... 查看详情

java冒泡排序和二分查找(预习)

经查阅,将资料整理如下:一、冒泡排序1、算法简介它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完... 查看详情

滴滴2019年php高级研发工程师面试题总结

滴滴2019年php高级研发工程师面试题总结一算法基本排序算法要会写,时间复杂度要会推算,主要是冒泡排序,快速排序,选择排序.查找算法,要会写二分查找法,实际场景要会应用.实例算法思路要明白,基本算法看多了,我觉得是几种思... 查看详情

数据结构和算法-面试题(代码片段)

##########################################"""数据结构:1,用Python代码简单实现一个栈。实现pop/push及max方法,要求能在O(1)时间内取得最大值。排序算法:写个快速排序热热身,分析一下复杂度,如果不使用额外的空间,应该怎么写?快... 查看详情

排序算法(冒泡,选择,插入,快速)查找算法(二分,快速)

                        四种排序算法1.冒泡排序  思路分析:从前往后相邻的两个数一次进行比较,大的往下沉,小的网上冒。当相邻的两个数的比较后发现他们的排序与排序要求相反,就互换。 ... 查看详情

❤️互联网大厂面试高频算法题汇总❤️——❤️二分专场❤️(代码片段)

文章目录1、前言2、题目汇总3、二分模板4、二分流程5、二分高频题详解5.1、LeetCode33.搜索旋转排序数组5.2、LeetCode704.二分查找5.3、LeetCode69.x的平方根5.4、LeetCode4.寻找两个正序数组的中位数5.5、LeetCode153.寻找旋转排序数组中的最... 查看详情

算法基础(面试)(代码片段)

...多算法当中。因为其实现代码较短,应用较常见。所以在面试中经常会问到排序算法及其相关的问题。但万变不离其宗,只要熟悉了思想,灵活运用也不是难事。一般在面试中最常考的是快速排序和归并排序,并且经常有面试官... 查看详情

二分查找专题总结-基础篇(代码片段)

...是一个常考的知识点。同时,它也是非常容易出错的一道面试题。左右指针的位置,取值,比较是大于还是大于等于。里面细节很多。死记硬背往往容易出错,只有真正理解思路和多多练习,才能掌握不出错的”二分算法“。本... 查看详情

java面试题常见算法总结(代码片段)

常见算法1、7种常见排序算法1.1、冒泡排序1.2、简单选择排序1.3、直接插入排序1.4、希尔排序1.5、归并排序1.6、快速排序1.7、堆排序1、7种常见排序算法7种常见排序算法的时间复杂度、辅助空间以及稳定性对照表。排序算法平均... 查看详情

面了50次阿里,把50篇面试经历都搞下来了,长记性了

...也能替你查漏补缺,毕竟现在这么卷,该掌握的八股文还是得掌握。面试无非就是考察计算机基础+算法+语言特性+项目 查看详情

面了50次阿里,把50篇面试经历都搞下来了,长记性了

...也能替你查漏补缺,毕竟现在这么卷,该掌握的八股文还是得掌握。面试无非就是考察计算机基础+算法+语言特性+项目 查看详情