Java面试黄金宝典26

1. 什么是快速排序

  • 定义

快速排序(Quick Sort)是对冒泡排序的一种改进,它采用分治法(Divide and Conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。该算法选择一个基准值(pivot),将数组分为两部分,使得左边部分的所有元素都小于等于基准值,右边部分的所有元素都大于等于基准值,然后分别对左右两部分递归地进行快速排序,最终使整个数组有序。

  • 要点
  1. 基准值选择:基准值的选取对排序性能有较大影响,常见的选择方式有选第一个元素、最后一个元素、中间元素或者随机元素作为基准值。
  2. 分区操作:这是快速排序的核心,通过分区将数组分为两部分,使得左边部分元素小于等于基准值,右边部分元素大于等于基准值。
  3. 递归排序:对分区后的左右两部分分别递归调用快速排序算法,直到子数组不可再分。

  • 应用
  1. 大规模数据排序:在处理大规模无序数据时,快速排序平均时间复杂度为 O(nlogn),能高效完成排序任务。例如数据库中对大量数据记录按照某个字段进行排序。
  2. 编程语言内置排序函数:很多编程语言的内置排序函数在一定条件下会采用快速排序算法,如 Python 的 sorted() 函数在部分情况下使用优化后的快速排序。

示例代码

java

public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pivotIndex = partition(arr, low, high);
            quickSort(arr, low, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (arr[j] < pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);
        return i + 1;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2. 什么是直接选择排序

  • 定义

直接选择排序(Straight Selection Sort)是一种简单的排序算法。它的基本思想是在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

  • 要点
  1. 选择最小(大)元素:每次遍历未排序序列,找出最小(大)元素的位置。
  2. 交换元素:将找到的最小(大)元素与未排序序列的第一个元素交换位置。

  • 应用
  1. 数据量较小的排序场景:由于直接选择排序的时间复杂度为 O(n^2),在数据量较小时,实现简单且性能损失可接受。比如对班级里少量学生的成绩进行排序。
  2. 对排序稳定性要求不高的场景:直接选择排序是不稳定的排序算法,当对排序稳定性没有严格要求时可以使用。

示例代码

java

public class SelectionSort {
    public static void selectionSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            swap(arr, i, minIndex);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

3. 什么是堆排序

  • 定义

堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。堆是一种完全二叉树,分为大顶堆和小顶堆。大顶堆的每个节点的值都大于或等于其子节点的值,小顶堆则相反。堆排序的基本步骤是先将数组构建成一个大顶堆,然后将堆顶元素(最大值)与数组的最后一个元素交换,再对剩余的元素重新调整为大顶堆,重复这个过程直到数组有序。

  • 要点
  1. 堆的构建:从最后一个非叶子节点开始,依次调整每个节点,使其满足堆的性质。
  2. 堆的调整:在交换堆顶元素后,需要对剩余元素重新调整为大顶堆。

  • 应用
  1. 优先队列的实现:堆排序的思想可用于实现优先队列,优先队列中插入和删除操作都能保持高效。例如任务调度系统中,根据任务的优先级进行调度。
  2. 海量数据中找 Top K 元素:可以使用堆排序在海量数据中找出最大或最小的 K 个元素。

示例代码

java

public class HeapSort {
    public static void heapSort(int[] arr) {
        int n = arr.length;

        // 构建大顶堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 一个个交换元素
        for (int i = n - 1; i > 0; i--) {
            swap(arr, 0, i);
            heapify(arr, i, 0);
        }
    }

    private static void heapify(int[] arr, int n, int i) {
        int largest = i;
        int left = 2 * i + 1;
        int right = 2 * i + 2;

        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        if (largest != i) {
            swap(arr, i, largest);
            heapify(arr, n, largest);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

4. 什么是归并排序

  • 定义

归并排序(Merge Sort)是建立在归并操作上的一种有效的排序算法,该算法采用分治法(Divide and Conquer)。它将一个数组分成两个子数组,分别对这两个子数组进行归并排序,然后将排好序的子数组合并成一个有序的数组。

  • 要点
  1. 分割数组:不断将数组分成两半,直到每个子数组只有一个元素。
  2. 合并子数组:将两个有序的子数组合并成一个有序的数组,合并过程需要额外的存储空间。

  • 应用
  1. 外部排序:处理大规模数据时,当数据无法一次性加载到内存中,可将数据分成多个小块,分别排序后再合并。例如对大型文件进行排序。
  2. 对排序稳定性有要求的场景:归并排序是稳定的排序算法,当需要保证相等元素的相对顺序不变时可以使用。

示例代码

java

public class MergeSort {
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            mergeSort(arr, left, mid);
            mergeSort(arr, mid + 1, right);
            merge(arr, left, mid, right);
        }
    }

    private static void merge(int[] arr, int left, int mid, int right) {
        int n1 = mid - left + 1;
        int n2 = right - mid;

        int[] L = new int[n1];
        int[] R = new int[n2];

        for (int i = 0; i < n1; i++) {
            L[i] = arr[left + i];
        }
        for (int j = 0; j < n2; j++) {
            R[j] = arr[mid + 1 + j];
        }

        int i = 0, j = 0, k = left;
        while (i < n1 && j < n2) {
            if (L[i] <= R[j]) {
                arr[k] = L[i];
                i++;
            } else {
                arr[k] = R[j];
                j++;
            }
            k++;
        }

        while (i < n1) {
            arr[k] = L[i];
            i++;
            k++;
        }

        while (j < n2) {
            arr[k] = R[j];
            j++;
            k++;
        }
    }
}

5. 什么是基数排序

  • 定义

基数排序(Radix Sort)是一种非比较排序算法,它根据数字的每一位来进行排序。从最低位开始,依次对每一位进行排序,直到最高位。通过多次排序,最终使整个数组有序。

  • 要点
  1. 按位排序:从最低位到最高位,依次对每一位进行排序。
  2. 使用桶:通常使用 10 个桶来存储每一位上的数字。

  • 应用
  1. 整数排序:对于整数数据,基数排序能高效完成排序,尤其在数据范围不大且位数固定的情况下。
  2. 字符串排序:可以按照字符的 ASCII 码值进行排序,常用于对字符串数组进行排序。

示例代码

java

import java.util.Arrays;

public class RadixSort {
    public static void radixSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        int max = getMax(arr);

        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSort(arr, exp);
        }
    }

    private static int getMax(int[] arr) {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        return max;
    }

    private static void countingSort(int[] arr, int exp) {
        int n = arr.length;
        int[] output = new int[n];
        int[] count = new int[10];

        Arrays.fill(count, 0);

        for (int i = 0; i < n; i++) {
            count[(arr[i] / exp) % 10]++;
        }

        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }

        for (int i = n - 1; i >= 0; i--) {
            output[count[(arr[i] / exp) % 10] - 1] = arr[i];
            count[(arr[i] / exp) % 10]--;
        }

        for (int i = 0; i < n; i++) {
            arr[i] = output[i];
        }
    }
}

6. 什么是二分查找树

  • 定义

二分查找树(Binary Search Tree,BST)是一种二叉树,对于树中的每个节点,其左子树中的所有节点的值都小于该节点的值,右子树中的所有节点的值都大于该节点的值。利用这个性质,可以在 O(logn) 的平均时间复杂度内进行查找、插入和删除操作。

  • 要点
  1. 节点比较:插入、查找和删除操作都基于节点值的比较。
  2. 递归操作:大多数操作都可以通过递归的方式实现。

  • 应用
  1. 字典和集合的实现:可以用于实现字典和集合等数据结构,提供高效的查找、插入和删除操作。
  2. 数据库索引:在数据库中,二分查找树可以作为索引结构,提高数据的查询效率。

示例代码

java

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }
}

public class BinarySearchTree {
    private TreeNode root;

    public void insert(int val) {
        root = insertRec(root, val);
    }

    private TreeNode insertRec(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }

        if (val < root.val) {
            root.left = insertRec(root.left, val);
        } else if (val > root.val) {
            root.right = insertRec(root.right, val);
        }

        return root;
    }

    public boolean search(int val) {
        return searchRec(root, val);
    }

    private boolean searchRec(TreeNode root, int val) {
        if (root == null || root.val == val) {
            return root != null;
        }

        if (val < root.val) {
            return searchRec(root.left, val);
        }

        return searchRec(root.right, val);
    }
}

7. 什么是 LRU 实现

  • 定义

LRU(Least Recently Used)即最近最少使用,是一种缓存淘汰策略。当缓存满时,会优先淘汰最近最少使用的数据。通常使用哈希表和双向链表来实现,哈希表用于快速查找数据,双向链表用于维护数据的访问顺序。

  • 要点
  1. 哈希表:存储键值对,实现 O(1) 的查找时间复杂度。
  2. 双向链表:维护数据的访问顺序,最近访问的数据放在链表头部,最久未访问的数据放在链表尾部。

  • 应用
  1. 操作系统的页面置换算法:当物理内存不足时,操作系统可以使用 LRU 算法淘汰最近最少使用的页面。
  2. 数据库的缓存管理:数据库系统可以使用 LRU 算法管理缓存,提高数据的访问效率。

示例代码

java

import java.util.HashMap;
import java.util.Map;

class DLinkedNode {
    int key;
    int value;
    DLinkedNode prev;
    DLinkedNode next;

    public DLinkedNode() {}

    public DLinkedNode(int key, int value) {
        this.key = key;
        this.value = value;
    }
}

public class LRUCache {
    private Map<Integer, DLinkedNode> cache = new HashMap<>();
    private int size;
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            DLinkedNode newNode = new DLinkedNode(key, value);
            cache.put(key, newNode);
            addToHead(newNode);
            size++;
            if (size > capacity) {
                DLinkedNode removed = removeTail();
                cache.remove(removed.key);
                size--;
            }
        } else {
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DLinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(DLinkedNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {
        DLinkedNode removed = tail.prev;
        removeNode(removed);
        return removed;
    }
}

8. 判断一个数是不是整数

  • 定义

判断一个数是否为整数,就是确定该数是否没有小数部分,或者是否属于 intlong 等整数类型。

  • 要点
  1. 类型判断:使用 instanceof 关键字判断对象是否为整数类型。
  2. 小数部分判断:对于浮点数,通过取整后与原数比较来判断是否有小数部分。

  • 应用
  1. 数据验证:在进行数据处理时,需要验证输入的数据是否为整数,例如在用户输入年龄、数量等信息时。
  2. 数学计算:在某些数学计算中,需要确保操作数为整数,避免出现意外的结果。

示例代码

java

public class IntegerCheck {
    public static boolean isInteger(double num) {
        return num == (int) num;
    }

    public static boolean isInteger(Object obj) {
        return obj instanceof Integer;
    }
}

9. 红黑树与二叉树有什么区别、红黑树用途

  • 定义
  1. 红黑树:是一种自平衡的二叉搜索树,每个节点都带有颜色属性(红色或黑色)。通过对节点颜色的约束和旋转操作,保证树的高度始终保持在 O(logn),从而保证插入、删除和查找操作的时间复杂度都是 O(logn)。
  2. 二叉树:是一种树形数据结构,每个节点最多有两个子节点,但不保证树的平衡性,最坏情况下,插入、删除和查找操作的时间复杂度可能达到 O(n)。

  • 要点
  1. 红黑树的性质:根节点是黑色;每个叶子节点(NIL 节点,空节点)是黑色;如果一个节点是红色的,则它的子节点必须是黑色的;对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
  2. 平衡维护:红黑树通过旋转和颜色调整来维护平衡,而普通二叉树不进行平衡维护。

  • 应用
  1. Java 集合框架TreeMapTreeSet 底层使用红黑树实现,保证元素的有序性和高效的插入、删除和查找操作。
  2. 操作系统:用于实现文件系统的目录结构,提高查找效率。

10. 什么是 AVL 树,有哪四种旋转方式,举个 AVL 树左右旋转的例子

  • 定义

AVL 树是一种自平衡的二叉搜索树,它在每个节点中维护一个平衡因子(该节点的左子树高度减去右子树高度),平衡因子的取值只能是 -1、0 或 1。当插入或删除节点导致某个节点的平衡因子超出这个范围时,需要通过旋转操作来恢复树的平衡。

  • 四种旋转方式
  1. 左旋转(Left Rotation):用于处理右右失衡的情况,将一个节点的右子树提升为根节点,原根节点变为新根节点的左子树。
  2. 右旋转(Right Rotation):用于处理左左失衡的情况,将一个节点的左子树提升为根节点,原根节点变为新根节点的右子树。
  3. 左右旋转(Left - Right Rotation):用于处理左右失衡的情况,先对左子树进行左旋转,再对根节点进行右旋转。
  4. 右左旋转(Right - Left Rotation):用于处理右左失衡的情况,先对右子树进行右旋转,再对根节点进行左旋转。

  • 应用
  1. 数据库索引:AVL 树能保证高效的查找、插入和删除操作,可用于数据库的索引结构,提高数据的查询效率。
  2. 编译器的符号表:在编译器中,AVL 树可以用于实现符号表,快速查找和管理标识符。

  • 左右旋转例子

假设我们有一个 AVL 树,插入一个节点后导致根节点的左子树的右子树高度增加,出现左右失衡的情况。

初始 AVL 树:

plaintext

    5
   /
  3
   \
    4

插入节点 2 后,树变为:

plaintext

    5
   /
  3
 / \
2   4

此时根节点 5 的平衡因子为 2,左子树 3 的平衡因子为 -1,出现左右失衡。

首先对左子树 3 进行左旋转:

plaintext

    5
   /
  4
 /
3
 \
  2

然后对根节点 5 进行右旋转:

plaintext

    4
   / \
  3   5
 /
2

经过左右旋转后,树恢复平衡。

示例代码

java

class AVLTreeNode {
    int val;
    int height;
    AVLTreeNode left;
    AVLTreeNode right;

    AVLTreeNode(int val) {
        this.val = val;
        this.height = 1;
    }
}

public class AVLTree {
    private AVLTreeNode root;

    private int height(AVLTreeNode node) {
        if (node == null) {
            return 0;
        }
        return node.height;
    }

    private int getBalance(AVLTreeNode node) {
        if (node == null) {
            return 0;
        }
        return height(node.left) - height(node.right);
    }

    private AVLTreeNode rightRotate(AVLTreeNode y) {
        AVLTreeNode x = y.left;
        AVLTreeNode T2 = x.right;

        x.right = y;
        y.left = T2;

        y.height = Math.max(height(y.left), height(y.right)) + 1;
        x.height = Math.max(height(x.left), height(x.right)) + 1;

        return x;
    }

    private AVLTreeNode leftRotate(AVLTreeNode x) {
        AVLTreeNode y = x.right;
        AVLTreeNode T2 = y.left;

        y.left = x;
        x.right = T2;

        x.height = Math.max(height(x.left), height(x.right)) + 1;
        y.height = Math.max(height(y.left), height(y.right)) + 1;

        return y;
    }

    public AVLTreeNode insert(AVLTreeNode node, int val) {
        if (node == null) {
            return new AVLTreeNode(val);
        }

        if (val < node.val) {
            node.left = insert(node.left, val);
        } else if (val > node.val) {
            node.right = insert(node.right, val);
        } else {
            return node;
        }

        node.height = 1 + Math.max(height(node.left), height(node.right));

        int balance = getBalance(node);

        // 左左情况
        if (balance > 1 && val < node.left.val) {
            return rightRotate(node);
        }

        // 右右情况
        if (balance < -1 && val > node.right.val) {
            return leftRotate(node);
        }

        // 左右情况
        if (balance > 1 && val > node.left.val) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 右左情况
        if (balance < -1 && val < node.right.val) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        return node;
    }
}

友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读

https://download.youkuaiyun.com/download/ylfhpy/90553604

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值