golang标准库-环形链表(ring)

  • 前言:

go语言提供了两种链表,双向链表和环形链表,单向链表是双向链表的一种;链表适合快速增删而不适合快速查询;本文详细介绍环形链表

  • 标准库:container/ring

  • 结构

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)的地址,是当前元素的值,由于只是创建了链表,还没有赋值,所以为nil; 链表有Len()方法可以拿到链表的元素,当前为10。

 

链表元素的常见操作

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的链表遍历赋值,结果如下:

golang标准库-环形链表(ring)_第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
	}
	// 遍历链表的值
	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)
	})
}

 结果如下:

golang标准库-环形链表(ring)_第2张图片

到这里环形链表的方法都介绍完了,可以看到,对于双向链表它还是有一些特殊,追根到底就是它是一个闭合的链表的原因;

Unlink方法可以用来截取链表;Link方法可以用来拼接链表;Do方法由于它的特性我们可以用来遍历链表和对元素进行过滤等操作,也可以用自己的方法遍历(像我上面的for循环,不过推荐使用Do方法,毕竟方便);Move方法可以跳到链表的任意位置。

环形链表在实际运用中用的还是非常少的,只有在很特殊的情况下才适合使用,因为元素之间有且只有一个上下级关系,而且还要形成闭合的环形,这种条件还是毕竟难形成的。

你可能感兴趣的:(go)