编程练习题(答案版)

1.递归的考察

请说明下面这段代码的输出结果。 2023.08.25

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
	test(4)
}

func test(n int) {
    if n > 2 {
       n--
       test(n)
    }
    fmt.Println("n=", n)
}

解析:

1.第1次调用:test(4)

n=4,n>2,进入 if 语句块:

​ n–:n=3

​ test(3):调用test(3)

2.第2次调用:test(3)

n=3,n>2,进入 if 语句块:

​ n–:n=2

​ test(2):调用test(2)

3.第3次调用:test(2)

n=2,n>2,不满足 if 条件,往下执行输出语句:

​ fmt.Println(“n=”, n):输出n=2

【注意】到这里,整个递归调用的过程并没有结束,而是回到前一个递归层次(这是很容易出错的地方)。

4.回到第2次调用,继续执行下面的输出语句:

​ fmt.Println(“n=”, n):输出n=2(注意,这里的局部变量n值为2)

再回到前一个递归层次。

5.回到第1次调用,继续执行下面的输出语句:

​ fmt.Println(“n=”, n):输出n=3

这已经是第1次调用了,整个递归调用结束。

综上,代码的输出结果为:

1
2
3
n= 2
n= 2
n= 3

注意,虽然 test 函数在递归时修改了 n 的值,但每个递归调用都有自己的局部变量副本,因此修改不会影响到其他调用。

2.闭包的使用

2023.08.25

  1. 编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如jpg),并返回一个闭包。
  2. 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg,如果已经有.jpg后缀,则返回原文件名。
  3. 要求使用闭包的方式完成。
  4. strings.HasSuffix:func HasSuffix(s, suffix string) bool,判断s是否有后缀字符串suffix。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func main() {
    mySuffix := makeSuffix(".jpg") // 先获得一个闭包

    fmt.Println(mySuffix("hello.jpg")) // hello.jpg
    fmt.Println(mySuffix("winter"))    // winter.jpg
    fmt.Println(mySuffix("pic.png"))   // pic.png.jpg

}
func makeSuffix(suffix string) func(string) string {
    return func(name string) string {
       if strings.HasSuffix(name, suffix) {
          return name
       } else {
          return name + suffix
       }
    }
}

3.冒泡排序

2023.08.29

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 冒泡排序:每轮使1个数在正确的位置上,需要双层循环
// 外层循环:控制循环的轮数,每轮排好1个数
// 内层循环:从外层循环的i值开始,表示第i个元素是即将需要参与排序的数
func bubbleSort(arr *[10]int) {
	for i := 0; i < len(arr)-1; i++ { // 控制循环的轮数
		for j := i; j > 0; j-- { // // 控制实际参与排序的数字
			if arr[j] < arr[j-1] {
				temp := arr[j]
				arr[j] = arr[j-1]
				arr[j-1] = temp
			}
		}
	}
}

4.快速排序

2023.08.29

2023.10.14 补

需要额外分配空间的版本:

 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
func quickSort(arr []int) []int {
    if len(arr) <= 1 {
        return arr
    }

    pivot := arr[0] // 选择第一个元素作为基准
    var left, right []int

    for i := 1; i < len(arr); i++ {
        if arr[i] < pivot {
            left = append(left, arr[i])
        } else {
            right = append(right, arr[i])
        }
    }

    left = quickSort(left)
    right = quickSort(right)

    return append(append(left, pivot), right...)
}

func main() {
    // 待排序的切片
    arr := []int{64, 34, 25, 12, 22, 11, 90}

    fmt.Println("未排序的数组:", arr)
    arr = quickSort(arr)
    fmt.Println("排序后的数组:", arr)
}

这种方式比较好理解,但需要额外分配空间,不是原地进行交换,空间和时间上的性能较差。

推荐使用下面这种原地(In-Place)交换的方式,具有更高的性能。

 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
func main() {
  arr := []int{3, 8, 1, 5, 4, 7, 6, 9, 2}
  QuickSort2(0, len(arr)-1, arr)
  fmt.Println(arr)
}

func QuickSort2(low, high int, arr []int) {
    if low < high {
       pivotIndex := partition(low, high, arr) // 选择分区点并获取分区点的索引
       QuickSort2(low, pivotIndex, arr)        // 递归排序分区点左侧的子数组
       QuickSort2(pivotIndex+1, high, arr)     // 递归排序分区点右侧的子数组
    }
}

// partition 将数组分成两部分,左侧的元素都小于或等于基准元素,右侧的元素都大于基准元素。
// 这是快速排序算法中的关键步骤,它在原地(in-place)完成,不需要额外的内存空间。
func partition(low, high int, arr []int) int {
    pivot := arr[(low+high)/2] // 选择最中间的元素作为基准
    i, j := low, high

    for {
       for arr[i] < pivot {
          i++
       }
       for arr[j] > pivot {
          j--
       }
       if i >= j {
          return j
       }
       arr[i], arr[j] = arr[j], arr[i]
       i++
       j--
    }
}

5.二分查找

2023.08.29

递归实现:

 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
package main

import "fmt"

func main() {
 arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8}
 binarySearch(&arr, 0, len(arr)-1, 8)
}
func binarySearch(arr *[8]int, leftIndex int, rightIndex int, findElem int) {

 // 当leftIndex大于rightIndex时,表示未找到指定元素
 if leftIndex > rightIndex {
    fmt.Println("找不到")
    return
 }
 middleIndex := (leftIndex + rightIndex) / 2
 middleElem := arr[middleIndex]

 if findElem < middleElem {
    binarySearch(arr, leftIndex, middleIndex-1, findElem)
 } else if findElem > middleElem {
    binarySearch(arr, middleIndex+1, rightIndex, findElem)
 } else {
    fmt.Println("找到了,下标为:", middleIndex)
 }
}

非递归实现:(2023.09.03补)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 二分查找,一定要注意区间的定义,是左闭右闭还是左闭右开。
// 这里我们采用左闭右闭。
func search(nums []int, target int) int {
    left := 0
    right := len(nums) - 1
    for left <= right {
       middle := (left + right) / 2
       if target < nums[middle] {
          right = middle - 1
       } else if target > nums[middle] {
          left = middle + 1
       } else {
          return middle
       }
    }
    return -1
}

6.自定义排序

2023.08.30

使用 sort.Sort(data Interface),实现对 Student 结构体切片的排序。

Student 结构体有三个字段:name string, age int, score int。

Student 结构体切片中存有多个 Student 对象,将这些对象按照 age 字段升序排列后遍历输出。

 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
package main

import (
    "fmt"
    "math/rand"
    "sort"
    "strconv"
)

type Student struct {
    name  string
    age   int
    score int
}

// StuSlice 定义一个切片类型,存放多个学生
type StuSlice []Student

// 实现Interface接口中的三个方法:Len、Less、Swap
func (s StuSlice) Len() int {
    return len(s)
}

func (s StuSlice) Less(i, j int) bool {
    return s[i].age < s[j].age
}

// Swap 将切片中的元素进行交换
func (s StuSlice) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}
func main() {
    s1 := StuSlice{}

    for i := 0; i < 10; i++ {
       s1 = append(s1, Student{"stu" + strconv.Itoa(i),
          int(float32(rand.Intn(101))*0.1 + 15), // 生成15-25之间的随机数
          rand.Intn(101)})                       // 生成0-100之间的随机数
    }

    for i := 0; i < 10; i++ {
       fmt.Printf("%v\n", s1[i])
    }
    fmt.Println("-----排序后-----")
    sort.Sort(s1)
    for i := 0; i < 10; i++ {
       fmt.Printf("%v\n", s1[i])
    }
}

7.连续因子

2023.09.03

一个正整数 N 的因子中可能存在若干连续的数字。例如 630 可以分解为 3×5×6×7,其中 5、6、7 就是 3 个连续的数字。给定任一正整数 N,要求编写程序求出最长连续因子的个数,并输出最小的连续因子序列。

输入格式:

输入在一行中给出一个正整数 N(1<N<231)。

输出格式:

首先在第 1 行输出最长连续因子的个数;然后在第 2 行中按 因子1*因子2*……*因子k 的格式输出最小的连续因子序列,其中因子按递增顺序输出,1 不算在内。

输入样例:

1
630

输出样例:

1
2
3
5*6*7

自己做的本题解答:

 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
func main() {
	var num int
	fmt.Scanln(&num)
	count, factorSlice := test(num)
	fmt.Println(count)
	var resultStr string
	for i := 0; i < len(factorSlice); i++ {
		if i < len(factorSlice)-1 {
			resultStr += strconv.Itoa(factorSlice[i]) + "*"
		} else {
			resultStr += strconv.Itoa(factorSlice[i])
		}
	}
	fmt.Println(resultStr)
}
func test(num int) (int, []int) {
    
    // 先遍历出所有小于根号num的因子
	var factor []int
	for i := 2; i*i < num; i++ {
		if num%i == 0 {
			factor = append(factor, i)
			// fmt.Printf("%d ", i)
		}
	}
    
    // 然后遍历因子列表,统计最大的连续因子的数量
	count := 1
	maxLength := 1
	for i := 0; i < len(factor)-1; i++ {
		if factor[i+1] == factor[i]+1 {
			count++
		} else {
			if count > maxLength {
				maxLength = count
			}
			count = 1
		}
	}
    
    // 然后再次遍历因子列表,求得连续的因子序列
	var resultSlice []int
	for i := 0; i < len(factor); i++ {
		if factor[i]+maxLength-1 == factor[i+maxLength-1] {
			resultSlice = factor[i : i+maxLength]
			break
		}
	}
	return maxLength, resultSlice
}

个人总结:在求解过程中,进行了3次for循环,总觉得这不是最优解,应该还可以更进一步优化。

8.查验身份证

2023.09.04

L1-016 查验身份证

一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下:

首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};然后将计算的和对11取模得到值Z;最后按照以下关系对应Z值与校验码M的值:

1
2
Z:0 1 2 3 4 5 6 7 8 9 10
M:1 0 X 9 8 7 6 5 4 3 2

现在给定一些身份证号码,请你验证校验码的有效性,并输出有问题的号码。

输入格式:

输入第一行给出正整数N(≤100)是输入的身份证号码的个数。随后N行,每行给出1个18位身份证号码。

输出格式:

按照输入的顺序每行输出1个有问题的身份证号码。这里并不检验前17位是否合理,只检查前17位是否全为数字且最后1位校验码计算准确。如果所有号码都正常,则输出All passed

输入样例1:

1
2
3
4
5
4
320124198808240056
12010X198901011234
110108196711301866
37070419881216001X

输出样例1:

1
2
3
12010X198901011234
110108196711301866
37070419881216001X

输入样例2:

1
2
3
2
320124198808240056
110108196711301862

输出样例2:

1
All passed

自己写的代码:

 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
package main

import (
	"fmt"
	"reflect"
	"strconv"
)

func main() {
	var numCount int
	allPass := true
	fmt.Scanln(&numCount)

	var idCardList = make([]string, numCount)
	for i := 0; i < numCount; i++ {
		var idCard string
		fmt.Scanln(&idCard)
		idCardList[i] = idCard
	}
	for _, idCard := range idCardList {
		if !identity(idCard) {
			fmt.Println(idCard)
			allPass = false
		}
	}
	if allPass {
		fmt.Println("All passed")
	}
}

func identity(str string) bool {
	lenStr := len(str)
	lastStr := string(str[lenStr-1]) // 最后一个字符
	sum := 0
	weightArray := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
	mArray := []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
	for i := 0; i < lenStr-1; i++ { // 注意,这里不包括最后一位
		indexNum, err := strconv.Atoi(string(str[i])) // 注意:这里的str[i]为byte(uint8),直接转为int得到的是其ascii码的值,如数字1得到的是49。所以要先转换成string,然后再将string转换成int。
		if err != nil {
			return false
		}
		sum += indexNum * weightArray[i] // 每位的值分别与权重相乘
	}
	z := sum % 11
	return mArray[z] == lastStr // 最后一位与加权运算得到的结果是否一致
}

几个需要注意的细节点:

  1. 字符串中的数字截取

    如:

    1
    2
    3
    
    str := "123456"
    fmt.Println(str[0])                 // 49
    fmt.Println(reflect.TypeOf(str[0])) // uint8,即byte

    输出的结果为49,为什么呢?

    因为str[0]截取字符串得到的类型是byte类型(uint8)。当打印值的时候,打印的是其ASCII码值。

    所以如果我们想从由数字组成的字符串中截取每一个数字的话,就不能直接使用int()函数去转成int类型,因为这样只会得到这个字符的ASCII码值。正确的做法为:

    先将str[0]转换为string类型,然后再将该string通过strconv.Atoi()函数转换为int类型。即:

    1
    2
    3
    4
    5
    
    num, err := strconv.Atoi(string(str[0]))
    if err != nil {
        return
    }
    fmt.Println(num)
  2. strconv.Atoi()函数的使用

    strconv.Atoi()函数的作用是将由数字组成的字符串转换成int类型。其有两个返回值,第一个返回值为转换后得到的值,第二个返回值为 error。

    注意,一定要是由数字(必须是整数,小数也不行)的字符串,否则第一个参数会返回0。

    建议将第二个参数接收过来,并用if语句去判断是否出错,否则可能得到的不是我们想要的结果。如:

    1
    2
    
    num, _ := strconv.Atoi("12.23")
    fmt.Println(num)  // 0

    这里我们将error隐式处理了,但是返回的结果却不是我们想要的,而且也没有报错信息(这是比较致命的)。这无疑给代码的排错增加了难度。

    所以建议一定要对error进行处理。

9.字符串转换为整数

2023.09.25

题目描述:实现一个函数 Atoi(s string) (int64, error),该函数将一个字符串 s 解析为一个带符号的整数,并返回解析后的整数值和错误。如果解析失败,应返回适当的错误。

要求:

  1. 函数的输入参数 s 是一个字符串,表示要解析的整数。
  2. 函数应能处理正整数和负整数。
  3. 函数的返回值应为解析后的整数和一个错误。如果解析成功,错误应该为 nil,否则应包含错误信息。
  4. 不考虑超出int64范围越界的问题。

示例:

1
2
3
4
5
6
7
8
val, err := Atoi("123")
// val 应该为 123,err 应该为 nil

val, err := Atoi("-456")
// val 应该为 -456,err 应该为 nil

val, err := Atoi("12a34")
// val 应该为 0,err 应该包含合适的错误信息

请实现上述要求并编写 Atoi 函数的完整代码。

参考解答:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func Atoi(s string) (int64, error) {
    s0 := s
    if s[0] == '-' || s[0] == '+' {
       s = s[1:]
    }
    var n int64
    for _, ch := range []byte(s) { // 将s转为byte的切片,并遍历
       ch -= '0' // 将一个ch(Unicode码点)转换为相应的整数值。如果ch包含字符'3',执行ch-='0'之后,ch将变成整数3。
       if ch > 9 {  // 若ch大于9,则表示该字符非法
          return 0, errors.New("`" + string(ch+'0') + "`为非法字符,类型转换出错!")
       }
       n = n*10 + int64(ch)
    }
    if s0[0] == '-' {
       n = -n
    }
    return n, nil
}

10.单例模式

2023.10.07

手写一个单例模式的代码,并测试创建实例的函数创建的实例对象都是同一个。

参考代码:

使用Once:

 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
type Singleton struct {}

var singleInstance *Singleton
var once sync.Once

func GetSingletonObj() *Singleton {
    once.Do(func() { // 只执行一次
       fmt.Println("Create Obj")
       singleInstance = new(Singleton)
    })
    return singleInstance
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
       wg.Add(1)
       go func() {
          obj := GetSingletonObj()
          fmt.Printf("%x\n", unsafe.Pointer(obj)) // 可以看到,这里打印的地址值是相同的,说明只创建了一个实例对象。
          wg.Done()
       }()
    }
    wg.Wait()
}

不使用Once,使用双重互斥锁+饿汉式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
var mu sync.Mutex
var instance *Singleton

type Singleton struct {
    // 在这里定义单例的属性
    Value int
}

// GetInstance 返回单例实例
func GetInstance() *Singleton {
    if instance == nil {
        mu.Lock()
        defer mu.Unlock()
        if instance == nil {
            instance = &Singleton{
                Value: 42, // 这里可以初始化单例的属性
            }
        }
    }
    return instance
}

11.循环定时任务

2023.10.07

创建一个可以循环执行的定时任务,要求可以指定任务的次数,当达到指定执行次数时,程序退出。

参考代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var (
    wg      sync.WaitGroup
    taskNum = 5
)

func main() {
    ticker := time.NewTicker(time.Second) // 使用time.NewTicker()的方式,每隔1s执行一次定时任务
    count := 0
    wg.Add(1)
    go func() {
       defer wg.Done()
       defer ticker.Stop()
       for {
          count++
          t := <-ticker.C
          fmt.Println(t)
          if count >= taskNum { // 控制定时任务执行的次数
             return
          }
       }
    }()
    wg.Wait()
    fmt.Println("程序结束!")
}

12.对象池

2023.10.07

实现一个简单的对象池的功能,要求:

  • 通过NewObjPool(numOfObj int)函数设置连接池的大小,并初始化连接池。
  • 通过GetObj(timeout time.Duration)可以获取一个连接池对象,timeout 参数为指定的超时时间(当操作指定时间还没有获取到连接池对象时,报超时的错误)。
  • 通过ReleaseObj(obj *ReusableObj)函数可以将一个连接池对象放入连接池中。
  • 测试连接池的所有功能。

思考使用什么数据结构可以更好地实现连接池?

 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
type ReusableObj struct {}

type ObjPool struct {
    bufChan chan *ReusableObj
}

func NewObjPool(numOfObj int) *ObjPool {
    objPool := ObjPool{}
    objPool.bufChan = make(chan *ReusableObj, numOfObj)
    for i := 0; i < numOfObj; i++ {
       objPool.bufChan <- &ReusableObj{}
    }
    return &objPool
}

func (p *ObjPool) GetObj(timeout time.Duration) (*ReusableObj, error) {
    select {
    case ret := <-p.bufChan:
       return ret, nil
    case <-time.After(timeout): // 超时控制
       return nil, errors.New("time out")
    }
}

func (p *ObjPool) ReleaseObj(obj *ReusableObj) error {
    select {
    case p.bufChan <- obj:
       return nil
    default:
       return errors.New("overflow")
    }
}

func main() {
    pools := NewObjPool(10)

    for i := 0; i < 10; i++ { // 在不放回的情况下,如果取的数量超过了连接池中连接的数量,会报超时的错误。
       if v, err := pools.GetObj(time.Second); err != nil {
          panic(err)
       } else {
          fmt.Println("v:", unsafe.Pointer(v))

          // 对连接进行的操作....

          if err := pools.ReleaseObj(v); err != nil { // 操作完成后,将连接放回连接池
             panic(err)
          }
       }
    }
    fmt.Println("Done")
}

13.生成随机字符串

2023.10.07

定义一个GetRandomString(n int)函数,生成随机的字符串(包含数字和小写英文字母),可以指定字符串的位数n。

参考代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func main() {
    result := GetRandomString(4) // 生成长度为4的随机字符串
    fmt.Println("result:", result)
}

func GetRandomString(n int) string { // 这里的n代表生成的字符串长度
    str := "0123456789abcdefghijklmnopqrstuvwxyz" // 可选的字符集
    var result []byte
    r := rand.New(rand.NewSource(time.Now().UnixNano())) // 指定随机种子
    for i := 0; i < n; i++ {
       result = append(result, str[r.Intn(len(str)-1)])
    }
    return string(result)
}

14.删除数组中的重复元素

2023.10.07

定义函数removeDuplicates(array []int),删除数组中的重复元素。

参考代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {
    array := []int{1, 6, 6, 8, 6, 1, 2, 2, 2}
    res := removeDuplicates(array)
    fmt.Println(res) // [1 6 8 2]
}

// 删除重复元素
func removeDuplicates(array []int) []int {
    mapData := make(map[int]bool)
    var result []int
    for _, value := range array {
       _, exist := mapData[value] // 使用map探测重复元素
       if !exist {
          mapData[value] = true
          result = append(result, value)
       }
    }
    return result
}

15.反射的最佳实践

2023.10.07

使用反射的方式来获取结构体的tag标签,遍历字段的值,修改字段值,调用结构体方法。

具体的结构体和结构体方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type Mankind struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Gender string `json:"gender"`
}

func (m *Mankind) Introduce() {
    fmt.Printf("My name is %v, %v years old!\n", m.Name, m.Age)
}

func main() {
	m1 := Mankind{"Jerry", 25, "male"}
    // 定义名为callStructure的函数,完成题目要求的功能。
}
func callStructure() {

参考代码:

 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
type Mankind struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Gender string `json:"gender"`
}

func (m *Mankind) Introduce() { // 使用指针接收者的方法,在反射调用时和普通的方法略有不同
    fmt.Printf("My name is %v, %v years old!\n", m.Name, m.Age)
}

func main() {
    m1 := Mankind{"Jerry", 25, "male"}
    callStructure(&m1) // 要修改属性值,这里必须传入地址
}
func callStructure(m *Mankind) { // 指针方式接收形参
    structValue := reflect.ValueOf(m).Elem() // “解引用”

    // 遍历属性和标签
    for i := 0; i < structValue.NumField(); i++ {
       field := structValue.Field(i)
       fieldName := structValue.Type().Field(i).Name
       tag := structValue.Type().Field(i).Tag.Get("json")

       fmt.Printf("field[%v] is %v, value is %v, tag is %v\n", i, fieldName, field, tag)

       // 设置属性值
       if field.CanSet() {
          if fieldName == "Name" { // 根据字段名修改字段值
             field.SetString("Alice")
          }
          if fieldName == "Age" {
             field.SetInt(28)
          }
          if fieldName == "Gender" {
             field.SetString("female")
          }
       }
    }
    fmt.Println("属性值修改成功!")

    // 调用结构体方法
    // method := structValue.MethodByName("Introduce")  // 这样写无法调用指针接收者方法Introduce
    method := reflect.ValueOf(m).MethodByName("Introduce") // 由于Introduce是指针接收者方法,所以这里应该用指针(解引用之前)来调用该方法。
    if method.IsValid() {
       method.Call(nil)
    } else {
       fmt.Println("方法调用失败!")
    }
}

16.单向环形队列

2023.10.11

使用数组模拟实现一个单向的环形队列,只要不超过队列的最大长度,就可以一直利用数组队列的空间,不断地进行Push和Pop操作,要求实现三个方法:Push(添加元素)、Pop(取出元素)、ShowQueue(显示队列所有元素)。

参考代码:

  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
type CircleQueue struct {
    maxSize int
    array   [5]int
    head    int // head指向下一个弹出元素的位置
    tail    int // tail指向下一个添加元素的位置
}

// Push 向队列中添加元素
func (queue *CircleQueue) Push(data int) (err error) {
    if (queue.tail+1)%queue.maxSize == queue.head { // 队列已满
       return errors.New("queue full")
    }
    queue.array[queue.tail] = data
    queue.tail = (queue.tail + 1) % queue.maxSize
    return
}

// Pop 从队列中弹出元素
func (queue *CircleQueue) Pop() (data int, err error) {
    if queue.head == queue.tail { // 队列为空
       return -1, errors.New("queue empty")
    }
    data = queue.array[queue.head]
    queue.head = (queue.head + 1) % queue.maxSize
    return data, nil
}

func (queue *CircleQueue) ShowCircleQueue() {
    queueLen := (queue.tail - queue.head + queue.maxSize) % queue.maxSize // 队列中元素个数
    if queueLen == 0 {
       fmt.Println("queue is empty")
       return
    }
    originalHead := queue.head
    for i := 1; i <= queueLen; i++ { // 遍历队列长度个元素
       fmt.Printf("array[%d]=%d\t", i, queue.array[originalHead])
       originalHead = (originalHead + 1) % queue.maxSize
    }
    fmt.Println()
}

func main() {

    // 先创建一个队列
    queue := CircleQueue{
       maxSize: 5,
       head:    0,
       tail:    0,
    }

    // 添加数据
    var key string
    var val int

    fmt.Println("1. 输入push 表示添加数据到队列")
    fmt.Println("2. 输入pop 表示从队列中获取数据")
    fmt.Println("3. 输入show 表示显示队列")
    fmt.Println("4. 输入exit 表示退出")

    for {
       fmt.Printf("请输入指令:")

       if _, err := fmt.Scanln(&key); err != nil {
          panic(err)
       }

       keyMap := map[string]struct{}{"pop": struct{}{}, "push": struct{}{}, "show": struct{}{}, "exit": struct{}{}}
       if _, exist := keyMap[key]; !exist { // 输入的指令不正确
          fmt.Println("请输入正确的指令(add/get/show/exit)!")
          continue
       }

       switch key {
       case "push":
          fmt.Printf("输入需要添加到队列的数据:")
          _, err := fmt.Scanln(&val)
          if err != nil {
             panic(err)
          }

          if err := queue.Push(val); err != nil {
             fmt.Println(err.Error())
          } else {
             fmt.Println("加入队列成功!")
          }
       case "pop":
          data, err := queue.Pop()
          if err != nil {
             fmt.Println(err.Error())
          } else {
             fmt.Printf("取出元素:%d\n", data)
          }
       case "show":
          fmt.Printf("当前队列为:")
          queue.ShowCircleQueue()
       case "exit":
          os.Exit(0)
       }
    }
}

17.约瑟夫问题

2023.10.12

题目描述:

你需要实现一个Josephus环,该环包含一定数量的成员。每个成员都有一个唯一的ID。

要求:

  1. 编写函数InitRing ,来初始化Josephus环。
  2. 编写函数GetRingElems,打印出Josephus环中所有的成员ID。
  3. 编写函数 PlayGame,以模拟Josephus问题的解决过程。

具体要求如下:

  • 从环中的第 startNo 号成员开始,数数,数到第 countNo 号成员后,将其从环中淘汰。
  • 淘汰后,重新从下一个成员开始数,继续数 countNo 个成员,再淘汰。
  • 重复上述过程,直到只剩下一个成员为止。

三个函数的签名如下:

1
2
3
4
5
6
func InitRing(num int) *JosephusRing{}  // num:初始化成员的数量
func GetRingElems(first *JosephusRing) {}  // first:Josephus环的第一个成员的指针
func PlayGame(first *JosephusRing, startNo int, countNo int) {}  
// first:Josephus环的第一个成员的指针
// startNo:开始计数的成员的ID号(从1开始)。
// countNo:每次数数的步数。

参考代码:

 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
type JosephusRing struct {
    id   int
    next *JosephusRing
}

func InitRing(num int) *JosephusRing { // 指定成员的个数进行初始化环
    first := &JosephusRing{}
    last := &JosephusRing{}
    if num < 1 {
       fmt.Println("成员数量至少为1!")
       return nil
    }
    for i := 1; i <= num; i++ {
       rData := &JosephusRing{
          id: i,
       }
       if i == 1 { // 第一个成员
          first = rData     // first指向链表的第一个元素,此后就不再改变
          last = rData      // 通过移动last来实现链表元素的增长,初始值为第一个元素
          last.next = first // 将新加入的元素指向first
       } else {
          last.next = rData // 将新的元素加入链表,并将last指向新的元素
          rData.next = first
          last = rData
       }
    }
    return first
}

func GetRingElems(first *JosephusRing) {
    if first.next == nil {
       fmt.Println("链表为空,没有任何成员!")
       return
    }
    last := first
    fmt.Println()
    for {
       fmt.Printf("[id=%d]--->", last.id)
       if last.next == first { // 遍历到最后一个元素了
          break
       }
       last = last.next
    }
}

func PlayGame(first *JosephusRing, startNo int, countNo int) {
    if first.next == nil {
       fmt.Println("链表为空!")
       return
    }
    last := first // 需要一个last指针指向最后(也就是first之前的那个元素),因为要删除first结点,必须要找打它的前一个结点
    for {         // 将last指向最后的元素
       if last.next == first {
          break
       }
       last = last.next
    }
    // 让first和last都移动startNo-1次(移动到起始位置)(因为startNo表示的是开始的编号,但first已经指向第1个元素了,所以只需要移动startNo-1次)
    for i := 1; i <= startNo-1; i++ {
       first = first.next
       last = last.next
    }
    // 游戏开始,开始数数(countNo)
    // first始终指向的是要删除的成员
    for {
       // 移动countNo-1次(因为成员自身也算一次)
       for i := 1; i <= countNo-1; i++ {
          first = first.next
          last = last.next
       }

       // 删除first指向的结点
       fmt.Printf("\nid=%d的元素被删除!", first.id)
       first = first.next
       last.next = first

       // 循环的终止条件:链表中只有一个元素(first=last)
       if last == first {
          break
       }
    }
    fmt.Printf("\nid=%d的元素被删除!", first.id)
}

func main() {
    first := InitRing(5)
    GetRingElems(first)
    PlayGame(first, 2, 3)
}

18.选择排序

2023.10.14

 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
package main

/**
    从小到大排序
    选择排序:每轮选择一个无需中的最小值,n-1轮后,数组有序。
    技巧:先写内层循环,然后再嵌套一个外层循环。
    先假设我们只需要找一次最小值,具体操作如下:
       1.假设arr[0]就是最小元素(min=arr[0]);(注意:需要使用一个变量index记录下标0,后面交换的时候要用到)
       2.遍历数组的每个元素,依次与min进行比较,如果有比min值更小的,则更新min的值和下标index;
       3.交换arr[0]和arr[index]的值。
    这样就完成了一次最小值的排序,我们只需要在外面嵌套一层for循环,将0改成循环变量j即可。这样就大大简化了我们写代码的过程。
*/

func main() {
  arr := []int{3, 8, 1, 5, 4, 7, 6, 9, 2}
  result := SelectionSort(arr)
  fmt.Println(result)
}

func SelectionSort(arr []int) []int {
    newArr := arr
    for j := 0; j < len(arr)-1; j++ {
       //minArr := newArr[j] // 之前用了一个变量来存最小值,后面发现可以优化一下,只需要记录下标minIndex就可以了,所以将这里注释掉
       minIndex := j
       for i := j + 1; i < len(arr); i++ { // 找出最小值的下标
          if newArr[i] < newArr[minIndex] {
             minIndex = i // 更新minIndex
          }
       }
       newArr[minIndex], newArr[j] = newArr[j], newArr[minIndex]
    }
    return newArr
}

19.插入排序

2023.10.14

 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
package main

import "fmt"

/**
* Description:
	技巧:也是和选择排序一样,先写内层循环,然后再嵌套一层循环。
* @Author Hollis
* @Create 2023-10-12 18:20
 */

func main() {
    arr := []int{3, 8, 1, 5, 4, 7, 6, 9, 2}
    result := InsertSort(arr)
    fmt.Println(result)
}

func InsertSort(arr []int) []int {
    newArr := arr
    for i := 1; i <= len(newArr)-1; i++ {
       // 先确定第二个元素应该插入的位置
       insertVal := newArr[i]
       insertIndex := i - 1

       for insertIndex >= 0 && newArr[insertIndex] > insertVal {
          newArr[insertIndex+1] = newArr[insertIndex]
          insertIndex--
       }
       if insertIndex+1 != i {
          newArr[insertIndex+1] = insertVal
       }
    }
    return newArr
}

20.Hash表

2023.10.15

实现一个简单的哈希表,然后要求你编写插入、查找和删除键值对的操作。

问题描述:

你需要实现一个简单的哈希表(不要使用Go的内置map),该哈希表支持以下操作:

  1. Insert(key string, value int):插入一个键值对,如果键已存在,则更新其值。
  2. Get(key string) int:获取给定键对应的值,如果键不存在,则返回 -1。
  3. Delete(key string):从哈希表中删除给定键的键值对。

你的哈希表应该能够处理哈希碰撞,可以使用开放寻址法或链表法等方法来解决碰撞问题。你可以使用已经实现的哈希函数或自己编写一个哈希函数。

示例使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func main() {
    myHashMap := Constructor() // 创建哈希表实例

    myHashMap.Insert("apple", 1) // 插入键值对
    myHashMap.Insert("banana", 2)
    myHashMap.Insert("cherry", 3)

    fmt.Println(myHashMap.Get("apple")) // 输出: 1
    fmt.Println(myHashMap.Get("banana")) // 输出: 2

    myHashMap.Delete("banana") // 删除键值对
    fmt.Println(myHashMap.Get("banana")) // 输出: -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
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
type MyHashMap struct {
    data [1000][]KeyValue
}

type KeyValue struct {
    key   string
    value int
}

/** Initialize your data structure here. */
func Constructor() MyHashMap {
    return MyHashMap{}
}

/** value will always be non-negative. */
func (this *MyHashMap) Insert(key string, value int) {
    index := hash(key)
    for i, kv := range this.data[index] {
        if kv.key == key {
            this.data[index][i].value = value
            return
        }
    }
    this.data[index] = append(this.data[index], KeyValue{key, value})
}

/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
func (this *MyHashMap) Get(key string) int {
    index := hash(key)
    for _, kv := range this.data[index] {
        if kv.key == key {
            return kv.value
        }
    }
    return -1
}

/** Removes the mapping of the specified value key if this map contains a mapping for the key */
func (this *MyHashMap) Delete(key string) {
    index := hash(key)
    for i, kv := range this.data[index] {
        if kv.key == key {
            this.data[index] = append(this.data[index][:i], this.data[index][i+1:]...)
            return
        }
    }
}

func hash(s string) int {
    h := 0
    for _, ch := range s {
        h = 31*h + int(ch)
    }
    return h % 1000
}

哈希值更新:对于每个字符 ch,哈希值 h 会按以下方式更新:

  • h 乘以 31:将当前的哈希值乘以 31,这个选择的基数通常是质数,帮助分散哈希值。
  • 加上字符的ASCII值:将当前字符 ch 的ASCII值(整数表示)加到哈希值 h 上。

21.归并排序

2023.10.16

递归实现

归并排序-递归实现的基本思想:先选取数组中最中间的值作为middle,将数组分为左右两个数组。 然后将左右两个数组分别进行归并排序,然后定义一个和原来数组容量相同的数组help,用两个指针p1和p2, 分别指向左右两个数组的最开始元素(需要借助middle),取出二者中更小的元素存入数组help中,移动p1和p2,直到p1和p2越界,最后,使用help数组覆盖原来的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
40
41
42
43
44
func main() {
    arr := []int{3, 8, 1, 5, 4, 7, 6, 9, 2}
    help := make([]int, len(arr))
    MergeSort(arr, help, 0, len(arr)-1)
    fmt.Println(arr)
}

func MergeSort(arr, help []int, left, right int) {
    if left >= right {
       return
    }

    middle := left + (right-left)>>1      // 求中点
    MergeSort(arr, help, left, middle)    // 左部分有序
    MergeSort(arr, help, middle+1, right) // 右部分有序

    Merge(arr, help, left, middle, right) // 将左右数组合并到help数组中(middle参数用来分割左右数组)
}

func Merge(arr, help []int, left, middle, right int) { // 左:left...middle,右:middle+1...right。help为辅助辅助,存放合并后的值。
    p1 := left
    p2 := middle + 1
    i := left // 两个数组中比较出来的数字,应该放在help数组中的位置
    for ; p1 <= middle && p2 <= right; i++ {
       if arr[p1] <= arr[p2] {
          help[i] = arr[p1]
          p1++
       } else {
          help[i] = arr[p2]
          p2++
       }
    }
    for ; p1 <= middle; i++ {  // 将p1或p2中剩下的元素添加到help数组中
       help[i] = arr[p1]
       p1++
    }
    for ; p2 <= right; i++ {
       help[i] = arr[p2]
       p2++
    }
    for i := left; i <= right; i++ {
       arr[i] = help[i]
    }
}

22.和等于k的最长子数组长度

2023.10.17

力扣:和等于 k 的最长子数组长度 给定一个数组nums和一个目标值k,找到累加和等于k的最长连续子数组长度。如果不存在任意一个符合要求的子数组,则返回 0。

参考代码:

 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
// maxSubArrayLen 函数接受一个整数数组 nums 和一个目标值 k,并返回累加和等于 k 的最长连续子数组的长度
func maxSubArrayLen(nums []int, k int) int {
    prefixSum := make(map[int]int)  // prefixSum 是一个映射,用于存储累加和和对应的索引
    maxLen := 0  // maxLen 用于跟踪最长子数组的长度
    sum := 0   // sum 用于跟踪当前的累加和

    for i := 0; i < len(nums); i++ {
       sum += nums[i]

       // 如果当前累加和等于目标值 k,整个数组就是一个候选子数组
       if sum == k {
          maxLen = i + 1
       } else if index, ok := prefixSum[sum-k]; ok {
          // 如果存在之前的累加和等于当前累加和减去 k,说明这两个位置之间的元素和为 k
          maxLen = max(maxLen, i-index)
       }

       // 如果当前累加和不在前缀和映射中,将其添加进去
       if _, ok := prefixSum[sum]; !ok {
          prefixSum[sum] = i
       }
    }
    return maxLen
}

// max 函数用于比较两个整数并返回较大的那个
func max(a, b int) int {
    if a > b {
       return a
    }
    return b
}

func main() {
    nums := []int{1, -1, 5, -2, 3}
    k := 3
    result := maxSubArrayLen(nums, k)
    fmt.Println("最长连续子数组的长度为:", result)
}

23.荷兰国旗问题

2023.10.17

给定一个数组arr,和一个整数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数的右边。 要求额外空间复杂度O(1),时间复杂度O(N)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
    arr := []int{3, 1, 2, 8, 5, 2, 7, 1, 3, 6, 3}
    num := 3
    partition(arr, num)
    fmt.Println(arr)
}

func partition(arr []int, num int) {
    low, mid, high := 0, 0, len(arr)-1

    for mid <= high {
       if arr[mid] < num {
          arr[low], arr[mid] = arr[mid], arr[low]
          low++
          mid++
       } else if arr[mid] == num {
          mid++
       } else {
          arr[mid], arr[high] = arr[high], arr[mid]
          high--
       }
    }
}

在这个示例中,我们使用三个指针:low、mid和high,它们分别表示数组中小于num的区域、等于num的区域和大于num的区域。 我们从中间开始遍历数组中的每个元素,并根据其值将元素放入相应的区域。具体来说: 如果当前元素小于num,将其与low位置的元素交换,然后将low和mid分别向前移动一位。 如果当前元素等于num,只将mid向前移动一位。 如果当前元素大于num,将其与high位置的元素交换,然后将high向后移动一位。 这个过程将数组分成三个区域,分别包含小于、等于和大于num的元素,满足题目要求的条件。

24.找到第K大的数

2023.10.18

给你一个整数数组nums和一个正数k,请你返回第k大的数。要求时间复杂度O(n)。 可以把这个问题转化为:确定排完序(升序)后,第 len(num)-k 位置上的数,这个数就是我们的最后结果。 但我们不需要真的去将数组nums排好序,可以借鉴快速排序中的partition函数(左边的数比pivot小,右边的数比pivot大)的思想。 如果pivot刚好等于了我们的目标位置 len(num)-k,则说明这个位置上的数就是我们要找的数。 如果pivot小于目标位置 len(num)-k,说明我们要找的数在pivot的右侧,反之则在左侧。(只需要找一侧就可以了,这样就提高了查找的效率)

参考代码:

 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
func main() {
	nums := []int{3, 2, 1, 5, 6, 4}
	k := 6
	result := findKthLargest(nums, k)
	fmt.Printf("第 %d 大的数是:%d\n", k, result)
}

func findKthLargest(nums []int, k int) int {
	return quickSelect(nums, 0, len(nums)-1, len(nums)-k)
}

func quickSelect(nums []int, left, right, index int) int {
	for {
		if left > right { // 遍历完了都没找到的情况
			return 0
		}
		pivotIndex := partition2(nums, left, right)
		if pivotIndex == index {
			return nums[pivotIndex]
		}
		if pivotIndex < index {
			left = pivotIndex + 1
		} else {
			right = pivotIndex - 1
		}
	}
}

func partition2(nums []int, left, right int) int {
	l := left
	r := right
	pivot := l + (r-l)>>1
	for {
		for nums[l] < nums[pivot] {
			l++
		}
		for nums[r] > nums[pivot] {
			r--
		}
		if l >= r {
			return r
		}
		nums[l], nums[r] = nums[r], nums[l]
		l++
		r--
	}
}

25.大根堆

2023.10.18

创建一个 MaxHeap 结构体,其中包含最大堆的堆切片和堆大小。它包括了插入元素、删除最大元素、堆化(上浮和下沉)操作。在 main 函数中,使用最大堆来插入元素并删除最大元素(依次输出堆中最大的元素)。

 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
type MaxHeap struct {
	data []int // 堆的元素存储在切片中
	size int   // 堆的大小
}

// NewMaxHeap 创建一个新的最大堆
func NewMaxHeap() *MaxHeap {
	return &MaxHeap{}
}

// Insert 将元素插入到最大堆
func (h *MaxHeap) Insert(key int) {
	h.data = append(h.data, key) // 将新元素添加到堆的末尾
	h.size++                     // 堆大小加一
	h.heapifyUp(h.size - 1)      // 对新元素执行上浮操作,以保持最大堆性质
}

// ExtractMax 删除并返回最大元素
func (h *MaxHeap) ExtractMax() (int, error) {
	if h.size == 0 {
		return 0, fmt.Errorf("heap is empty")
	}
	max := h.data[0]         // 最大元素是根节点
	last := h.data[h.size-1] // 获取最后一个元素
	h.size--                 // 堆大小减一
	if h.size > 0 {
		h.data[0] = last // 用最后一个元素替代根节点
		h.heapifyDown(0) // 对根节点执行下沉操作,以保持最大堆性质
	}
	return max, nil
}

// heapifyUp 堆化操作,自下而上,用于插入操作
func (h *MaxHeap) heapifyUp(index int) {
	for index > 0 {
		parent := (index - 1) / 2 // 计算父节点索引
		if h.data[index] <= h.data[parent] {
			break // 如果当前节点不大于父节点,堆性质已经满足,退出循环
		}
		// 交换当前节点和父节点的值
		h.data[index], h.data[parent] = h.data[parent], h.data[index]
		index = parent // 更新索引
	}
}

// heapifyDown 堆化操作,自上而下,用于删除操作
func (h *MaxHeap) heapifyDown(index int) {
	for {
		leftChild := 2*index + 1  // 左子节点索引
		rightChild := 2*index + 2 // 右子节点索引
		largest := index          // 假设当前节点是最大的

		// 寻找左右子节点中的最大者
		if leftChild < h.size && h.data[leftChild] > h.data[largest] {
			largest = leftChild
		}
		if rightChild < h.size && h.data[rightChild] > h.data[largest] {
			largest = rightChild
		}

		if largest == index {
			break // 如果当前节点已经是最大的,堆性质已经满足,退出循环
		}

		// 交换当前节点和最大子节点的值
		h.data[index], h.data[largest] = h.data[largest], h.data[index]
		index = largest // 更新索引(largest的位置指向的是和原来index互换值的位置,现在我们需要将index设置为该值,方便进行向下面的子树遍历)
	}
}

func main() {
	maxHeap := NewMaxHeap()

	maxHeap.Insert(10)
	maxHeap.Insert(5)
	maxHeap.Insert(20)
	maxHeap.Insert(3)
	maxHeap.Insert(30)

	fmt.Println("Max Heap:")
	for maxHeap.size > 0 {
		max, _ := maxHeap.ExtractMax()
		fmt.Printf("%d ", max)
	}
}
0%