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次调用了,整个递归调用结束。
综上,代码的输出结果为:
注意,虽然 test
函数在递归时修改了 n
的值,但每个递归调用都有自己的局部变量副本,因此修改不会影响到其他调用。
2.闭包的使用
2023.08.25
编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如jpg),并返回一个闭包。
调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg,如果已经有.jpg后缀,则返回原文件名。
要求使用闭包的方式完成。
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
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
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
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 )
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
解析为一个带符号的整数,并返回解析后的整数值和错误。如果解析失败,应返回适当的错误。
要求:
函数的输入参数 s
是一个字符串,表示要解析的整数。
函数应能处理正整数和负整数。
函数的返回值应为解析后的整数和一个错误。如果解析成功,错误应该为 nil
,否则应包含错误信息。
不考虑超出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。
要求:
编写函数InitRing
,来初始化Josephus环。
编写函数GetRingElems
,打印出Josephus环中所有的成员ID。
编写函数 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
),该哈希表支持以下操作:
Insert(key string, value int)
:插入一个键值对,如果键已存在,则更新其值。
Get(key string) int
:获取给定键对应的值,如果键不存在,则返回 -1。
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 )
}
}