• 黑马Java八股文面试题视频教程,Java面试八股文宝典(基础篇)


    1.二分查找

    1.1二分查找实现

    定义

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

    基本步骤:

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

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

    获取中间索引 M = Floor((L+R) /2)

    中间索引的值 A[M] 与待搜索的值 T 进行比较

    ① A[M] == T 表示找到,返回中间索引

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

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

    当 L > R 时,表示没有找到,应结束循环

    代码实现

    public class BinarySearch {
        public static void main(String[] args) {
            int[] array = {1,5,8,11,19,22,31,35,40,45,48,49,50}; //已排序的数组
            int target = 48; //需要搜索的目标值
            int index = binarySearch(array, target); //元素对应的索引
            System.out.println(index);
        }
     
        //二分查找,找到返回元素索引,找不到返回 -1
        public static int binarySearch(int[] array, int target){
            //定义左边界 left
            int left = 0;
            //定义右边界 right
            int right = array.length - 1;
            //定义中间的索引
            int middle;
            while (left <= right) {//表示左边界还未超过右边界,重复执行 3、4 两步
                middle = (left + right)/2;//获得中间索引
                if (array[middle] == target) { //找到目标值
                    return middle;
                }else if (array[middle] > target){ //表示中间值middle右边的均大于目标值,舍弃
                    right = middle - 1;//重新定义右边界
                }else { // 中间值 middle 左边的均小于目标值 ,舍弃
                    left = middle + 1;//重新定义左边界
                }
            }
            return -1;
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    运行结果:

    10
    
    • 1

    1.2整数溢出

    上述代码虽然实现了二分查找算法 ,但是在取中间值的时候超过整数的最大值,就会出现整数溢出的问题,所以需要对上述代码进行优化,从而解决整数溢出的问题

    方法1:在取中间值时,对原本的公式进行公式演化,可以避免造成整数溢出的问题

    演化过程:
    
            middle = (left + right) / 2 = left/2 + right/2 = left - left/2 + right/2 = left+(right-left)/2
    
    • 1
    • 2
    • 3

    优化代码:

    int left = 0;
    int right = Integer.MAX_VALUE - 1;
     
    //int middle = (left + right) / 2;
    //方法1.进行公式演化,可以避免造成整数溢出的问题
    // -- left/2 + right/2 -- left - left/2 + right/2 -- left+(right-left)/2
    int middle = left + (right - left) / 2;
    System.out.println(middle);
     
    //此时假设在右侧
    left = middle + 1;
    middle = left + (right - left) / 2;
    System.out.println(middle);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    方法2:同样是在取中间值的时候,利用无符号的右移运算,也可以解决整数溢出的问题,而且也比除法的效率高

    优化代码:

    int left = 0;
    int right = Integer.MAX_VALUE - 1;
    
    //方法2.利用无符号的右移运算,也可以解决整数溢出的问题,而且也比除法的效率高
    int middle = (left + right) >>> 1;  //>>> 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。
    System.out.println(middle);
    
    //此时假设在右侧
    left = middle + 1;
    middle = (left + right) >>> 1;
    System.out.println(middle);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.3相关面试题

    1. (京东实习生招聘)有一个有序表为 1,5,8,11,19,22,31,35,40,45,48,49,50 当二分查找值为 48 的结点时,查找成功需要比较的次数 — 4次

    2. (美团点评校招)使用二分法在序列 1,4,6,7,15,33,39,50,64,78,75,81,89,96 中查找元素 81 时,需要经过( )次比较 — 4次

    3. (北京易道博识校招)在拥有128个元素的数组中二分查找一个数,需要比较的次数最多不超过多少次 — 7次

    解题办法:

    1、2 — 奇数二分取中间,偶数二分取中间靠左

    3 — 2ⁿ = N = log₂N = log₁₀N / log₁₀2 (其中 n 为查找次数,N 为元素个数 )

                结果为整数--即为最终结果;结果为小数,则舍去小数部分,整数加一为最终结果
    
    • 1

    【注意】

        1.上述写的二分查找是以 jdk 中 Arrays.binarySearch 的实现作为示范
    
        2.但实际上,二分查找有诸多变体,一旦使用变体的实现代码,则左右边界的选取会有变化,进而会影响之前选择题的答案选择
    
    • 1
    • 2
    • 3

    2.冒泡排序

    冒泡排序定义

    冒泡排序就是重复“从序列右边开始比较相邻两个数字的大小,再根据结果交换两个数字的位置”这一操作的算法。在这个过程中,数字会像泡泡一样,慢慢从右往左“浮”到序列的顶端,所以这个算法才被称为“冒泡排序”。

    基本步骤:

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

    代码实现:

    public class BubbleSort {
        public static void main(String[] args) {
            int[] array = {5,9,7,4,1,3,2,8};
            bubble(array);
        }
     
        
     
        public static void bubble(int[] array){
            for (int j = 0; j < array.length - 1; j++) {
                //一轮冒泡
                boolean swapped = false; //表示是否发生了交换 false表示没有发生交换
                for (int i = 0; i < array.length - 1 - j; i++) {
                    if (array[i] > array[i+1]) {
                        swap(array, i, i+1);
                        swapped = true;
                    }
                }
                
                if (!swapped) {//数组中的元素没有发生交换,直接退出循环
                    break;
                }
                System.out.println("第"+(j+1)+"轮冒泡:"+ Arrays.toString(array));
            }
        }
     
        public static void swap(int[] array,int i,int j){//将数组中下标为i和j的元素分别交换位置
            int t = array[i]; //先将下标为 i 的元素取出来放进缓存区
            array[i] = array[j];//将下标为 j 的元素放到原先下标为 i 的位置
            array[j] = t; //将下标为 i 的元素 从缓存区里取出来放入 原先下标为 j 的位置
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行结果:

    第1轮冒泡:[5, 7, 4, 1, 3, 2, 8, 9]
    第2轮冒泡:[5, 4, 1, 3, 2, 7, 8, 9]
    第3轮冒泡:[4, 1, 3, 2, 5, 7, 8, 9]
    第4轮冒泡:[1, 3, 2, 4, 5, 7, 8, 9]
    第5轮冒泡:[1, 2, 3, 4, 5, 7, 8, 9]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上述代码的实现相较普通的冒泡排序优化在

    • 每经过一轮冒泡,内层循环就可以减少一次
    • 如果某一轮冒泡没有发生交换,则表示所有的数据有序,可以结束外层循环

    进行再一步优化:

    public static void bubble_v2(int[] array){
            int n = array.length - 1;
            while (true) {
                int lastIndex = 0;//定义在一轮冒泡中最后发生交换的下标
                for (int i = 0; i < n; i++) {
                    if (array[i] > array[i+1]) {
                        swap(array, i, i+1);
                        lastIndex = i;//取交换前的 前一个元素的下标
                    }
                }
                n = lastIndex; //将 lastIndex 作为 新一轮冒泡的循环次数
                System.out.println(Arrays.toString(array));
                if (n == 0) {//循环次数为零,退出循环
                    break;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果:

    [5, 7, 4, 1, 3, 2, 8, 9]
    [5, 4, 1, 3, 2, 7, 8, 9]
    [4, 1, 3, 2, 5, 7, 8, 9]
    [1, 3, 2, 4, 5, 7, 8, 9]
    [1, 2, 3, 4, 5, 7, 8, 9]
    [1, 2, 3, 4, 5, 7, 8, 9]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    相较上一个冒泡算法的实现,本次实现的优化点在 每轮冒泡时,最后一次交换索引可以作为下一轮冒泡的比较次数,如果这个值为零,表示整个数组有序,直接退出外层循环即可

    3.选择排序

    定义:

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

    基本步骤:

    1. 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集
    2. 重复以上步骤,直到整个数组有序

    代码实现:

    public class SelectionSort {
        public static void main(String[] args) {
            int[] array = {5,3,7,2,1,9,8,4};
            selection(array);
        }
     
        private static void selection(int[] array){
            for (int i = 0; i < array.length - 1; i++) {//i 代表每轮选择最小元素要交换到的目标索引
                int minIndex = i; // 代表最小元素的索引
                for (int j = minIndex + 1; j < array.length; j++) { //假定下标为 0 的元素为最小,所以循环次数为数组的长度
                    if (array[minIndex] > array[j]) {
                        minIndex = j; //更新最小下标
                    }
                }
                if (minIndex != i) {  //如果最小下标发生变化,则更换元素的位置
                    swap(array, minIndex, i);
                }
                System.out.println(Arrays.toString(array));
            }    
        }
     
        public static void swap(int[] array,int i,int j){//将数组中下标为i和j的元素分别交换位置
            int t = array[i]; //先将下标为 i 的元素取出来放进缓存区
            array[i] = array[j];//将下标为 j 的元素放到原先下标为 i 的位置
            array[j] = t; //将下标为 i 的元素 从缓存区里取出来放入 原先下标为 j 的位置
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行结果:

    [1, 3, 7, 2, 5, 9, 8, 4]
    [1, 2, 7, 3, 5, 9, 8, 4]
    [1, 2, 3, 7, 5, 9, 8, 4]
    [1, 2, 3, 4, 5, 9, 8, 7]
    [1, 2, 3, 4, 5, 9, 8, 7]
    [1, 2, 3, 4, 5, 7, 8, 9]
    [1, 2, 3, 4, 5, 7, 8, 9]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.冒泡排序和选择排序的比较

    4.1区别

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

    4.2稳定排序与不稳定排序

    分别使用 冒泡排序与选择排序 对扑克牌的花色和数字进行排序(先对花色进行排序,再对数字进行排序)

    代码实现:

    public class StableVsUnstable {
        public static void main(String[] args) {
            System.out.println("=================不稳定================");
            Card[] cards = getStaticCards();
            System.out.println(Arrays.toString(cards));
            selection(cards, Comparator.comparingInt((Card a) -> a.sharpOrder).reversed());
            System.out.println(Arrays.toString(cards));
            selection(cards, Comparator.comparingInt((Card a) -> a.numberOrder).reversed());
            System.out.println(Arrays.toString(cards));
     
            System.out.println("=================稳定=================");
            cards = getStaticCards();
            System.out.println(Arrays.toString(cards));
            bubble(cards, Comparator.comparingInt((Card a) -> a.sharpOrder).reversed());
            System.out.println(Arrays.toString(cards));
            bubble(cards, Comparator.comparingInt((Card a) -> a.numberOrder).reversed());
            System.out.println(Arrays.toString(cards));
        }
     
        public static void bubble(Card[] a, Comparator<Card> comparator) {
            int n = a.length - 1;
            while (true) {
                int last = 0; // 表示最后一次交换索引位置
                for (int i = 0; i < n; i++) {
                    if (comparator.compare(a[i], a[i + 1]) > 0) {
                        swap(a, i, i + 1);
                        last = i;
                    }
                }
                n = last;
                if (n == 0) {
                    break;
                }
            }
        }
     
        private static void selection(Card[] a, Comparator<Card> comparator) {
            for (int i = 0; i < a.length - 1; i++) {
                // i 代表每轮选择最小元素要交换到的目标索引
                int s = i; // 代表最小元素的索引
                for (int j = s + 1; j < a.length; j++) {
                    if (comparator.compare(a[s], a[j]) > 0) {
                        s = j;
                    }
                }
                if (s != i) {
                    swap(a, s, i);
                }
            }
        }
     
        public static void swap(Card[] a, int i, int j) {
            Card t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
     
        enum Sharp {
            diamond, club, heart, spade, black, red
        }
        static Card[] getStaticCards() {
            List<Card> list = new ArrayList<>();
            Card[] copy = Arrays.copyOfRange(Card.cards, 2, 13 * 4 + 2);
            list.add(copy[7]);
            list.add(copy[12]);
            list.add(copy[12+13]);
            list.add(copy[10]);
            list.add(copy[9]);
            list.add(copy[9+13]);
            return list.toArray(new Card[0]);
        }
     
     
        static Map<String, Integer> map = Map.ofEntries(
                Map.entry("RJ", 16),
                Map.entry("BJ", 15),
                Map.entry("A", 14),
                Map.entry("K", 13),
                Map.entry("Q", 12),
                Map.entry("J", 11),
                Map.entry("10", 10),
                Map.entry("9", 9),
                Map.entry("8", 8),
                Map.entry("7", 7),
                Map.entry("6", 6),
                Map.entry("5", 5),
                Map.entry("4", 4),
                Map.entry("3", 3),
                Map.entry("2", 2)
        );
     
        static class Card {
            private Sharp sharp;
            private final String number;
            private final int numberOrder;
            private final int sharpOrder;
     
            public Card(Sharp sharp, String number) {
                this.sharp = sharp;
                this.number = number;
                this.numberOrder = map.get(number);
                this.sharpOrder = sharp.ordinal();
            }
     
            private static final Card[] cards;
     
            static {
                cards = new Card[54];
                Sharp[] sharps = {Sharp.spade, Sharp.heart, Sharp.club, Sharp.diamond};
                String[] numbers = {"A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2"};
                int idx = 2;
                for (Sharp sharp : sharps) {
                    for (String number : numbers) {
                        cards[idx++] = new Card(sharp, number);
                    }
                }
                cards[0] = new Card(Sharp.red, "RJ");
                cards[1] = new Card(Sharp.black, "BJ");
            }
     
            @Override
            public String toString() {
                return switch (sharp) {
                    case heart -> "[\033[31m" + "♥" + number + "\033[0m]";
                    case diamond -> "[\033[31m" + "♦" + number + "\033[0m]";
                    case spade -> "[\033[30m" + "♠" + number + "\033[0m]";
                    case club -> "[\033[30m" + "♣" + number + "\033[0m]";
                    case red -> "[\033[31m" + "\uD83C\uDFAD" + "\033[0m]";
                    case black -> "[\033[30m" + "\uD83C\uDFAD" + "\033[0m]";
                };
            }
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134

    运行结果:

    =================不稳定================
    [[7], [2], [2], [4], [5], [5]]
    [[7], [2], [4], [5], [2], [5]]
    [[7], [5], [5], [4], [2], [2]]
    =================稳定=================
    [[7], [2], [2], [4], [5], [5]]
    [[7], [2], [4], [5], [2], [5]]
    [[7], [5], [5], [4], [2], [2]]
    ————————————————
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.插入排序

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

    基本步骤:

    1. 将数组分为两个区域,排序区域和未排序区域,每一轮从未排序区域中取出第一个元素,插入到排序区域(需保证顺序)
    2. 重复以上步骤,直到整个数组有序

    代码实现:

    public class InsertSort {
        public static void main(String[] args) {
            int[] array = {9,3,7,2,5,8,1,4};
            insert(array);
        }
     
        private static void insert(int[] array){
            for (int i = 1; i < array.length; i++) {//i 表示待插入元素的索引
                int t = array[i]; //表示待插入的元素值
                int j = i - 1; //表示已将排序区域的元素索引
                while (j >= 0) {
                    if (t < array[j]) {//待插入的元素值 小于 已将排序区域的元素索引的值
                        array[j+1] = array[j];//将下标为 j 的元素向后移动一位
                    }else { //待插入的元素值 大于 已经排序区域的元素索引的值
                        break; //找到插入位置,直接退出循环
                    }
                    j--; //依次向前进行比较,直到下标出现为负,退出循环
                }
                array[j+1] = t;//将待插入的元素值 插入到 空出的位置
                System.out.println(Arrays.toString(array));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    运行结果:

    [9, 3, 7, 2, 5, 8, 1, 4]
    [3, 9, 7, 2, 5, 8, 1, 4]
    [3, 7, 9, 2, 5, 8, 1, 4]
    [2, 3, 7, 9, 5, 8, 1, 4]
    [2, 3, 5, 7, 9, 8, 1, 4]
    [2, 3, 5, 7, 8, 9, 1, 4]
    [1, 2, 3, 5, 7, 8, 9, 4]
    [1, 2, 3, 4, 5, 7, 8, 9]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.选择排序和插入排序的比较

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

    7.希尔排序

    基本步骤:

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

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

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

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

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

    代码实现:

    package practise;
    
    import java.util.Arrays;
    
    public class ShellSort {
        public static void main(String[] args) {
            int[] array = {9, 3, 7, 2, 5, 8, 1, 4};
            shell(array);
        }
    /*
    *
    * 首先选取一个间隙序列,如 (n/2,n/4 … 1),n 为数组长度
    
    每一轮将间隙相等的元素视为一组,对组内元素进行插入排序,目的有二
    
    ① 少量元素插入排序速度很快
    
    ② 让组内值较大的元素更快地移动到后方
    
    当间隙逐渐减少,直至为 1 时,即可完成排序
    *
    * */
        private static void shell(int[] arr) {
            int length = arr.length;
            int temp;
            for (int step = length / 2; step >= 1; step /= 2) {
                for (int i = step; i < length; i++) {
                    temp = arr[i];
                    int j = i - step;
                    while (j >= 0 && arr[j] > temp) {
                        arr[j + step] = arr[j];
                        j -= step;
                    }
                    arr[j + step] = temp;
                    System.out.println(Arrays.toString(arr));
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    运行结果:

    [5, 3, 7, 2, 9, 8, 1, 4]  
    [5, 3, 7, 2, 9, 8, 1, 4]
    [5, 3, 1, 2, 9, 8, 7, 4]
    [5, 3, 1, 2, 9, 8, 7, 4]
    [1, 3, 5, 2, 9, 8, 7, 4]
    [1, 2, 5, 3, 9, 8, 7, 4]
    [1, 2, 5, 3, 9, 8, 7, 4]
    [1, 2, 5, 3, 9, 8, 7, 4]
    [1, 2, 5, 3, 7, 8, 9, 4]
    [1, 2, 5, 3, 7, 4, 9, 8]
    [1, 2, 5, 3, 7, 4, 9, 8]
    [1, 2, 5, 3, 7, 4, 9, 8]
    [1, 2, 3, 5, 7, 4, 9, 8]
    [1, 2, 3, 5, 7, 4, 9, 8]
    [1, 2, 3, 4, 5, 7, 9, 8]
    [1, 2, 3, 4, 5, 7, 9, 8]
    [1, 2, 3, 4, 5, 7, 8, 9]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    8.插入和选择-推导某一轮排序结果

    题目:

    使用直接插入排序算法对序列18,23,19,9,23,15进行排序,第三趟排序后的结果为()

    A.9,18,15,23,19,23 B.18,23,19,9,23,15

    C.18,19,23,9,23,15 D.9,18,19,23,23,15

    推算过程:

            18,23,19,9,23,15       18,19,23,9,23,15      9,18,19,23,23,15
    
    • 1

    使用直接选择排序算法对序列18,23,19,9,23,15进行排序,第三趟排序后的结果为()

    A.9,23,19,18,23,15 B.9,15,18,19,23,23

    C.18,19,23,9,23,15 D.18,19,23,9,15,23

    推算过程:

        9,23,19,18,23,15  9,15,19,18,23,23  9,15,18,19,23,23
    
    • 1

    9.快速排序

    1.算法描述
    每一轮排序选择一个基准点(pivot)进行分区

    让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区

    当分区完成时,基准点元素的位置就是其最终位置

    在子分区内重复以上过程,直至子分区元素个数少于等于 1,这体现的是分而治之的思想 (divide-and-conquer)

    从以上描述可以看出,一个关键在于分区算法,常见的有洛穆托分区方案、双边循环分区方案、霍尔分区方案

    2.单边循环快排(lomuto 洛穆托分区方案)
    基本步骤:

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

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

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

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

    代码实现:

    public class QuickSort1 {
        public static void main(String[] args) {
            int[] array = {5,3,7,2,9,8,1,4};
            //3,5,7,2,9,8,1,4  3,2,7,5,9,8,1,4  3,2,1,5,9,8,7,4  3,2,1,4,9,8,7,5
            //3,2,1,4,9,8,7,5  1,2,3,4,9,8,7,5  1,2,3,4,5,8,7,9  1,2,3,4,5,7,8,9
            quick(array, 0, array.length-1);
        }
     
        //递归
        public static void quick(int[] array,int low,int high){
            if (low >= high) {
                return;
            }
            int p = partition(array, low, high); //表示  基准点元素所在的正确索引
            //确定左边分区范围
            quick(array,low,p-1);
            //确定右边分区范围
            quick(array, p+1, high);
        }
     
        //分区
        private static int partition(int[] array,int low,int high){ //low 表示左边界  high: 表示右边界
            int pv = array[high]; //选取最右边的元素为基准点元素
            int i = low;
            for (int j = low; j < high; j++) {
                if (array[j] < pv) {//下标小于基准点的元素
                    swap(array, i, j); //将小于基准点的元素换到 下标为i 所在的位置
                    i++;
                }
            }
            swap(array, high, i); //将基准点和 下标为i 的元素互换位置
            System.out.println(Arrays.toString(array)+"i="+i);
            //返回值表示基准点元素所在的正确索引,以此来确定下一轮的边界分区
            return i;
        }
     
        public static void swap(int[] array,int i,int j){//将数组中下标为i和j的元素分别交换位置
            int t = array[i]; //先将下标为 i 的元素取出来放进缓存区
            array[i] = array[j];//将下标为 j 的元素放到原先下标为 i 的位置
            array[j] = t; //将下标为 i 的元素 从缓存区里取出来放入 原先下标为 j 的位置
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    运行结果:

    [3, 2, 1, 4, 9, 8, 7, 5]i=3
    [1, 2, 3, 4, 9, 8, 7, 5]i=0
    [1, 2, 3, 4, 9, 8, 7, 5]i=2
    [1, 2, 3, 4, 5, 8, 7, 9]i=4
    [1, 2, 3, 4, 5, 8, 7, 9]i=7
    [1, 2, 3, 4, 5, 7, 8, 9]i=5
    3.双边循环快排
    基本步骤:

    选择最左元素作为基准点元素

    j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i,j 相交

    最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置

    代码实现:

    public class QuickSort2 {
        public static void main(String[] args) {
            int[] array = {5,3,7,2,9,8,1,4};
            // 5,3,4,2,9,8,1,7  5,3,4,2,1,8,9,7  1,3,4,2,5,8,9,7
            // 1,3,2,4,5,8,9,7  1,2,3,4,5,8,9,7  1,2,3,4,5,8,7,9  1,2,3,4,5,7,8,9
            quick(array, 0, array.length-1);
        }
     
        //递归
        public static void quick(int[] array,int low,int high){
            if (low >= high) {
                return;
            }
            int p = partition(array, low, high); //表示  基准点元素所在的正确索引
            //确定左边分区范围
            quick(array,low,p-1);
            //确定右边分区范围
            quick(array, p+1, high);
        }
     
        //分区
        private static int partition(int[] array,int low,int high){ //low 表示左边界  high: 表示右边界
            int pv = array[low]; //选取最左边的元素为基准点元素
            int i = low; //i 开始位于左边界
            int j = high; // j 开位于右边界
            while (i < j){
                //j 从最右边的元素开始找 小于基准点的元素
                while (i < j && array[j] > pv) { // i < j -- 防止 i 和 j发生越界
                    j--;
                }
                //i 从最左边的元素开始找 大于基准点的元素
                while (i < j && array[i] <= pv) { //array[i] <= pv -- i开始位于左边界,且 pv 为左边界的元素 如果不相等,就无法进入循环
                    i++;
                }
                swap(array, i, j); // j 找到小于基准点的元素  i找到大于基准点的元素,两者位置发生互换
            }
            swap(array, low, j); //基准点 和 i(i和j相等) 互换,i 为 新的分区位置
            System.out.println(Arrays.toString(array) + "j=" + j);
            return j;
        }
     
        public static void swap(int[] array,int i,int j){//将数组中下标为i和j的元素分别交换位置
            int t = array[i]; //先将下标为 i 的元素取出来放进缓存区
            array[i] = array[j];//将下标为 j 的元素放到原先下标为 i 的位置
            array[j] = t; //将下标为 i 的元素 从缓存区里取出来放入 原先下标为 j 的位置
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    运行结果:

    [1, 3, 4, 2, 5, 8, 9, 7]j=4
    [1, 3, 4, 2, 5, 8, 9, 7]j=0
    [1, 2, 3, 4, 5, 8, 9, 7]j=2
    [1, 2, 3, 4, 5, 7, 8, 9]j=6
    4.快速排序的特点
    平均时间复杂度是 O(nlog₂⁡n ),最坏时间复杂度 O(n²)

    数据量较大时,优势非常明显

    属于不稳定排序

    5.洛穆托分区方案 vs 霍尔分区方案
    霍尔的移动次数平均来讲比洛穆托少3倍

    10.ArrayList-扩容规则

    扩容规则:

    1. ArrayList() 会使用长度为零的数组 (调用无参构造的时候)

    2. ArrayList(int initialCapacity) 会使用指定容量的数组(调用有参构造的时候,其中参数为整形)

    3. public ArrayList(Collection c) 会使用 c 的大小作为数组容量(调用有参构造的时候,其中参数为集合)

    4. add(Object o) 首次扩容为 10,再次扩容为上次容量的 1.5 倍(这里用的是位运算)

    5. addAll(Collection c) 没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量 1.5 倍, 实际元素个数)

    11.Iterato_FailFast 和 Iterator_FailSafe

    1.基本介绍

    • fail-fast : 一旦发现遍历的同时,其他人来修改,则立刻抛出异常
    • fail-safe : 发现遍历的同时,其他人来修改,应当有应对策略,例如牺牲一致性来让整个遍历运行完成

    2.两者区别

    @SuppressWarnings("all")
    public class FailFastVsFailSafe {
     
        private static void failFast(){
            ArrayList<Student> list = new ArrayList<>();
            list.add(new Student("A"));
            list.add(new Student("B"));
            list.add(new Student("C"));
            list.add(new Student("D"));
            for (Student student : list) {
                System.out.println(student);
            }
            System.out.println(list);
        }
     
        private static void failSafe(){
            CopyOnWriteArrayList<Student> list = new CopyOnWriteArrayList<>();
            list.add(new Student("A"));
            list.add(new Student("B"));
            list.add(new Student("C"));
            list.add(new Student("D"));
            for (Student student : list) {
                System.out.println(student);
            }
            System.out.println(list);
        }
     
        static class Student{
            String name;
     
            public Student(String name) {
                this.name = name;
            }
     
            @Override
            public String toString() {
                return "Student{" +
                        "name='" + name + '\'' +
                        '}';
            }
        }
     
        public static void main(String[] args) {
            failFast();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    接下来分别演示两者的区别:

        在 failFast()方法中的 for循环遍历list 处加上断点,并在断点上添加条件  student.name.equals("C") ,用debug来调试程序,启动程序后,在调试器页面可以看到 ArrayList 集合中有四个元素,此时模拟另外一个线程来向 ArrayList 中添加一个元素(Alt + F8)
    
    • 1
  • 相关阅读:
    基于高阶微分器的无模型滑模控制器及其在自动电压调节器中的应用
    【Mybatis小白从0到90%精讲】07:Mybatis 传递参数方式详解
    vue问题记录
    学习笔记2--自动驾驶技术国内外发展
    软考是什么?-最全软考详解
    [npm]package.json文件
    oracle行转列、列转行总结
    如何提高Hbase的读取效率
    数字化校园建设规划方案
    JRDZ静态中间继电器
  • 原文地址:https://blog.csdn.net/weixin_45428910/article/details/128047301