数组和链表的优缺点
链表和数组是两种基本的数据结构,两种结构都是用来存储一系列的数据。
- 数组的优点:方便遍历查找需要的数据。在查询数组指定位置(如查询数组中第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())
}