container/list
链表一个很大的优点:插入快,删除快。而数组的有优点就是遍历快,索引快。
故链表适合于那些频繁插入删除操作的场景。数组适合于那些多次查询的场景。
golang语言的链表实现在标准库container/list中。
使用案例:作为构造队列、栈的基础数据结构。
- list包含两个公开的程序实体,List和Element;是一个带哨兵头节点的双向链表
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
}
type Element struct {
next, prev *Element
list *List // The list to which this element belongs.
Value interface{} // The value stored with this element.
}
- 比较list和slice的创建、插入、遍历和删除
const IteratorNum int = 10000000
func CreateSliceAndListCmp() {
// slice 创建
t := time.Now()
slice := make([]int, 10)
for i := 0; i < 1*IteratorNum; i++ {
slice = append(slice, i)
}
fmt.Println("slice 创建成功:", time.Now().Sub(t).String())
// list创建添加
t = time.Now()
l := list.New()
for i := 0; i < 1*IteratorNum; i++ {
l.PushBack(i)
}
fmt.Println("list创建成功:", time.Now().Sub(t).String())
}
func IteratorListAndSliceCmp() {
sli := make([]int, 10)
for i := 0; i < 1*IteratorNum; i++ {
sli = append(sli, 1)
}
l := list.New()
for i := 0; i < 1*IteratorNum; i++ {
l.PushBack(1)
}
// 比较遍历
t := time.Now()
for _, _ = range sli {
}
fmt.Println("遍历slice的速度:" + time.Now().Sub(t).String())
t = time.Now()
for e := l.Front(); e != nil; e = e.Next() {
}
fmt.Println("遍历list的速度:" + time.Now().Sub(t).String())
}
func InsertListAndSliceCmp() {
sli := make([]int, 10)
for i := 0; i < 1*IteratorNum; i++ {
sli = append(sli, 1)
}
l := list.New()
for i := 0; i < 1*IteratorNum; i++ {
l.PushBack(1)
}
//比较插入
t := time.Now()
halfNum := int(IteratorNum / 2)
slif := sli[:halfNum]
slib := sli[halfNum:]
slif = append(slif, 10)
slif = append(slif, slib...) //测试slice元素移动和拷贝
fmt.Println("slice 的插入速度" + time.Now().Sub(t).String())
var em *list.Element
len := l.Len()
var i int
//查找中间元素
for e := l.Front(); e != nil; e = e.Next() {
i++
if i == len/2 {
em = e
break
}
}
t = time.Now()
ef := l.PushBack(2)
l.MoveBefore(ef, em) //测试list元素移动
fmt.Println("list: " + time.Now().Sub(t).String())
}
func DeleteSliceElemAndListElemCmp() {
sli := make([]int, 10)
for i := 0; i < 1*IteratorNum; i++ {
sli = append(sli, 1)
}
l := list.New()
for i := 0; i < 1*IteratorNum; i++ {
l.PushBack(1)
}
//比较删除
halfNum := int(IteratorNum / 2)
t := time.Now()
sli = append(sli[:halfNum],sli[halfNum + 1:]...) //删除中间元素
fmt.Println("slice 删除速度:", time.Now().Sub(t).String())
var e = &list.Element{}
var j = 0
for i := l.Front(); i != nil; i = i.Next(){
if j == l.Len()/2 {
e = i
break
}
}
t1 := time.Now()
l.Remove(e) //删除中间元素
fmt.Println("list 删除速度:", time.Now().Sub(t1).String())
}
- list在内部就是一个双向循环链表
习题1:如下代码输出是否正常?会不会死循环?
func IterateList() {
var l list.List
for i := 0; i < 1; i++ {
l.PushBack(i)
}
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value.(int))
}
}
- 默认不是线程安全的
习题2:以下代码输出什么?
var l list.List
func main() {
for i := 0; i < 10; i++ {
go func() {
l.PushBack(i)
}()
}
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
}
- 不可以把自己生成的Element类型值传给list;在list包含的方法中,只接受interface{}类型的值
- PushBackList((other *List)方法,也只是用other中元素的值,重新构造Element元素
- list是开箱即用的,因为它的‘延迟初始化’机制
习题3:以下代码会报错吗?若有错,是运行时错误,还是编译时错误?若不错,输出什么?
func main() {
var l list.List
fmt.Println("before pushback:", l)
l.PushBack(1)
fmt.Println("after pushback:", l)
}
“延迟初始化”:把初始化操作延后,仅在实际需要的时候才进行。优点在于“延后”,它可以分散初始化操作带来的计算量和存储空间消耗。
- 方法列表
func (e *Element) Next() *Element //返回该元素的下一个元素,若无,返回nil
func (e *Element) Prev() *Element//返回该元素的前一个元素,若无,返回nil。
func New() *List //返回一个初始化的list,长度为0
func (l *List) Back() *Element //获取list l的最后一个元素
func (l *List) Front() *Element //获取list l的第一个元素
func (l *List) Init() *List //list l初始化或者清除list l
func (l *List) InsertAfter(v interface{}, mark *Element) *Element
//在list l中元素mark之后插入一个值为v的元素,并返回该元素,
//若mark不是list中元素,不做任何操作。
func (l *List) InsertBefore(v interface{}, mark *Element) *Element
//在list l中元素mark之前插入一个值为v的元素,并返回该元素,
//若mark不是list中元素,不做任何操作。
func (l *List) Len() int //获取list l的长度
func (l *List) MoveAfter(e, mark *Element)
//将元素e移动到元素mark之后,若元素e或者mark不属于list l,或者e==mark,则list l不变。
func (l *List) MoveBefore(e, mark *Element)
//将元素e移动到元素mark之前,如果元素e或者mark不属于list l,或者e==mark,则list l不变。
func (l *List) MoveToBack(e *Element)//将元素e移动到list l的末尾,若e不属于list l,则list不变。
func (l *List) MoveToFront(e *Element)//将元素e移动到list l的首部,若e不属于list l,则list不变。
func (l *List) PushBack(v interface{}) *Element//在list l的末尾插入值为v的元素,并返回该元素。
func (l *List) PushBackList(other *List)//在list l的尾部插入另外一个list,其中l和other可以相等。
func (l *List) PushFront(v interface{}) *Element//在list l的首部插入值为v的元素,并返回该元素。
func (l *List) PushFrontList(other *List)//在list l的首部插入另外一个list,其中l和other可以相等。
func (l *List) Remove(e *Element) interface{}//如果元素e属于list l,将其从list中删除,并返回元素e的值。
方法实践
- 习题4:如下代码运行正常吗?输出什么?
func main() {
//初始化一个list
l := list.New()
for i := 1; i < 5; i++ {
l.PushBack(i)
}
fmt.Println("Before Removing...")
//遍历list的同时,删除满足条件的元素
for e := l.Front(); e != nil; e = e.Next() {
v := e.Value.(int)
if v == 1 || v == 3 {
fmt.Println("removing", e.Value)
l.Remove(e)
}
}
fmt.Println("After Removing...")
//遍历删除完元素后的list
for e := l.Front(); e != nil; e = e.Next() {
fmt.Printf("%d ", e.Value)
}
}
那如下代码输出什么?
func main() {
l := list.New()
for i := 1; i < 5; i++ {
l.PushBack(i)
}
fmt.Println("Before Removing...")
var n *list.Element
for e := l.Front(); e != nil; e = n {
fmt.Println("removing", e.Value)
n = e.Next()
l.Remove(e)
}
fmt.Println("After Removing...")
for e := l.Front(); e != nil; e = e.Next() {
fmt.Printf("%d ", e.Value)
}
}
- 习题5:如何对list排序?
type Student struct {
Name string
Age int
}
//冒泡排序,是相邻元素比较,相邻元素互换位置
//改变原来链表
func ListSortByAge(old *list.List) (newList *list.List) {
length := old.Len()
if length <= 1 {
return old
}
for i := 0; i < length-1; i++ {
for j := 0; j < length-1-i; j++ {
e1 := old.Front()
//找到要比较的相邻元素
for k := 0; k < j; k++ {
e1 = e1.Next()
}
e2 := e1.Next()
if e1.Value.(Student).Age > e2.Value.(Student).Age {
old.MoveBefore(e2, e1) //相邻元素互换位置
}
//IteratorStudentList(old)
}
//fmt.Println()
}
return old
}
//不改变原有链表
//插入排序,在有序元素列上,做插入元素操作
func SortListAndDontChangeOldList(old *list.List) *list.List {
newList := list.New()
for v := old.Front(); v != nil; v = v.Next() {
node := newList.Front() //从头开始遍历新链表
for nil != node {
//新链表是有序的,在有序链表上,找到第一个比v值大的,再把v查到该元素前
if node.Value.(Student).Age > v.Value.(Student).Age {
newList.InsertBefore(v.Value.(Student), node)
break
}
node = node.Next()
}
if node == nil {
newList.PushBack(v.Value.(Student))
}
}
return newList
}
func CreateStudentList() *list.List {
var l list.List
l.PushBack(Student{"zhu yi", 1})
l.PushBack(Student{"li si", 4})
l.PushBack(Student{"wang wu", 5})
l.PushBack(Student{"niu er", 2})
l.PushBack(Student{"zhang wu", 5})
l.PushBack(Student{"tian yi", 1})
l.PushBack(Student{"wang er", 2})
l.PushBack(Student{"zhang san", 3})
return &l
}
func IteratorStudentList(l *list.List) {
for e := l.Front(); e != nil; e = e.Next() {
v := e.Value.(Student)
fmt.Printf("%s_%d ", v.Name, v.Age)
}
fmt.Println()
}
func main() {
old := CreateStudentList()
fmt.Println("排序前,老的list:")
IteratorStudentList(old)
fmt.Println()
fmt.Println("插入排序后, 产生新的list:")
IteratorStudentList(SortListAndDontChangeOldList(old))
fmt.Println()
fmt.Println("插入排序后,老的list不变:")
IteratorStudentList(old)
fmt.Println()
fmt.Println("冒泡排序后,改变老的list:")
IteratorStudentList(ListSortByAge(old))
}
container/ring
container/ring底层包含一个公开的程序实体Ring,内部实现是一个双向循环链表。
使用案例:构造定长环回队列,比如保存用户最近10次操作
- 循环链表一旦被创建,长度不可变?ring.Link(r *Ring)???
- 方法列表
func New(n int) *Ring //创建一个长度为n的环形链表
func (r *Ring) Do(f func(interface{})) //对链表中任意元素执行f操作,如果f改变了r,则该操作造成的后果是不可预期的。
func (r *Ring) Len() int //求环长度,返回环中元素数量
func (r *Ring) Link(s *Ring) *Ring //Link连接r和s,并返回r原本的后继元素r.Next()。r不能为空。
//如果r和s指向同一个环形链表,则会删除掉r和s之间的元素,删掉的元素构成一个子链表,返回
//指向该子链表的指针(r的原后继元素);如果没有删除元素,则仍然返回r的原后继元素,而不
//是nil。如果r和s指向不同的链表,将创建一个单独的链表,将s指向的链表插入r后面,返回原s
//最后一个元素后面的元素(即r的现后继元素)。
func (r *Ring) Unlink(n int) *Ring
//删除链表中n % r.Len()个元素,从r.Next()开始删除。如果n % r.Len() == 0,不修改r。
//返回删除的元素构成的链表,r不能为空。
func (r *Ring) Move(n int) *Ring //返回移动n个位置(n>=0向前移动,n<0向后移动)后的元素,r不能为空。
func (r *Ring) Next() *Ring //获取当前元素的下个元素
func (r *Ring) Prev() *Ring //获取当前元素的上个元素
方法实践
- 习题1:如下代码输出什么?
func set(i interface{}) {
i = 1
}
func do1() {
var r ring.Ring
r.Do(set) //不会改变*r的值
r.Do(func(i interface{}) {
fmt.Println(i)
})
}
func do2() {
var r = ring.New(1)
r.Value = 1
r.Do(func(i interface{}) {
fmt.Println(i)
})
}
- 习题2:遍历ring,ring.Do方法
func CreateRing(i int) *ring.Ring {
r := ring.New(i)
for j := 0; j < i; j++ {
r.Value = j
r = r.Next()
}
return r
}
func IterateRing(r *ring.Ring) {
r.Do(func(i interface{}) {
fmt.Println(i)
})
}
- 习题3:r.Link(s)方法
func createRing1(i int, t int) *ring.Ring {
r := ring.New(i)
for j := 0; j < i; j++ {
r.Value = j + t
r = r.Next()
}
return r
}
func iterateRing(r *ring.Ring) {
r.Do(func(i interface{}) {
fmt.Printf("%d ", i)
})
fmt.Println()
}
//ring.Unlink(s)和ring.Link(s)方法返回ring.Next()
func main() {
r1 := createRing1(5, 0)
fmt.Println("原始r1为:") //0 1 2 3 4
iterateRing(r1)
r2 := createRing1(5, 5)
fmt.Println("原始r2为:")//5 6 7 8 9
iterateRing(r2)
temp := r1.Link(r1.Move(2)) //删除r1和r1+2之间的元素
fmt.Println("删除r1和r1+2之间元素后,r1为:") //0 2 3 4
iterateRing(r1)
fmt.Println("删除r1和r1+2之间元素后,返回的temp为:")//1
iterateRing(temp)
r3 := r1.Link(r2) //r1链接r2,返回的是r指向原始r1.Next()
fmt.Println("r1链接r2后,r1为:")//0 5 6 7 8 9 2 3 4
iterateRing(r1)
fmt.Println("r1链接r2后,返回的r3为:")//2 3 4 0 5 6 7 8 9
iterateRing(r3)
r4 := r1.Unlink(r1.Len())
fmt.Println("r1 unlink r1.Len个元素后,r1为:")//0 5 6 7 8 9 2 3 4
iterateRing(r1)
fmt.Println("r1 unlink r1.Len个元素后,返回的r4为:")//5 6 7 8 9 2 3 4 0
iterateRing(r4)
r6 := r1.Unlink(3)
fmt.Println("r1 unlink 3个元素后,r1为:")//0 8 9 2 3 4
iterateRing(r1)
fmt.Println("r1 unlink 3个元素后,返回的r6为:")//5 6 7
iterateRing(r6)
}
- 练习:约瑟夫环问题
type Player struct {
num int //编号
alive bool //是否活着
}
const (
PlayerNum int = 41 //玩家总数
DeadNum int = 3 //死亡数字
StartNum int = 1 //起始位置编号
)
//约瑟夫环问题,返回最终活着的人编号
func joseph() int {
//初始化一个双向循环链表
var r = ring.New(PlayerNum)
for i := 0; i < PlayerNum; i++ {
r.Value = &Player{
num: i + 1,
alive: true,
}
r = r.Next()
}
aliveNum := PlayerNum //活着的人数
if StartNum > 1 { //起始位置不一定是编号为1
r = r.Move(StartNum - 1)
}
count := 1 //计数器
for aliveNum > 1 { //不是只剩一个人,游戏就继续
for count < DeadNum {
r = r.Next()
if r.Value.(*Player).alive { //如果活着就报数
count++
}
}
//出了for循环,表明找到活着的Player,并且其报数为deadNum
r.Value.(*Player).alive = false
aliveNum--
fmt.Println("Player ", r.Value.(*Player).num, "dead")
for r.Value.(*Player).alive == false { //找到下一个活着的Player,作为起点
r = r.Next()
}
count = 1
}
return r.Value.(*Player).num
}
func main() {
fmt.Println("最终活下来的家伙编号为:", joseph())
}
container/list和ring区别
- list带哨兵头节点,而ring没有
- list的Len()方法时间复杂度为O(1),而ring为O(n)
- var r ring.Ring语句声明的r,r是一个长度为1的链表,而类似的List生命,得到一个长度为0的链表,哨兵节点不计算在长度中
- 创建初始化一个Ring时,可以指定元素数量,而List不能
- Ring类型的数据结构仅由它自身代表,而List类型由它以及Element类型联系表示
container/heap
堆
- 堆是具有以下性质的完全二叉树或近似完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。图示如下:
假设数组 a[8]={100,33,3,7,11,6,8,5},抽象为完全二叉树为:
若父节点为i,其左右节点的下标分别为:(2*i+1)和 (2*i+2)。
若孩子节点的下标为j的话,那么其父节点的下标为(j-1)/2。 - 堆排序
//堆排序
//1.从一个节点往下调整
//2.构造堆:其实就是堆length/2 - 1那个节点开始直到0,都执行往下调整操作
//3.排序:取堆顶元素与length-i替换,再做调整
//构造大顶堆
func Less(i, j int) bool {
return i > j
}
func HeapAjustFromOneNode(h []int, i0, length int) bool {
i := i0
for { //循环从i0节点开始往下调整
j1 := 2*i + 1 //左孩子
if j1 >= length || j1 < 0 {
break
}
j := j1
if j2 := j1 + 1; j2 < length && Less(h[j2], h[j1]) {
j = j2 //右孩子更小,就换右孩子
}
if !Less(h[j], h[i]) {
break
}
h[j], h[i] = h[i], h[j] //swap操作
i = j //i更新为下交换后的节点下标
}
return i > i0 //判断本次从i0节点开始向下调整,是否有交换操作
}
func GenHeap(h []int) {
length := len(h) // Len()方法
i := length/2 - 1 //最初开始往下调整的非叶子节点
for ; i >= 0; i-- {
HeapAjustFromOneNode(h, i, length)
}
}
func HeapSort(h []int) {
GenHeap(h) //先构造一个小顶堆,目前堆顶元素已经是最小元素
length := len(h)
for i := 0; i < length; i++ {
//fmt.Println(h[0]) //堆顶元素即是h[0]
h[0], h[length-i-1] = h[length-i-1], h[0] //堆顶,即h[0]和length - i-1元素互换
HeapAjustFromOneNode(h, 0, length-i-1) //i+1个元素已经有序
}
}
func PrintSlice(h []int) {
length := len(h)
for i := 0; i < length; i++ {
fmt.Printf("%d ", h[i])
}
fmt.Println()
}
func main() {
h := []int{2, 1, 5, 100, 3, 6, 4, 5}
fmt.Println("排序前:")
PrintSlice(h)
HeapSort(h)
fmt.Println("排序后:")
PrintSlice(h)
}
heap包
- heap包为任何实现了heap.interface接口的类型提供堆操作。
- 底层结构:
// heap.Interface
type Interface interface {
sort.Interface
Push(x interface{}) // 在Len()位置插入一个元素
Pop() interface{} // 删除并返回Len()-1位置的元素
}
// sort.Interface
type Interface interface {
Len() // 获取当前对象的长度
Swap(i, j interface{}) // 交换i,j位置两个元素的位置
Less(i, j interface{}) // 比较i位置元素的值是否小于j位置元素
}
- 堆中的最小元素是索引0处的根。
- 堆是实现优先级队列的常用方法。若要构建优先级队列,以优先级作为Less方法顺序。
- 方法列表:
func Init(h Interface) //用于堆初始化,接受一个实现了heap.Interface的对象,
//并初始化为一个堆,所有的堆在使用之前都需要进行初始化
func Push(h Interface, x interface{})//向堆中添加一个元素,内部会调用h.Push()方法
func Pop(h Interface) interface{}//删除堆顶元素,并且调整,保持堆结构不变
func down(h Interface, i0, n int) bool //从i0节点开始向下调整,返回bool来显示是否有调整
func up(h Interface, j int)//从j节点开始,向上调整
func Fix(h Interface, i int)//当i节点有修改时,Fix用来保持堆的结构
func Remove(h Interface, i int) interface{}//删除i号元素,并保持堆结构
方法详解
- down(h Interface, i0, n int) bool方法
可以看出,down方法就是上边堆排序中的,从i0节点乡下调整的方法。
func down(h Interface, i0, n int) bool {
i := i0
for {
j1 := 2*i + 1 //左孩子
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
j := j1 // left child
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) { //看是否有右孩子,并且右孩子比左孩子更合适交换
j = j2 // = 2*i + 2 // right child
}
if !h.Less(j, i) { //如果没有交换就直接退出,因为调用down方法,有两种情况
//一个是在h已经是堆结构后,fix等方法调用;
//一个Init方法中,从length/2 - 1这个最右边非叶子节点开始调整构造堆结构
break
}
h.Swap(i, j)
i = j //从交换位置,继续向下调整
}
return i > i0 //在fix等函数中,能用来判断是否需要进行向上调整
//因为h已经是堆结构,如果改了i节点上的值,却发现向下调整时,若有交换操作
//那么就无需做向上调整操作
}
- up(h Interface, j int)方法
func up(h Interface, j int) {
for {
i := (j - 1) / 2 //父节点
if i == j || !h.Less(j, i) {
break
}
h.Swap(i, j)
j = i //更新节点为父节点
}
}
练习
//利用heap实现数据排序
type IntHeap []int
func (ih *IntHeap) Len() int {
return len(*ih)
}
func (ih *IntHeap) Less(i, j int) bool {
return (*ih)[i] < (*ih)[j]
}
func (ih *IntHeap) Swap(i, j int) {
(*ih)[i], (*ih)[j] = (*ih)[j], (*ih)[i]
}
func (ih *IntHeap) Push(x interface{}) {
*ih = append(*ih, x.(int))
}
func (ih *IntHeap) Pop() interface{} {
length := ih.Len()
x := (*ih)[length-1]
*ih = (*ih)[:length-1]
return x
}
func main() {
h := &IntHeap{2, 1, 5, 100, 3, 6, 4, 5}
//注意调用的是heap包的Init、Push、Pop等函数
heap.Init(h) //仅仅构建一个堆
heap.Push(h, 3) //插入元素后,保持堆的属性
for i := 0; i < len(*h); i++ { //并不是堆排序
fmt.Println((*h)[i])
}
(*h)[3] = 0
heap.Fix(h, 3) //若没有这行,下面输出什么?
fmt.Printf("minimum: %d\n", (*h)[0])
for h.Len() > 0 {
fmt.Printf("%d ", heap.Pop(h))
}
}