Go中链表的实现

数组和链表的优缺点
链表和数组是两种基本的数据结构,两种结构都是用来存储一系列的数据。
  • 数组的优点:方便遍历查找需要的数据。在查询数组指定位置(如查询数组中第4个数据)在操作中,只需要进行1次操作即可,时间复杂度为o(1)。
  • 数组的缺点:因为数组在内存中占用了连续的空间,在进行类似的查找和遍历时,本质是指针在内存中的定向偏移。然而,当需要对数组成员进行添加和删除的操作时,数组内完成这类操作的时间复杂度则变成了O(n)
  • 链表的优点:当进行插入和删除操作时,链表操作的时间复杂度仅为O(1).另外,因为链表在内存中不是连续存储的,所以可以充分利用内存中的碎片空间。

如果我们申请一个 100MB 大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败。而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组串联起来使用,所以如果我们申请的是 100MB 大小的链表,根本不会有问题。

底层内存模型分别为:

在这里插入图片描述

链表类型
  • 最常见的链表类型为:单链表,双向链表,循环链表
单向链表

链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的结点。为了将所有的结点串联起来,每一个链表的结点处了储存数据之外,还需要记录链表上的下一个结点的地址,称为后继指针next。

在这里插入图片描述

  • 在进行数组的插入,删除操作时,为了保持内存数据的连续性,需要做大量的数据搬移,所以时间复杂的是O(n)。而在链表中插入或者删除一个数据,我们并不需要为了保持内存的连续性而搬移结点,因为表的存储空间本身就是不连续的。所以,在链表中插入和删除一个数据是非常快速的。

在这里插入图片描述
但是,有利就有弊。链表要是想随机访问第K个元素,就没有数组那么高效了。因为链表中的数据并非连续存储的,所以无法像数组那样,根据地址和下标,通过寻址公式就能直接计算出对应的内存地址,而是需要根据指针一个结点一个结点地一次遍历,直到找到相应的结点。

循环链表
  • 循环链表是一种特殊的单向链表
  • 循环链表跟单链表唯一区别在尾结点。我们知道单向链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的结点指针是指向链表头结点。从我画的循环链表图中,你应该可以看出来,它像一个环一样收尾相连,所以叫作循环链表

在这里插入图片描述
和单链表相比,循环链表的优点是从链尾到链头比较方便,当要处理的数据具有环形结构特点时,就特别适合采用循环链表。比如著名的约瑟夫问题。尽管用单链表也可以实现,但是用循环链表实现的话,代码就会简洁很多。

双向链表

单向链表只有一个方向,结点只有一个后继指针 next指后面的结点。而双向链表,顾名思义,它支持两个方向,每个方向结点不止有一个后继指针。next指向后面的结点,还有一个前驱指针prev指向前面的结点。

在这里插入图片描述
双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。

Go语言单向链表实现
package splite


import (
	"errors"
	"fmt"
)

type ListNode struct {
	data interface{}
	next *ListNode
}

type LinkList struct {
	head   *ListNode
	length uint
}

func NewLinkList() *LinkList {
	return &LinkList{
		head:   &ListNode{0, nil},
		length: 0,
	}
}

func NewLinkNode(value interface{})*ListNode{
	return &ListNode{
		data:value,
		next:nil,
	}
}

func (this *ListNode) GetNext() *ListNode {
	return this.next
}

func (this *ListNode) GetValue() interface{} {
	return this.data
}

func (this *LinkList) Insert(i uint, node *ListNode) error {
	if i < 0 || node == nil || i > this.length{
		return errors.New("index out of range or node is nil")
	}

	preItem := (*this).head
	for j := uint(0); j < i; j++ {
		preItem = preItem.next
	}

	node.next = preItem.next
	preItem.next = node

	this.length++

	return nil
}

func (this *LinkList) Delete(node *ListNode)error{
	if nil == node{
		return errors.New("node is nil")
	}

	pre := this.head
	cur := this.head.next

	for nil != cur{
		if cur.data == node.data {
			break
		}
		pre = cur
		cur = cur.next
	}

	if nil == cur{
		return errors.New("not find node")
	}

	pre.next = cur.next
	node = nil
	this.length--

	return nil
}

func (this LinkList) FindByIndex(index uint)(*ListNode,error){
	if index < 0 || index >= this.length {
		return nil,errors.New("out of range")
	}

	preItem := this.head.next
	for i := uint(0); i < index; i++ {
		preItem = preItem.next
	}

	return preItem,nil
}

func (this LinkList)Print(){
	pre := this.head.next

	for nil != pre{
		fmt.Printf("%v\n",pre.GetValue())
		pre = pre.next
	}
}
package splite

import (
	"fmt"
	"testing"
)

func TestLinkList_Insert(t *testing.T) {
	list := NewLinkList()

	for i := 0; i < 10; i++{
		list.Insert(uint(i),
			NewLinkNode(i+10))
	}

	list.Print()

	n4 := ListNode{10,nil}
	list.Insert(0,&n4)
	list.Print()
}

func TestLinkList_Delete(t *testing.T) {
	list := NewLinkList()

	for i := 0; i < 10; i++{
		list.Insert(uint(i),NewLinkNode(i+10))
	}

	list.Print()

	fmt.Println()
	list.Delete(NewLinkNode(11))
	list.Delete(NewLinkNode(10))
	list.Delete(NewLinkNode(19))


	list.Print()
}


func TestLinkList_FindByIndex(t *testing.T) {
	list := NewLinkList()

	for i := 0; i < 10; i++{
		list.Insert(uint(i),NewLinkNode(i+10))
	}

	list.Print()

	fmt.Println()

	node, err := list.FindByIndex(0)
	if err != nil {
		t.Fatal(err)
	}
	fmt.Printf("node value:%v\n",node.GetValue())
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值