Go语言链表实现

一、数组和链表的优缺点

链表和数组作为算法中的两个基本数据结构,在程序设计过程中经常用到。尽管两种结构都可以用来存储一系列的数据,但又各有各的特点。

数组的优势,在于可以方便的遍历查找需要的数据。在查询数组指定位置(如查询数组中的第4个数据)的操作中,只需要进行1次操作即可,时间复杂度为O(1)。但是,这种时间上的便利性,是因为数组在内存中占用了连续的空间,在进行类似的查找或者遍历时,本质是指针在内存中的定向偏移。然而,当需要对数组成员进行添加和删除的操作时,数组内完成这类操作的时间复杂度则变成了O(n)。

链表的特性,使其在某些操作上比数组更加高效。例如当进行插入和删除操作时,链表操作的时间复杂度仅为O(1)。另外,因为链表在内存中不是连续存储的,所以可以充分利用内存中的碎片空间。

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

底层内存模型分别为:

二、链表类型

最常见的链表类型分别为:单链表、双向链表和循环链表。

1. 单链表

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

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


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

2. 循环链表

循环链表是一种特殊的单链表。

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

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

3. 双向链表

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

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

三、Go语言单链表实现

package linklist

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
	}
}

完整代码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值