一、线性表概述
由 n 个相同数据元素组成的有限序列。序列:元素之间是有序的,若元素存在多个,则第一个元素没有前驱,最后一个元素没有后继,其他的元素都有且只有一个前驱和一个后继,相邻元素之间是一对一的关系
有限:数据元素的个数是有限的
二、线性表的划分
按照物理结果进行划分,线性表可以分为:顺序表和链表;顺序表是顺序结构存储,链表是链式存储结构
三、顺序表概述
用一段地址连续的存储单元依次存储线性表的数据元素
上面可以看到,在一个顺序表中,存在有长度(当前元素的个数)和容量(最大能够承受的容量);在物理存储上,它们是连续存储的,即知道第一个元素的位置,则中间任意一个元素的位置都是可以计算得到的(每个元素的内存占用大小是固定的,内存空间是连续的)
3.1、顺序表的插入
3.2、顺序表的删除
3.3、小结
对于顺序表的插入和删除,如果都是对最后一个元素进行操作,那么前面的元素都无需修改,复杂度为O(1);如果是其他的情况,则插入或删除位置后面的元素都需要进行移动,此时复杂度为O(n)
顺序表的底层是由数组实现的,逻辑关系上相邻的元素在物理结构上也是相邻的
3.4、顺序表的代码实现
访问链接:https://gitee.com/Elked/data-structures-and-algorithms
sq_list.go 代码
package sq_list
import "errors"
type SqListIn interface {
Add(data interface{}) // 新增数据
Insert(index int, data interface{}) error // 插入数据
Delete(index int) error // 删除数据
}
type SqList struct {
DataSource []interface{}
Lenth int
Cap int
}
func NewSqList() *SqList {
sqList := new(SqList)
sqList.DataSource = make([]interface{}, 0, 10)
sqList.Lenth = 0
sqList.Cap = 10
return sqList
}
func (sqList *SqList) CheckIsFull() {
if sqList.Lenth == sqList.Cap {
NewDataSource := make([]interface{}, sqList.Lenth, 2*sqList.Cap)
copy(NewDataSource, sqList.DataSource)
sqList.DataSource = NewDataSource
sqList.Cap = 2 * sqList.Cap
}
}
func (sqList *SqList) Add(data interface{}) {
sqList.CheckIsFull()
sqList.DataSource = append(sqList.DataSource, data)
sqList.Lenth++
}
func (sqList *SqList) Insert(index int, data interface{}) error {
if index < 0 || index > sqList.Lenth {
return errors.New("超界")
}
sqList.CheckIsFull()
sqList.DataSource = sqList.DataSource[:sqList.Lenth+1] // 将元素的空间开辟一个
for i := sqList.Lenth; i > index; i-- {
sqList.DataSource[i] = sqList.DataSource[i-1]
}
sqList.DataSource[index] = data
sqList.Lenth++
return nil
}
func (sqList *SqList) Delete(index int) error {
if index < 0 || index >= sqList.Lenth {
return errors.New("超界")
}
sqList.DataSource = append(sqList.DataSource[:index], sqList.DataSource[index+1:]...)
sqList.Lenth--
return nil
}
main.go 代码
package main
import (
"Data_structure_and_algorithm/day01/sq_list"
"fmt"
)
func main() {
var sqList sq_list.SqListIn = sq_list.NewSqList()
sqList.Add(3)
sqList.Add(4)
sqList.Add(5)
sqList.Add(6)
fmt.Println(sqList) // &{[3 4 5 6] 4 10}
sqList.Insert(1, 10)
fmt.Println(sqList) // &{[3 10 4 5 6] 5 10}
sqList.Delete(2)
fmt.Println(sqList) // &{[3 10 5 6] 4 10}
}
在上面的代码中,有几个点需要注意,main函数是整个函数的入口,定义的 sqList 这个变量的数据类型是 SqListIn ,这个数据类型是一个接口类型,如果要定义一个变量为接口类型,则赋值的对象必须实现该接口的全部方法
四、链表概述
线性表的顺序存储结构,最大的缺点就是插入和删除时需要移动大量的元素,这显然需要耗费时间。导致这个问题的原因是在于相邻元素的存储位置也是相邻关系,它们在内存的排布上是紧密的,中间没有空隙,自然没法快速完成插入和删除
用一组任意的存储单元存储线性表中的数据元素,其存储单元可以是连续的,也可以是不连续的
- 线性表的链式结构的特点是用一组任意的存储单元存储线性表中的数据元素,这种存储单元可以存放在内存中未被占用的任意位置
- 相比顺序存储结构,在链式存储结构中,除了需要存储数据元素信息之外,还需要存储它的后继元素的存储地址(指针)
- 数据域:存储数据元素信息的域。指针域:存储直接后继位置的域,指针域中存储的信息称为指针或链。这两部分组成数据元素称为存储印象,节点
- 链式结构:n个节点链接成一个链表,即为线性表(a1,a2,a3,...,an)
- 如果链表的每个节点中只包含一个指针域,就是单链表
4.1、链表图示
前面讲到的顺序表对于查询和更新的操作都是非常快的,但是对于删除和插入的操作则显得不是那样的完美;这里使用链表在应对频繁的插入和删除操作,效率的提升是非常明显的
4.2、链表的节点
根据前面的定义,可以知道链表的数据元素可以利用内存中未被占用的任意空间;这里的链表将不能够像前面的顺序表一样,通过下标索引来进行查询,在链表的结构中,除了存储元素数据外,还需要存储该元素下一个元素的地址信息
链表中的每个数据元素,既要存储本身的数据信息,还要存储后继信息,这两部分信息组成了链表的节点
4.3、头指针和头节点
在前面介绍的顺序表中,其底层是通过数组实现的,并使用该数组的第一个元素对应的地址位置表示该顺序表。在链表中,使用头指针指向链表的第一个元素的位置,这样通过这个头指针就能够获取到这个链表中所有元素的位置;为了对链表进行操作,有时候会在第一个节点前面加上一个节点,该节点中的数据域不存储任何数据(或者存储一些该线性表的长度等附加信息),头节点的指针域存储指向第一个节点的位置
头指针
- 头指针是指链表指向第一个节点的指针,若链表有头节点,则指向头节点的指针
- 头指针具有标识作用,所以通常使用头指针代表链表
- 无论链表是否为空,头指针均不为空
- 头指针是链表的必要元素
头节点
- 头节点是单链表前附设的一个节点
- 头节点的数据域一般不存储任何信息,也可以放一些关于线性表的长度等附加信息
- 头节点的指针域存放指向第一个节点的指针(即第一个节点的存储位置)
- 头节点不一定是链表的必要元素
4.4、单链表的代码实现
访问链接:https://gitee.com/Elked/data-structures-and-algorithms
LinkList.go
package LinkList
// 单个节点结构体
type LinkNode struct {
Data interface{} // 数据域
Next *LinkNode // 指针域
}
// 单链表结构体
type LinkList struct {
Head *LinkNode // 头节点
}
// 构造一个单链表
func NewLinkList() *LinkList {
head := new(LinkNode)
head.Data = nil
head.Next = nil
return &LinkList{head}
}
func (list *LinkList) Add(data interface{}) {
// 构造该节点的内容
addNode := &LinkNode{data,nil}
// 遍历该单链表,将该节点接到该链表中
currentNode := list.Head
for currentNode.Next != nil{
currentNode = currentNode.Next
}
currentNode.Next = addNode
}
func (list *LinkList) Insert(index int, data interface{}) {
// 构造该节点的内容
addNode := &LinkNode{data,nil}
currentNode := list.Head
for i:=0;i<=index-1;i++{
currentNode = currentNode.Next
}
addNode.Next = currentNode.Next
currentNode.Next = addNode
}
main.go
package main
import (
"Data_structure_and_algorithm/danxianglianbiao/LinkList"
"fmt"
)
func main() {
list := LinkList.NewLinkList()
list.Add("hello")
list.Add("beijing")
list.Add("shanghai")
list.Add("guangzhou")
currentNode := list.Head
for currentNode.Next != nil{
currentNode = currentNode.Next
fmt.Println(currentNode.Data)
}
list.Insert(0,"chongqin")
fmt.Println("------")
currentNode = list.Head
for currentNode.Next != nil{
currentNode = currentNode.Next
fmt.Println(currentNode.Data)
}
}
在上面的代码中,主要对每个节点的结构体的设定,最后对于这个链表,仅需要知道头节点的位置即可(其他的节点可以通过Next获取到)