阅读 137

Go学习六:sort包、map、双向链表、双向循环链表

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

sort包

  • Go语言标准库中sort提供了排序API
  • sort包提供了多种排序算法,这些算法是内部实现的,每次使用sort包排序时,会自动选择最优算法实现
    • 插入排序
    • 快速排序
    • 堆排
  • sort包中最上层是一个名称为Interface的接口,只要满足sort.Interface类型都可以排序
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}
复制代码
  • Go语言标准库默认提供了对int、float64、string进行排序的API
  • 很多函数的参数都是sort包下类型,需要进行转换.

排序实现

  • 对int类型切片排序
	num := [] int{1, 7, 5, 2, 6}
	sort.Ints(num) //升序
	fmt.Println(num)
	sort.Sort(sort.Reverse(sort.IntSlice(num))) //降序
	fmt.Println(num)
复制代码
  • 对float64类型切片排序
	f := [] float64{1.5, 7.2, 5.8, 2.3, 6.9}
	sort.Float64s(f) //升序
	fmt.Println(f)
	sort.Sort(sort.Reverse(sort.Float64Slice(f))) //降序
	fmt.Println(f)
复制代码
  • 对string类型切片排序
    • 按照编码表数值进行排序
    • 多字符串中按照第一个字符进行比较
    • 如果第一个字符相同,比较第二个字符
	s := []string{"我", "我是Goer", "a", "d", "国家", "你", "我a"}
	sort.Sort(sort.StringSlice(s)) //升序
	fmt.Println(s)
	//查找内容的索引,如果不存在,返回内容应该在升序排序切片的哪个位置插入
	fmt.Println(sort.SearchStrings(s, "你是"))
	sort.Sort(sort.Reverse(sort.StringSlice(s)))
	fmt.Println(s)
复制代码

map

  • map以散列表方式存储键值对集合

  • map中每个元素都是键值对

map[key]Value
复制代码
  • key是操作map的唯一标准.可以通过key对map中元素进行增加/删除/修改/查看
  • key是唯一的,添加重复的key会覆盖之前的元素.
  • map是值类型,只声明时为空指针(nil)
	var m map[string]int
	fmt.Println(m == nil) //输出:true
	fmt.Printf("%p", m)   //输出:0x0
复制代码
  • map读写数据时并不是并发安全的,可以结合RWMutex保证并发安全(RWMutex在后面讲解)

实例化map的几种方式

  • 使用make函数实例化一个没有初始值的map
	m := make(map[string]string)
	fmt.Println(m==nil)//输出:false
	fmt.Printf("%p", m)//输出:内存地址
复制代码
  • 可以在声明map时直接给map赋初始值.注意初始值在一行和在多行写时的语法区别
    • map中元素键值对语法满足: key:value
    • key和value的类型必须和map[key]value类型严格对应
	m := map[string]string{"name": "msr", "address": "中国广东"}
	m1 := map[string]string{
		"name":     "msr",
		"addresss": "中国广东",
	}
	fmt.Println(m, m1)
复制代码

操作map中的元素

  • 使用key判断,如果key不存在向map中新增数据,如果key存在会覆盖map中元素
	m := make(map[string]int)
	m["money"] = 5
	fmt.Println(m) //输出:map[money:5]
	m["money"] = 6
	fmt.Println(m) //map[money:6]
复制代码
  • Go语言标准库中提供了对map元素删除的函数,使用顶层delete()即可完成删除
    • 如果key存在执行删除元素
    • 如果key不存在,map中内容不变,也不会有错误
	m := make(map[string]int)
	m["money"] = 5
	delete(m, "没有的key")
	fmt.Println(m) //输出:map[money:5]
	delete(m, "money")
	fmt.Println(m) //输出:map[]
复制代码
  • 获取map中指定key对应的值
    • 使用:map变量[key]获取key对应的值
    • 如果key不存在返回map[key]Value中Value类型的默认值.例如:Value是string类型就返回""
    • 返回值可以是一个,也可以是两个.
      • 一个表示key对应的值
      • 两个分别表示:key对应的值和这个key是否存在
	m := map[string]string{"name": "msr", "address": "中国广东"}
	fmt.Println(m["name"]) //输出:msr
	fmt.Println(m["age"])  //输出:空字符串
	value, ok := m["age"]
	fmt.Println(value, ok) //输出:空字符串 false
复制代码
  • 如果希望把map中所有元素都遍历,可以使用for结合range实现
	m := map[string]string{"name": "msr", "address": "中国广东"}
	//range遍历map时返回值分别表示key和value
	for key, value := range m {
		fmt.Println(key, value)
	}
复制代码

双向链表概述

  • 双向链表结构如下 双向链表

  • 双向链表结构中元素在内存中不是紧邻空间,而是每个元素中存放上一个元素和后一个元素的地址

    • 第一个元素称为头(head)元素,前连接(前置指针域)为nil
    • 最后一个元素称为尾(foot)元素,后连接(后置指针域)为nil
  • 双向链表的优点:

    • 在执行新增元素或删除元素时效率高,获取任意一个元素,可以方便的在这个元素前后插入元素
    • 充分利用内存空间,实现内存灵活管理
    • 可实现正序和逆序遍历
    • 头元素和尾元素新增或删除时效率较高
  • 双向链表的缺点

    • 链表增加了元素的指针域,空间开销比较大
    • 遍历时跳跃性查找内容,大量数据遍历性能低

双向链表容器List

  • 在Go语言标准库的container/list 包提供了双向链表List
  • List结构体定义如下
    • root表示根元素
    • len表示链表中有多少个元素
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
	root Element // sentinel list element, only &root, root.prev, and root.next are used
	len  int     // current list length excluding (this) sentinel element
}
复制代码
  • 其中Element结构体定义如下
    • next表示下一个元素,使用Next()可以获取到
    • prev表示上一个元素,使用Prev()可以获取到
    • list表示元素属于哪个链表
    • Value表示元素的值,interface{}在Go语言中表示任意类型
  // Element is an element of a linked list.
  type Element struct {
  	// Next and previous pointers in the doubly-linked list of elements.
  	// To simplify the implementation, internally a list l is implemented
  	// as a ring, such that &l.root is both the next element of the last
  	// list element (l.Back()) and the previous element of the first list
  	// element (l.Front()).
  	next, prev *Element

  	// The list to which this element belongs.
  	list *List

  	// The value stored with this element.
  	Value interface{}
  }
复制代码

操作List

  • 直接使用container/list包下的New()新建一个空的List
	mylist := list.New()
	fmt.Println(mylist)       //输出list中内容
	fmt.Println(mylist.Len()) //查看链表中元素的个数
	fmt.Printf("%p", mylist)  //输出地址
复制代码
  • Go语言标准库中提供了很多向双向链表中添加元素的函数
	//添加到最后,List["a"]
	mylist.PushBack("a")
    //添加到最前面,List["b","a"]
	mylist.PushFront("b") 
	//向第一个元素后面添加元素,List["b","c","a"]
	mylist.InsertAfter("c", mylist.Front()) 
	//向最后一个元素前面添加元素,List["b","c","d","a"]
	mylist.InsertBefore("d", mylist.Back()) 
复制代码
  • 取出链表中的元素
	fmt.Println(mylist.Back().Value)  //最后一个元素的值
	fmt.Println(mylist.Front().Value) //第一个元素的值

	//只能从头向后找,或从后往前找,获取元素内容
	n := 5
	var curr *list.Element
	if n > 0 && n <= mylist.Len() {
		if n == 1 {
			curr = mylist.Front()
		} else if n == mylist.Len() {
			curr = mylist.Back()
		} else {
			curr = mylist.Front()
			for i := 1; i < n; i++ {
				curr = curr.Next()
			}
		}
	} else {
		fmt.Println("n的数值不对")
	}
	//遍历所有值
	for e := mylist.Front(); e != nil; e = e.Next() {
		fmt.Println(e.Value)
	}
复制代码
  • 移动元素的顺序
	mylist.MoveToBack(mylist.Front()) //把第一个移动到后面
	mylist.MoveToFront(mylist.Back()) //把最后一个移动到前面
	mylist.MoveAfter(mylist.Front(),mylist.Back())//把第一个参数元素,移动到第二个参数元素后面
	mylist.MoveBefore(mylist.Front(),mylist.Back())//把第一个参数元素,移动到第二个参数元素前面
复制代码
  • 删除元素
mylist.Remove(mylist.Front())
复制代码

双向循环链表

  • 循环链表特点是没有节点的指针域为nil,通过任何一个元素都可以找到其他元素
  • 环形链表结构如下
graph LR
	A -->B	
	B -->C	
	C -->D	
	D -->E
	E -->D
	A -->E
	E -->A
	D -->C
	C -->B
	B -->A	
  • 双向循环链表和双向链表区别

    • 双向循环链表没有严格意义上的头元素和尾元素
    • 没有元素的前连接和后连接为nil
    • 一个长度为n的双向循环链表,通过某个元素向某个方向移动,在查找最多n-1次后一定会找到另一个元素

Go语言中的双向循环链表

  • 在container/ring包下结构体Ring源码如下
    • 官方明确说明了Ring是循环链表的元素,又是环形链表.
    • 实际使用时Ring遍历就是环形链表第一个元素
// A Ring is an element of a circular list, or ring.
// Rings do not have a beginning or end; a pointer to any ring element
// serves as reference to the entire ring. Empty rings are represented
// as nil Ring pointers. The zero value for a Ring is a one-element
// ring with a nil Value.
//
type Ring struct {
	next, prev *Ring
	Value      interface{} // for use by client; untouched by this library
}
复制代码
  • Go语言标准库中对container/ring包提供的API如下
	type Ring
		//实例化长度为n的环形链表
		func New(n int) *Ring
		//长度
		func (r *Ring) Len() int
		//下一个元素
		func (r *Ring) Next() *Ring
		//上一个元素
		func (r *Ring) Prev() *Ring
		//移动n次,支持负数
		func (r *Ring) Move(n int) *Ring
		//合并s和r
		func (r *Ring) Link(s *Ring) *Ring
		//删除r后面n%r.Len()元素,删除多个,当前元素前面的不删除
		func (r *Ring) Unlink(n int) *Ring
		//循环遍历,i是当前元素的值
		func (r *Ring) Do(f func(interface{}))
复制代码

代码演示

  • 实例化、赋值、遍历
	r := ring.New(3)
	for i := 0; i < r.Len(); i++ {
		r.Move(i).Value = i
	}
	r.Do(func(i interface{}) {
		fmt.Println(i)
	})
复制代码
  • 实例化后的r就是链表中第一个创建的元素.可以找到元素的前后元素
	fmt.Println(r.Next().Value)//输出:1
	fmt.Println(r.Next().Next().Value)//输出:2
	fmt.Println(r.Next().Next().Next().Value)//输出:0
	fmt.Println(r.Move(-1).Value)//输出:2
	fmt.Println(r.Prev().Value)//输出:2
复制代码
  • 可以向环形链表添加或删除链表
	s := ring.New(1)
	s.Value = 13
	//r是哪个元素,就把新的链表添加到哪个元素后面
	r.Link(s)
	r.Do(func(i interface{}) {
		fmt.Print(i, " ")
	})
	fmt.Println("")
	//从r元素向后,n/r.Len()个元素被删除,当前元素和前面的保留
	r.Unlink(1)
	r.Do(func(i interface{}) {
		fmt.Print(i, " ")
	})
复制代码

golang学习一:从环境配置开始到HelloWorld入门 golang学习二:golang自带的工具 olang学习三:golang基础语法 golang学习四:流程控制 golang学习五:常用数学函数与数组 golang学习六:for循环 golang学习七:goto和label golang学习八:切片 golang学习九:sort包、map、双向链表、双向循环链表 golang学习十:函数 golang学习十一:包的访问权限、变量作用域、闭包 golang学习十二:值传递和引用传递 golang学习十三:结构体 golang学习十四:golang中的面向对象 golang学习十五:错误异常处理 golang学习十六:文件操作 golang学习十七:反射 golang学习十八:XML操作 golang学习十九:日志 golang学习二十:golang并发编程入门 golang学习二十一:select和GC

文章分类
后端
文章标签