《大话数据结构》学习笔记 —— 03 线性表(golang实现)


线性表


定义

线性表:零个或多个数据元素的有限序列。


序列

元素之间是有顺序

若存在多个元素,则第一个元素无前驱元素,最后一个元素无后继元素,每个元素有且仅有一个前驱和后继元素

而且元素类型也相同!


有限

线性表强调表里的元素有限。


长度

线性表元素的个数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)。

如下图所示:

《大话数据结构》学习笔记 —— 03 线性表(golang实现)_第1张图片





头结点与头指针的异同:


  • 头指针
  1. 链表指向第一个结点的指针,若链表有头结点,则头指针为指向头结点的指针!!!

    所以上面的2张图,其实是错误的,正确的图应该是下面这样的:

《大话数据结构》学习笔记 —— 03 线性表(golang实现)_第2张图片

  1. 头指针具有标示作用,所以常常冠以链表的的名字。

  2. 空链表的头指针指向Null。

  3. 无论链表是否为空,头指针均不为空。头指针是链表的必要元素!


  • 头结点
  1. 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点前面,其数据域一般无意义。

  2. 有了头结点,对第一元素的结点前的插入和删除操作,就和其他结点一致了。

  3. 头结点不是链表的必要元素!

你可能感兴趣的:(数据结构)