线性表
定义
线性表:零个或多个数据元素的有限序列。
序列
元素之间是有顺序
,
若存在多个元素,则第一个元素无前驱元素,最后一个元素无后继元素,每个元素有且仅有一个前驱和后继元素
。
而且元素类型也相同!
有限
线性表强调表里的元素有限。
长度
线性表元素的个数n(n≥0)定义为线性表的长度,
当n=0时,称为空表。
线性表的顺序存储结构
定义
用一段地址连续的存储单元依次存储线性表的数据元素。
关键词:
- 起始位置
存储这个线性表需要在内存中找一块地,所以这个地的起始位置很重要。
- 最大存储容量
我们需要估算线性表的最大存储容量,实现的时候,我们建立一个固定长度的数组,数组的长度就是这个线性表最大存储容量。
最大存储容量在分配后一般是不变的
,现在很多的高级语言都可以动态调整数组长度,但是这会带来性能的损耗。
- 长度
线性表当前的长度,也就是当前线性表的元素个数,随着线性表插入和删除操作的进行,这个当前长度是变化的
。
在任意时刻,线性表的长度应该小于等于最大存储容量
。
代码实现
package ArrayList
import (
"errors"
"fmt"
)
// 接口
type List interface {
// 数组大小
Size() int
// 获取
Get(index int) (interface{}, error)
// 修改
Set(index int, newval interface{}) error
// 插入
Insert(index int, newval interface{}) error
// 追加
Append(newval interface{})
// 清空
Clear()
// 删除
Delete(index int) error
// 输出字符串
String() string
}
// 数据结构
type ArrayList struct {
// 数据
data []interface{}
// 数组大小
TheSize int
}
// 初始化
func NewArrayList() *ArrayList {
// 初始化结构体
list := new(ArrayList)
// 开辟内存空间
list.data = make([]interface{}, 0, 10)
// 数组大小设为0
list.TheSize = 0
return list
}
// 数组大小
func (list *ArrayList) Size() int {
return list.TheSize
}
// 获取数据
func (list *ArrayList) Get(index int) (interface{}, error) {
// 传入无效下标
if index < 0 || index >= list.TheSize {
return nil, errors.New("索引越界")
}
// 正常返回
return list.data[index], nil
}
// 修改数据
func (list *ArrayList) Set(index int, newval interface{}) error {
// 传入无效下标
if index < 0 || index >= list.TheSize {
return errors.New("索引越界")
}
// 设置值
list.data[index] = newval
return nil
}
// 追加
func (list *ArrayList) Append(newval interface{}) {
list.data = append(list.data, newval)
list.TheSize++
}
// 插入
func (list *ArrayList) Insert(index int, newval interface{}) error {
// 传入无效下标
if index < 0 || index >= list.TheSize {
return errors.New("索引越界")
}
// 检测是否内存分配空间是否满了
list.checkIsFull()
// 内存移动一位
list.data = list.data[:list.TheSize+1]
// 从后往前移动,大于index的元素将值赋值给下一位
for i := list.TheSize; i > index; i-- {
list.data[i] = list.data[i-1]
}
// 直接插入数据
list.data[index] = newval
// 数组大小加上1
list.TheSize++
return nil
}
// 判断是否满了
func (list *ArrayList) checkIsFull() {
// cap可以获取切片分配的空间大小
if list.TheSize == cap(list.data) {
// 开辟两倍内存空间,注意第二个参数指定的是切片的长度,第三个参数是用来指定预留的空间长度
newData := make([]interface{}, 2*list.TheSize, 2*list.TheSize)
// 拷贝内容
copy(newData, list.data)
// 重新赋值
list.data = newData
}
}
// 删除
func (list *ArrayList) Delete(index int) error {
// 将删除点前后的元素连接起来
list.data = append(list.data[:index], list.data[index+1:]...)
// 数组大小减去1
list.TheSize--
return nil
}
// 清空
func (list *ArrayList) Clear() {
// 重新开辟内存空间
list.data = make([]interface{}, 0, 10)
// 数组大小设为0
list.TheSize = 0
}
// 返回字符串
func (list *ArrayList) String() string {
// Sprint将内容生成字符串
return fmt.Sprint(list.data)
}
时间复杂度
线性表的顺序存储结构,在读数据时,时间复杂度为O(1);
而在进行插入、删除操作时,时间复杂度为O(n)。
优缺点
优点:
- 无需为表中元素间的逻辑关系增加额外的存储空间
- 可以快速存取表中任一位置的元素
缺点:
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定最大存储容量
- 造成存储空间“碎片”
线性表的链式存储结构
定义
用一组任意的存储单元存储线性表的元素,这组存储单元可以存在内存未被占用的任意位置。
关键词:
- 结点
存储数据元素的域,称为数据域
;
存储直接后续位置的域,称为指针域
;指针域中存储的信息称为指针
或者链
;
数据域
和指针域
这两部分信息组成数据元素的存储映像,称为结点
。
- 头指针
链表中的第一个结点的存储位置称为头指针
,最后一个结点指针为空(Null)。
如下图所示:
头结点与头指针的异同:
- 头指针
-
链表指向第一个结点的指针,若链表有头结点,则头指针为指向头结点的指针!!!
所以上面的2张图,其实是错误的,正确的图应该是下面这样的:
-
头指针具有标示作用,所以常常冠以链表的的名字。
-
空链表的头指针指向Null。
-
无论链表是否为空,头指针均不为空。
头指针是链表的必要元素!
- 头结点
-
头结点是为了操作的统一和方便而设立的,放在第一个元素的结点前面,其数据域一般无意义。
-
有了头结点,对第一元素的结点前的插入和删除操作,就和其他结点一致了。
-
头结点不是链表的必要元素!