container包

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))
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值