缘起

最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)
本系列笔记拟采用golang练习之

欧几里得算法

  1. 欧几里得算法(又称辗转相除法)用于计算两个数的最大公约数,
  2. 被称为世界上最古老的算法。
  3. 现在人们已无法确定该算法具体的提出时间,
  4. 但其最早被发现记载于公元前300年欧几里得的著作中,
  5. 因此得以命名。
  6. 首先用较小的数字去除较大的数字,求出余数。
  7. 接下来再用较小的除数和余数进行mod运算,
  8. 重复同样的操作,
  9. 余数为0时,最后一次运算中的除数就是最大公约数。
  10. 摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一

image.png

目标

  • 分别用因式分解法和欧几里德算法求解若干随机整数的最大公约数, 并相互验证

设计

  • IGCDCalculator: 最大公约数计算器接口
  • tEuclideanCalculator: 欧几里德算法实现最大公约数求解
  • tNormalGcdCalculator: 因式分解法实现最大公约数求解

单元测试

euclidean_gcd_test.go, 对比验证欧几里德算法和因式分解法, 并比较计算效率

  1. package others
  2. import (
  3. "learning/gooop/others/euclidean"
  4. "math/rand"
  5. "testing"
  6. "time"
  7. )
  8. func TestEuclideanGCD(t *testing.T) {
  9. fnAssertTrue := func(b bool, msg string) {
  10. if !b {
  11. t.Fatal(msg)
  12. }
  13. }
  14. rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
  15. sampleCount := 100
  16. samples := make([]int, sampleCount)
  17. for i,_ := range samples {
  18. samples[i] = rnd.Intn(sampleCount) + 1
  19. }
  20. fnGenInt := func() int {
  21. n := rnd.Intn(5) + 1
  22. x := 1
  23. for i := 0;i < n;i++ {
  24. j := rnd.Intn(sampleCount)
  25. x *= samples[j]
  26. }
  27. return x
  28. }
  29. c1 := euclidean.EuclideanGCDCalculator
  30. c2 := euclidean.NormalGCDCalculator
  31. t.Log("testing 10 samples")
  32. for i := 0;i < 10;i++ {
  33. a,b := fnGenInt(), fnGenInt()
  34. g1 := c1.Calc(a, b)
  35. g2 := c2.Calc(a, b)
  36. //t.Logf("a=%v, b=%v, g1=%v, g2=%v", a, b, g1, g2)
  37. fnAssertTrue(g1 == g2, "expecting g1 == g2")
  38. fnAssertTrue(a % g1 == 0, "expecting a % gcd == 0")
  39. fnAssertTrue(b % g1 == 0, "expecting b % gcd == 0")
  40. t.Logf("gcd(%v, %v) = %v", a, b, g1)
  41. }
  42. t.Log("pass testing 10 samples")
  43. t.Log("\ntesting 100_000 samples")
  44. for i := 0;i < 100_000;i++ {
  45. a,b := fnGenInt(), fnGenInt()
  46. g1 := c1.Calc(a, b)
  47. g2 := c2.Calc(a, b)
  48. fnAssertTrue(g1 == g2, "expecting g1 == g2")
  49. fnAssertTrue(a % g1 == 0, "expecting a % gcd == 0")
  50. fnAssertTrue(b % g1 == 0, "expecting b % gcd == 0")
  51. }
  52. t.Log("pass testing 100_000 samples")
  53. fnTestCost := func(samples[][] int, c euclidean.IGCDCalculator) int64 {
  54. t0 := time.Now().UnixNano()
  55. for i, size := 0, len(samples);i < size;i++ {
  56. a, b := samples[i][0], samples[i][1]
  57. g1 := c.Calc(a, b)
  58. fnAssertTrue(a%g1 == 0, "expecting a % gcd == 0")
  59. fnAssertTrue(b%g1 == 0, "expecting b % gcd == 0")
  60. }
  61. cost := (time.Now().UnixNano() - t0) / 1000_000
  62. return cost
  63. }
  64. pairs := make([][]int, 10_000)
  65. for i,size := 0, len(pairs);i < size;i++ {
  66. pairs[i] = []int{ fnGenInt(), fnGenInt() }
  67. }
  68. t.Logf("testing 10_000 samples using EuclideanGCDCalculator, cost=%v ms", fnTestCost(pairs, c1))
  69. t.Logf("testing 10_000 samples using NormalGCDCalculator, cost=%v ms", fnTestCost(pairs, c2))
  70. }

测试输出

显而易见, 欧几里德算法要快上N个数量级

  1. $ go test -v euclidean_gcd_test.go
  2. === RUN TestEuclideanGCD
  3. euclidean_gcd_test.go:37: testing 10 samples
  4. euclidean_gcd_test.go:47: gcd(122262, 2135280) = 1722
  5. euclidean_gcd_test.go:47: gcd(2563600, 180180) = 260
  6. euclidean_gcd_test.go:47: gcd(5, 2019600) = 5
  7. euclidean_gcd_test.go:47: gcd(78540, 1547) = 119
  8. euclidean_gcd_test.go:47: gcd(17476560, 749800800) = 563760
  9. euclidean_gcd_test.go:47: gcd(395600, 12792) = 8
  10. euclidean_gcd_test.go:47: gcd(21, 165) = 3
  11. euclidean_gcd_test.go:47: gcd(7056, 2257) = 1
  12. euclidean_gcd_test.go:47: gcd(90, 918) = 18
  13. euclidean_gcd_test.go:47: gcd(90843648, 2522520) = 1176
  14. euclidean_gcd_test.go:49: pass testing 10 samples
  15. euclidean_gcd_test.go:51:
  16. testing 100_000 samples
  17. euclidean_gcd_test.go:61: pass testing 100_000 samples
  18. euclidean_gcd_test.go:80: testing 10_000 samples using EuclideanGCDCalculator, cost=1 ms
  19. euclidean_gcd_test.go:81: testing 10_000 samples using NormalGCDCalculator, cost=721 ms
  20. --- PASS: TestEuclideanGCD (8.34s)
  21. PASS
  22. ok command-line-arguments 8.347s

IGCDCalculator.go

最大公约数计算器接口

  1. package euclidean
  2. type IGCDCalculator interface {
  3. Calc(a, b int) int
  4. }

tEuclideanCalculator.go

欧几里德算法实现最大公约数求解

  1. package euclidean
  2. type tEuclideanCalculator struct {
  3. }
  4. func newEuclideanCalculator() IGCDCalculator {
  5. return &tEuclideanCalculator{}
  6. }
  7. func (me *tEuclideanCalculator) Calc(a, b int) int {
  8. if a <= 0 || b <= 0 {
  9. return 1
  10. }
  11. if a == b {
  12. return a
  13. }
  14. bigger := max(a, b)
  15. smaller := min(a, b)
  16. for smaller > 0 {
  17. remaining := bigger % smaller
  18. if remaining == 0 {
  19. return smaller
  20. } else {
  21. bigger ,smaller = smaller, remaining
  22. }
  23. }
  24. return 1
  25. }
  26. func max(a, b int) int {
  27. if a >= b {
  28. return a
  29. }
  30. return b
  31. }
  32. func min(a, b int) int {
  33. if a <= b {
  34. return a
  35. }
  36. return b
  37. }
  38. var EuclideanGCDCalculator = newEuclideanCalculator()

tNormalGcdCalculator.go

因式分解法实现最大公约数求解

  1. package euclidean
  2. import (
  3. "math"
  4. "sort"
  5. )
  6. type tNormalGcdCalculator struct {
  7. }
  8. func newNormalGcdCalculator() IGCDCalculator {
  9. return &tNormalGcdCalculator{}
  10. }
  11. func (me *tNormalGcdCalculator) Calc(a, b int) int {
  12. if a <= 0 || b <= 0 {
  13. return 1
  14. }
  15. if a == b {
  16. return a
  17. }
  18. aa := me.split(a)
  19. sort.Sort(sort.IntSlice(aa))
  20. bb := me.split(b)
  21. sort.Sort(sort.IntSlice(bb))
  22. for i, j := len(aa) - 1, len(bb) - 1;i >= 0 && j >= 0; {
  23. if aa[i] == bb[j] {
  24. return aa[i]
  25. }
  26. if aa[i] > bb[j] {
  27. i--
  28. } else {
  29. j--
  30. }
  31. }
  32. return 1
  33. }
  34. func (me *tNormalGcdCalculator) split(a int) []int {
  35. to := int(math.Floor(math.Sqrt(float64(a))))
  36. items := make([]int, 0)
  37. for i := 1;i <= to;i++ {
  38. if a % i == 0 {
  39. items = append(items, i)
  40. items = append(items, a / i)
  41. }
  42. }
  43. return items
  44. }
  45. var NormalGCDCalculator = newNormalGcdCalculator()

(end)