Skip to content

算法与设计模式


课程目标

  • 帮助建立算法与设计模式的联系
  • 理解常见算法背后的设计思想,并能在实际开发中灵活运用。

学习价值

  • 是通过大厂面试的必备硬核能力
  • 是升职加薪必备的基础能力
  • 是成为高阶测试开发工程师的必经之路

招聘需求

  • 阿里巴巴招聘需求:
    • 薪资: 20K+
    • 要求: 具备很强的逻辑思维能力和较高的分析、处理问题的能力
  • 字节测试开发实习生招聘需求:
    • 具备较强的逻辑思维能力、沟通协作能力和学习能力


算法的学习路径


实战案例


查找算法


排序


设计模式


设计模式原则


设计模式分类

  • 创建型设计模式共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型设计模式共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型设计模式共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式的使用时机

  • 但代码将要成为“狗屎码”时,请使用设计模式。
    • 重复代码:多个地方出现相似逻辑 → 考虑模板方法模式或策略模式。
    • 过长的分支逻辑:多个分支逻辑 → 考虑状态模式、策略模式或工厂模式。
    • 类臃肿:一个类承担过多职责 → 装饰器模式或职责链模式拆分功能。
  • 需要解耦的时候
    • 组件间强依赖:希望减少直接依赖 → 观察者模式(事件通知)、中介者模式(通过中介交互)
    • 创建对象依赖具体类:需要隔离对象创建 → 抽象工厂模式或工厂方法模式。
  • 需要提升扩展性时
    • 未来可能新增功能:通过扩展而非修改代码实现 → 装饰器模式(动态添加功能)、访问者模式(新增操作)。
    • 支持多种算法或行为:运行时切换 → 策略模式。
    • 需要动态组合对象:如树形结构 → 组合模式。
  • 性能或资源管理需求
  • 系统复杂度升高时
  • 接口兼容性问题

何时避免设计模式?

  • 问题足够简单:直接编码比套用模式更清晰。
  • 过度设计风险:未来需求不明确时,避免提前引入复杂模式。
  • 团队理解成本:模式增加理解难度,需权衡可维护性。

设计模式案例


需要动态切换排序-策略模式

完整代码实现

python代码

import random
from abc import ABC, abstractmethod  # 引入抽象基类模块
import time  # 引入时间模块用于性能测试

# 抽象策略接口 SortStrategy
class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data: list) -> tuple[list, float]:
        """子类必须实现的方法"""
        pass


# 冒泡排序策略实现
class BubbleSort(SortStrategy):
    def sort(self, data):
        """子类实现冒泡排序"""
        n = len(data)  # 数据长度
        for i in range(n):
            for j in range(0, n - i - 1):
                # 比较相邻元素并交换
                if data[j] > data[j + 1]:
                    data[j], data[j + 1] = data[j + 1], data[j]
        return data  # 返回排序后的数据列表


# 快速排序策略实现
class QuickSort(SortStrategy):
    def sort(self, data):
        """子类实现快速排序"""
        # 如果数据长度小于等于1,则返回原数据(递归终止条件)
        if len(data) <= 1:
            return data
        # 选择中间位置的元素作为基准点
        pivot = data[len(data) // 2]
        # 分割数据为三个部分:
        left = [x for x in data if x < pivot]   # 左侧元素小于基准点
        middle = [x for x in data if x == pivot]  # 中间元素等于基准点
        right = [x for x in data if x > pivot]  # 右侧元素大于基准点
        # 递归调用排序,并将结果合并
        return self.sort(left) + middle + self.sort(right)


# 策略模式上下文类 Sorter
class Sorter:
    def __init__(self, strategy: SortStrategy):
        """构造函数指定策略"""
        self._strategy = strategy  # 初始化策略

    def execute(self, data):
        return self._strategy.sort(data.copy())  # 执行策略排序方法,传入数据副本防止修改原始数据


# 性能测试函数
def time_sort(func, data):
    start = time.time()  # 记录开始时间
    func(data)  # 调用传入的函数
    return time.time() - start  # 返回耗时


if __name__ == '__main__':
    # 测试数据:生成10000个随机数
    data = [random.randint(0, 10000) for _ in range(10000)]
    # 创建Sorter对象并执行排序
    sorter = Sorter(QuickSort())  # 使用快速排序策略初始化Sorter
    # 执行快排测试性能
    print(time_sort(sorter.execute, data))  # 输出排序耗时
    # 更换策略
    sorter._strategy = BubbleSort()  # 将策略更换为冒泡排序
    # 执行冒泡排序测试性能
    print(time_sort(sorter.execute, data))  # 输出排序耗时

java 代码

import java.util.ArrayList;
import java.util.List;

// 定义排序策略的公共接口,所有具体排序算法都需要实现该接口
interface SortStrategy {
    int[] sort(int[] data); // 排序方法,接收整型数组并返回排序后的结果
}
// 实现冒泡排序的具体策略类
class BubbleSort implements SortStrategy {
    public int[] sort(int[] data) {
        System.out.println("执行冒泡排序"); // 打印日志用于调试
        int n = data.length; // 获取数组长度
        // 双层循环进行相邻元素比较与交换
        for (int i = 0; i < n-1; i++) {
            for (int j = 0; j < n-i-1; j++) {
                if (data[j] > data[j+1]) {  
                    // 如果前一个元素比后一个大,则交换
                    int temp = data[j];
                    data[j] = data[j+1];
                    data[j+1] = temp;
                }
            }
        }
        return data; // 返回排序后的数组
    }
}

// 实现快速排序的具体策略类
class QuickSort implements SortStrategy {
    public int[] sort(int[] data) {
        System.out.println("执行快速排序"); // 打印日志用于调试
        quickSort(data, 0, data.length-1);  // 调用递归快排函数
        return data; // 返回排序后的数组
    }

    // 快速排序主逻辑
    private void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high); // 分区操作获取基准点位置
            quickSort(arr, low, pi-1); // 对左半部分递归排序
            quickSort(arr, pi+1, high); // 对右半部分递归排序
        }
    }

    // 交换两个数组元素的方法
    private void swap(int[] list, int i,int j){
        // 交换操作
        int temp = list[i]; 
        list[i] = list[j];
        list[j] = temp;
    }

    // 分区操作:将小于基准值的放在左边,大于基准值的放在右边
    private int partition(int[] arr, int low, int high) {
        int pivot = arr[high];  // 选取最右端元素作为基准
        int i = low-1;  // 指针i记录小于pivot的边界
        for (int j=low; j<high; j++) {
            if (arr[j] < pivot) { // 当前元素小于基准值时
                i++;
                swap(arr, i, j); // 交换到i的位置
            }
        }
        swap(arr, i+1, high);  // 将基准值放到正确位置
        return i+1; // 返回基准值索引
    }
}

// 上下文类,负责持有当前排序策略并调用其排序方法
class Sorter {
    private SortStrategy strategy;

    // 构造函数设置初始策略
    public Sorter(SortStrategy strategy) {
        this.strategy = strategy;
    }

    // 执行排序操作,对原始数据进行克隆避免修改原数据
    public int[] execute(int[] data) {
        return strategy.sort(data.clone());
    }

    // 动态更换排序策略
    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }
}

// 提供时间测量功能的工具类
class TimeSort {
    // 接收Runnable对象,计算其执行耗时(单位:秒)
    public static double timeSort(Runnable func) {
        long start = System.currentTimeMillis();  // 记录开始时间
        func.run(); // 执行传入的函数
        return (System.currentTimeMillis() - start) / 1000.0;  // 返回耗时(秒)
    }
}

// 主程序
public class DemoJava {
    public static void main(String[] args) {
        // 随机产生10000个随机数
        List<Integer> randomNumbers = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            randomNumbers.add((int)(Math.random() * 10000));
        }
        // 将List转换为int数组
        int[] data = randomNumbers.stream().mapToInt(Integer::intValue).toArray();
//        System.out.println("原始数据:" + java.util.Arrays.toString(data));

        // 创建使用快速排序的Sorter实例,并测试性能
        Sorter sorter = new Sorter(new QuickSort());
        double quickSortTime = TimeSort.timeSort(() -> sorter.execute(data));
//        System.out.println("排序后的数据:" + java.util.Arrays.toString(sorter.execute(data)));
        System.out.println("QuickSort took: " + quickSortTime + " seconds");

        // 更换为冒泡排序策略,并再次测试性能
        sorter.setStrategy(new BubbleSort());
        double bubbleSortTime = TimeSort.timeSort(() -> sorter.execute(data));
//        System.out.println("排序后的数据:" + java.util.Arrays.toString(sorter.execute(data)));
        System.out.println("BubbleSort took: " + bubbleSortTime + " seconds");
    }
}

流程固化-模板方法模式

完整代码实现

python代码

import random
from abc import ABC, abstractmethod


class SortTemplate(ABC):
    def sort_and_print(self, data):
        """模板方法(固定流程)"""
        print("=== 开始排序流程 ===")
        self.pre_process(data)  # 钩子方法
        self.do_sort(data)  # 抽象方法
        self.validate(data)  # 默认验证
        self.print_result(data)  # 固定输出
        print("=== 流程结束 ===\n")

    @abstractmethod
    def do_sort(self, data):
        """子类必须实现的排序算法"""
        pass

    def pre_process(self, data):
        """钩子方法:预处理(可选覆盖)"""
        print(f"[默认预处理] 数据长度: {len(data)}")

    def validate(self, data):
        """默认方法:验证是否有序"""
        for i in range(len(data) - 1):
            if data[i] > data[i + 1]:
                raise RuntimeError("排序结果验证失败!")
        print("[验证通过] 数组已有序")

    def print_result(self, data):
        """固定方法:打印结果"""
        print("排序结果:", " ".join(map(str, data)))


class QuickSort(SortTemplate):
    def do_sort(self, data):
        """快速排序(原地修改)"""
        print("[算法] 快速排序")
        # 调用递归快速排序
        self._quick_sort(data, 0, len(data) - 1)

    def _quick_sort(self, data, low, high):
        """递归快速排序"""
        # 如果low >= high,则说明数组已排序完毕
        if low < high:
            pi = self._partition(data, low, high) # 分区操作获取基准点位置
            self._quick_sort(data, low, pi - 1) # 对左半部分递归排序
            self._quick_sort(data, pi + 1, high) # 对右半部分递归排序

    def _partition(self, data, low, high):
        """分区操作:将小于基准值的放在左边,大于基准值的放在右边"""
        pivot = data[high] # 选取最右端元素作为基准
        i = low - 1 # 指针i记录小于pivot的边界
        for j in range(low, high): # 遍历数组
            if data[j] < pivot: # 当前元素小于基准值时
                i += 1 # 移动指针i
                data[i], data[j] = data[j], data[i] # 交换到i的位置
        data[i + 1], data[high] = data[high], data[i + 1] # 将基准值放到正确位置
        return i + 1 # 返回基准值索引

    def pre_process(self, data):
        """覆盖预处理钩子方法"""
        print(f"[快速排序预处理] 检测到 {len(data)} 个元素")


class BubbleSort(SortTemplate):
    def do_sort(self, data):
        """冒泡排序实现"""
        print("[算法] 冒泡排序")
        # 调用冒泡排序实现
        self._bubble_sort(data)

    def _bubble_sort(self, data):
        """冒泡排序实现"""  
        for i in range(len(data) - 1): # 外层循环控制排序轮数,共 len(data) - 1 轮(n个元素最多需要n-1次完全比较)
            # 内层循环控制每一轮的比较次数,随着i递增,已排好序的元素不再参与比较
            # 每次遍历将当前未排序部分中最大的元素“冒泡”到数组末尾  
            for j in range(len(data) - 1 - i):
                # 比较相邻元素,如果前一个元素大于后一个元素,则交换位置
                if data[j] > data[j + 1]: 
                    data[j], data[j + 1] = data[j + 1], data[j]

    def pre_process(self, data):
        """覆盖预处理钩子方法"""
        print(f"[冒泡排序预处理] 检测到 {len(data)} 个元素")


if __name__ == '__main__':
    data = [random.randint(1, 100) for i in range(100)]
    QuickSort().sort_and_print(data)
    BubbleSort().sort_and_print(data)

java 代码实现

// SortTemplate.java
// 定义排序模板抽象类,封装排序流程框架
abstract class SortTemplate {

    // 模板方法(final防止子类修改流程)
    public final void sortAndPrint(int[] data) {
        System.out.println("=== 开始排序流程 ===");
        preProcess(data);       // 钩子方法(可选预处理)
        doSort(data);           // 抽象方法(子类实现排序算法)
        validate(data);        // 默认结果验证
        printResult(data);      // 固定输出逻辑
        System.out.println("=== 流程结束 ===\n");
    }

    // 抽象方法:子类必须实现的排序算法
    protected abstract void doSort(int[] data);

    // 钩子方法:预处理(子类可选覆盖)默认打印数据长度
    protected void preProcess(int[] data) {
        System.out.println("[默认预处理] 数据长度: " + data.length);
    }

    // 默认方法:验证数组是否有序(升序)
    protected void validate(int[] data) {
        for (int i = 0; i < data.length - 1; i++) {
            if (data[i] > data[i + 1]) {
                throw new RuntimeException("排序结果验证失败!");
            }
        }
        System.out.println("[验证通过] 数组已有序");
    }

    // 固定方法:打印结果
    private void printResult(int[] data) {
        System.out.print("排序结果: ");
        for (int num : data) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

//BubbleSort.java
// 冒泡排序类,继承排序模板类
public class BubbleSort extends SortTemplate {
    // 实现抽象方法:定义冒泡排序的具体算法
    @Override
    protected void doSort(int[] data) {
        System.out.println("[算法] 冒泡排序");
        bubbleSort(data);
    }

    private void bubbleSort(int[] data) {
        // 外层循环控制排序轮数,共 data.length - 1 轮
        for (int i = 0; i < data.length - 1; i++) {
            // 内层循环控制每一轮的比较次数,随着i递增,已排好序的元素不再参与比较
            for (int j = 0; j < data.length - 1 - i; j++) {
                // 比较相邻元素,如果前元素大于后元素,则交换位置
                if (data[j] > data[j + 1]) {
                    swap(data, j, j + 1);  // 交换相邻元素
                }
            }
        }
    }

    // 元素交换方法
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 覆盖预处理钩子方法,提供冒泡排序专用的预处理信息
    @Override
    protected void preProcess(int[] data) {
        System.out.println("[冒泡排序预处理] 检测到 " + data.length + " 个元素");
    }
}

// QuickSort.java
// 快速排序类,继承排序模板类
class QuickSort extends SortTemplate {
     // 实现抽象方法:定义快速排序的具体算法
    @Override
    protected void doSort(int[] data) {
        System.out.println("[算法] 快速排序");
        quickSort(data, 0, data.length - 1);
    }

    // 快速排序主逻辑
    private void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pivot = partition(arr, low, high); // 获取基准点位置
            quickSort(arr, low, pivot - 1); // 对左半部分递归排序
            quickSort(arr, pivot + 1, high); // 对右半部分递归排序
        }
    }

    // 分区函数:将小于基准值的放左边,大于基准值的放右边
    private 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 void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 覆盖预处理钩子方法,提供快速排序专用的预处理信息
    @Override
    protected void preProcess(int[] data) {
        System.out.println("[快速排序预处理] 检测到 " + data.length + " 个元素");
    }
}

// Main.java
// 主程序入口
public class Main {
    public static void main(String[] args) {
        int[] data = new int[]{5, 4, 3, 2, 1}; // 初始化测试数据
        // 创建冒泡排序实例
        SortTemplate bubbleSort = new BubbleSort();
        // 创建快速排序实例
        SortTemplate quickSort = new QuickSort();
        // 执行排序、输出全过程
        bubbleSort.sortAndPrint(data);
        quickSort.sortAndPrint(data);
    }
}

总结

  • 关键算法的掌握
    • 查找算法:顺序查找(O(n)) vs 二分查找(O(log n))
    • 排序算法:冒泡排序(O(n²)) vs 快速排序(O(n log n))
    • 递归思想:分治策略的应用
  • 设计模式的应用
    • 策略模式:动态切换算法实现
    • 模板方法:固化算法流程框架
    • 遵循开闭原则:对扩展开放,对修改关闭