链表与数组同样同为线性数据结构,很多编程语言也自带了链表的实现,链表能够存放不一样数据类型的数据;
与数组不一样,数组占用内存结构必须为连续,而链表则不须要内存空间为连续的;链表由多个节点链接而成,每一个节点除了存储当前节点的值外还存有指向链表中下一个节点的地址;链表也有多种结构:单链表、双向链表、循环链表等等;
单链表的数据结构以下所示:node
经过上图能够看出链表的基本结构,每一个节点由值与地址两部分组成,地址存储链中下一个节点的地址,由此将全部 节点串联起来;golang
相比数组不须要连续的内存空间,系统存在内存碎片也可以使用链表;
如须要申请200MB大小的数组,但当前内存中没有足够大连续的内存空间,就算当前可用内存有200MB也不会申请成功;
而链表则不一样,只要可用内存有200MB就可以使用,不须要内存块为连续的它经过指针将节点(内存块)串联起来;
以前所说的动态数组其实只是伪动态当静态数组满时经过内部的resize建立一个新静态数组进行扩容,而链表为真动态;编程
一、 数组插入删除
数组为保持内存的连续性插入删除须要移动N个元素,时间复杂度为O(n)
二、 链表插入删除
链表未使用连续内存空间则也不须要移动元素,因此速度会比数组快很多;
因为链表使用的不时连续存储空间,因此不能像数组同样经过寻址公式就能访问到指定的元素,须要经过一个一个遍历每一个节点才能找到对应的节点,因此数组的随机访问时间复杂度为O(1),而链表为O(n);数组
插入删除 O(n) O(1) 随机访问 O(1) O(n)数据结构
链表的插入与删除比数组快很多,链表并不是使用连续的内存空间,不须要去维护内存连续性,就插入与删除而言双向链表又比单链表性能要好;编程语言
要在节点c后插入O节点,须要从第一个节点开始遍历链表知道找到节点c而后执行以下操做:性能
O.next = c.next c.next = O
删除节点d须要找到该节点的前一个节点,找到节点c后执行以下操做:指针
c.next = d.next d.next = null
因为单链表只存储下一个节点地址须要遍历链表节点才能找到前一个节点,而双向链表既存储了下一个节点地址又存储上一个节点地址,双向链表性能比单链表要好;code
一、如上面所说的插入元素在c节点后插入O节点,操做时须要注意要先执行O指向c的下一个节点,当一上来就执行c.next=O此时链表c节点后的节点就断开丢失了;
二、链表删除时须要注意要释放节点,如上示例:执行c.next=d.next后如未执行d.next=null此时d节点就未被释放掉,虽然链表中未有节点指向该节点,但该节点并未断开链接;
三、使用头节点简化插入、删除操做;blog
type Node struct { e interface{} next *Node } type LinkedList struct { head *Node size int } /** 往链表头添加元素 */ func (l *LinkedList) AddFirst(e interface{}) { l.Add(0, e) } func (l *LinkedList) Add(index int, e interface{}) error { if index < 0 || index > l.size { return errors.New("Add failed. Illegal index.") } prev := l.head for i := 0; i < index; i++ { prev = prev.next } prev.next = &Node{e: e, next: prev.next} l.size++ return nil } /** nil->3 2 1 查找指定索引节点 */ func (l *LinkedList) Find(index int) *Node { cur := l.head.next for i := 0; i < index; i++ { cur = cur.next } return cur } /** 删除指定位置节点 */ func (l *LinkedList) Remove(index int) error { node := l.head.next if node != nil { if prev := l.Find(index - 1); prev != nil { node = prev.next if node != nil { prev.next = node.next node.next = nil } } } return nil }