go语言提供了两种链表,双向链表和环形链表,单向链表是双向链表的一种;链表适合快速增删而不适合快速查询;本文详细介绍环形链表
type Ring struct {
next, prev *Ring // next 当前元素的下一个元素,prev则是上一个元素
Value interface{} // 供调用者使用,本包不会操作该字段
}
环形链表的元素由一个或多个Ring结构体组成,每一个代表环形链表的一个元素,但它同时也代表链表本身。环形链表没有头尾;指向环形链表任一元素的指针都可以作为整个环形链表看待。由此可以看出,环形链表没有实体,多个元素组合在一起就是一个环形链表,每一个元素成员都可以代表环形链表。下面看例子:
func New(n int) *Ring // 创建一个有n个元素的环形链表,返回一个元素指针
创建一个环形链表并打印:
func main() {
r := ring.New(10)
log.Println(r, r.Len())
// 打印 2019/06/01 16:29:50 &{0xc00000c0a0 0xc00000c1a0 } 10
}
上面的结果:0xc00000c0a0代表下一个元素(next)的地址,0xc00000c1a0则是上一个元素(prev)的地址,
func (r *Ring) Next() *Ring // 获取下一个元素 func (r *Ring) Prev() *Ring // 获取上一个元素 func (r *Ring) Move(n int) *Ring // 获取当前位置移动n个位置后的元素
下面看例子:
func main() {
r := ring.New(5) // 创建长度为5的环形链表
// 遍历链表赋值,环形链表的遍历比较特殊
for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
now.Value = i
}
// 遍历链表的值
for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
log.Printf("%v = %v", i, now.Value)
}
}
上面创建长度为5的链表遍历赋值,结果如下:
接着上面的结果我们再来进行一系列操作:
func main() {
r := ring.New(5) // 创建长度为5的环形链表
// 遍历链表赋值,环形链表的遍历比较特殊
for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
now.Value = i
}
// 遍历链表的值
for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
log.Printf("%v = %v", i, now.Value)
}
// 上面的遍历已经将r这个指针指向了值为4的这个元素
log.Println("r:", r.Value) // 打印 4
log.Println("next:", r.Next().Value) // 打印 0
log.Println("prev", r.Prev().Value) // 打印 3
log.Println("move:", r.Move(2).Value) // 打印 1
}
上面介绍了链表的基础查询移动,下面最后再介绍链表的其余几个方法:
func (r *Ring) Link(s *Ring) *Ring // 将r和s两个环形链表相加,也就是讲两个链表合成为一个链表,返回r这个元素的下一个元素 func (*Ring) Unlink // 删除链表中n % r.Len()个元素,从r.Next()开始删除。如果n % r.Len() == 0,不修改r。返回删除的元素构成的链表,r不能为空。 func (r *Ring) Do(f func(interface{})) // 对链表中的每个元素都执行f方法
下面我们新建一个链表,先删除一些元素,再将删除的元素重新添加进原链表,再对链表的元素都执行+1的操作,看例子:
func main() {
r := ring.New(5) // 创建长度为5的环形链表
// 遍历链表赋值,环形链表的遍历比较特殊
for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
now.Value = i
}
log.Println("=========我是漂亮的分割线=======")
rmR := r.Unlink(2) // 2对5取余等于2,也就是移除2个元素,从r的下一个元素开始移除,返回删除的元素构成的链表
log.Println(r.Len()) // 移除了2个,打印 3
log.Println("=========我也是漂亮的分割线=======")
// 将上一步删除的元素再加进链表中
r.Link(rmR)
// 遍历链表的值
for i, now := 0, r.Next(); i < r.Len(); now, i = now.Next(), i+1 {
log.Printf("%v = %v", i, now.Value)
}
// 遍历链表的元素,转换为int后,打印+1的值
r.Do(func(i interface{}) {
log.Println(i.(int) + 1)
})
}
结果如下:
到这里环形链表的方法都介绍完了,可以看到,对于双向链表它还是有一些特殊,追根到底就是它是一个闭合的链表的原因;
Unlink方法可以用来截取链表;Link方法可以用来拼接链表;Do方法由于它的特性我们可以用来遍历链表和对元素进行过滤等操作,也可以用自己的方法遍历(像我上面的for循环,不过推荐使用Do方法,毕竟方便);Move方法可以跳到链表的任意位置。
环形链表在实际运用中用的还是非常少的,只有在很特殊的情况下才适合使用,因为元素之间有且只有一个上下级关系,而且还要形成闭合的环形,这种条件还是毕竟难形成的。