前置知识:无

建议:不要跳过,非常重要的自我验证技巧

对数器的适用场景

你在网上找到了某个公司的面试题,你想了好久,感觉自己会做,但是你找不到在线测试

你和朋友交流面试题,你想了好久,感觉自己会做,但是你找不到在线测试

你在网上做笔试,但是前几个测试用例都过了,突然一个巨大无比数据量来了,结果你的代码报错了,如此大的数据量根本看不出哪错了,甚至有的根本不提示哪个例子错了,怎么 debug?

对数器的实现

  1. 你想要测的方法 a(最优解)
  2. 实现复杂度不好但是容易实现的方法 b(暴力解)
  3. 实现一个随机样本产生器(长度也随机、值也随机)
  4. 把方法 a 和方法 b 跑相同的输入样本,看看得到的结果是否一样
  5. 如果有一个随机样本使得比对结果不一致,打印这个出错的样本进行人工干预,改对方法 a 和方法 b
  6. 当样本数量很多时比对测试依然正确,可以确定方法 a(最优解)已经正确

关键是第 5 步,找到一个数据量小的错误样本,便于你去带入 debug

然后把错误例子带入代码一步一步排查(Print 大法、断点技术都可以)

对数器的门槛其实是比较高的,因为往往需要在两种不同思路下实现功能相同的两个方法,暴力一个、想象中的最优解是另一个

以后的很多题目都会用到对数器,几乎可以验证任何方法,尤其在验证贪心、观察规律方面很有用

到时候会丰富很多对数器的实战用法,这里只是一个简单易懂的示例

对数器验证一下

用对数器验证一下 上节课 写的“三傻排序”的正确性

package main
 
import (
  "fmt"
  "math/rand"
  "sort"
)
 
const (
  N          = 200   // 随机数组最大长度
  MIN        = -1000 // 随机数组每个值,在 [MIN, MAX) 之间等概率随机
  MAX        = 1001
  TEST_TIMES = 50000 // 测试次数
)
 
func main() {
  if testSort(sort.Ints, selectionSort) {
    fmt.Println("选择排序正确")
  } else {
    fmt.Println("选择排序错误")
  }
  fmt.Println()
 
  if testSort(sort.Ints, bubbleSort) {
    fmt.Println("冒泡排序正确")
  } else {
    fmt.Println("冒泡排序错误")
  }
  fmt.Println()
 
  if testSort(sort.Ints, insertionSort) {
    fmt.Println("插入排序正确")
  } else {
    fmt.Println("插入排序错误")
  }
}
 
// 对数器:生成随机样本,测试 方法一 和 方法二 的结果是否相同
func testSort(fn1, fn2 func([]int)) bool {
  ok := true
  for i := 0; i < TEST_TIMES; i++ {
    arr := generateRandomArray()
    copiedArr := copyArray(arr)
    fn1(arr)
    fn2(copiedArr)
    for i := 0; i < len(arr); i++ {
      if arr[i] != copiedArr[i] {
        ok = false
        break
      }
    }
  }
  return ok
}
 
// 生成一个随机 int 数组,数组最大长度为 N,数组中每个数,都在 [MIN, MAX) 之间
func generateRandomArray() []int {
  length := generateRandomInt(0, N+1)
  arr := make([]int, length)
  for i := 0; i < length; i++ {
    arr[i] = generateRandomInt(MIN, MAX)
  }
  return arr
}
 
// 拷贝数组
func copyArray(arr []int) []int {
  copiedArr := make([]int, len(arr))
  copy(copiedArr, arr)
  return copiedArr
}
 
// 生成一个随机整数,[min, max) 范围
func generateRandomInt(min, max int) int {
  // go 1.20 废弃,不写自动使用 globalRand 生成随机数
  // rand.Seed(time.Now().UnixNano())
 
  // rand.Intn(n):等概率返回 [0, n) 的 int
  // 若传入的 n 为负数,则 panic
  return rand.Intn(max-min) + min
}
 
// ------------------------------
 
// 选择排序
func selectionSort(arr []int) {
  n := len(arr)
  for i := 0; i < n; i++ { // i ~ n - 1 找最小值
    minIdx := i
    for j := i + 1; j < n; j++ {
      if arr[j] < arr[minIdx] {
        minIdx = j
      }
    }
    arr[minIdx], arr[i] = arr[i], arr[minIdx] // 最小值放到 i 位置
  }
}
 
// 冒泡排序
func bubbleSort(arr []int) {
  n := len(arr)
  for i := n - 1; i > 0; i-- {
    for j := 0; j < i; j++ { // 0 ~ i 范围上,相邻位置较大的数往后移
      if arr[j] > arr[j+1] {
        arr[j], arr[j+1] = arr[j+1], arr[j]
      }
    }
  }
}
 
// 插入排序
func insertionSort(arr []int) {
  n := len(arr)
  for i := 1; i < n; i++ {
    for j := i; j > 0; j-- {
      if arr[j] < arr[j-1] {
        arr[j], arr[j-1] = arr[j-1], arr[j]
      } else {
        break
      }
    }
  }
}
选择排序正确

冒泡排序正确

插入排序正确

当有错时,打印是什么例子出错的,各自排序成了什么样,可能要把例子带入每个方法,去 debug!