6、线性数据结构-数组

线性数据结构

线性表

  • 线性表,表,有序序列,放元素,是一种有序的放元素的容器,抽象概念数学概念
    有序就可以编号,可以有索引
  • 顺序表:使用一大块连续的内存顺序存储表中的元素,这样实现的表称为顺序表,或称连续表
    • 在顺序表中,元素的关系使用顺序表的存储顺序自然地表示
  • 链接表:在存储空间中将分散存储的元素链接起来,这种实现称为链接表,简称链表

顺序表

​ 使用连续的内存单元存储该序列的所有元素,内存的顺序就是数据顺序

​ 例如:排好的队伍,数组Array

​ 开辟连续的内存空间,容器占用的内存称为 容量cap,可以最多容纳多少个元素

​ 当前容器目前的元素个数,称为长度len,增加元素+1,删除元素-1,用一个元数据记录

​ 顺序表需要开辟连续内存空间,变量指向顺序表的第一个元素地址

​ 变量指向顺序表A = [1, 1.5, 2.1, 3, 4] A这个标识符指向数组首地址

​ 顺序sequence不是排序sort

从CDUR四个方面来分析顺序表

C

容器元素个数 + 1
			append如同排队,在队尾后增加即可
			insert插队
				最尾巴后插队,就是append
				中间插队,占用当前位置,把当前位置与其后所有数据后移
				队首插队,所有元素统统后移
				挪动数据是要耗时间的,挪动的元素越多(规模越大),代价越大

D

容器元素个数 - 1
			pop队尾元素移除,影响最小
			remove
				如果是队尾,就是pop
				中间离队,后面数据统统前移
				队首离队,后面数据统统前移
				挪动数据是要耗时间的

U

元素个数len 不变
			定位问题
			更新内容

R

定位问题:
		使用索引,首地址 + 该类型的字节数 * 偏移,所以,定位要用索引计算得到元素的内存地址,不用遍历,效率极高。
		顺序表不管有多少个元素,都是一个四则元素公式直接推出来
		使用索引计算与规模n无关,使用固定的步骤就可以计算出元素的位置,O(2) 简写为 O(1)
		如果使用内容定位,内容比较,遍历的方式挨个比较内容,效率低下。最不幸,遍历完了,才知道没有
		获取内容:使用索引直接定位该位置,拿走内容
		遍历:容器中的元素,不管有没有顺序,我们都要不重复的将所有元素挨个摸一遍。
		首地址开始,挨个偏移取内容

前提:要数据规模,如果数据规模小,随便你玩。数据规模大,都是事
顺序表适合仅仅在尾部增删
扩容问题

链接表

​ 每一个元素存储在内存中,但是元素并不是连续的存储在内存中,散落内存的不同位置,前一个元素指向下一个元素(链接),是一种手拉手的状态。

​ 链表是个容器,可以放元素。定义一个变量指向链表第一个元素地址即可

​ cap指的是当前放了多少个元素,和len一致的,不需要提前开辟内存空间

​ 链表的长度,同顺序表

​ 单向:前一个元素指向下一个元素

​ 双向:前一个元素指向下一个元素,下一个元素指向上一个元素

从CDUR四个方面来分析链接表

CRUD 遍历,Create增,Read 读取,Update 改,Delete删

C

容器元素个数 + 1
			尾部追加,原来的尾部指向新尾巴,容器改tail。没有数据挪动,很快
			中间插入,断开原来的链接,分别和新元素拉手,没有数据挪动,代码不大
			首部插入,原来的头和新头互指,容器改head,没有数据挪动,代价也不高
			如果数据规模很大,请问链表新增元素代价大吗?

D

容器元素个数 - 1
			尾部删除,用tail定位尾部,当前尾部的前一个成为新的tail,删除旧tail的元素
			中间删除,遍历定位,当前元素删除,前驱、后继拉手即可
			首部删除,用head定位首部,当前head的下一个元素成为新head,删除旧的head的元素

U

元素个数len 不变
			定位问题
			更新内容

R

定位问题:
		使用索引,由于元素散落在内存中,不能使用顺序表公式来定位,找到开头,依次编索引。
		按照遍历元素的方式来找的,但是取的每一个节点中保存的下一个地址,使用该地址定位下一个元素O(n)
		和顺序表比起来,略慢,但是由于都是使用地址,效率还不错
		使用内容查找定位,需要遍历查找,效率低下
		获取内容:定位到了,取走内容即可
		遍历,同上

我手里有一个内容,我想问该线性表中有没有?请问用什么物理存储合适?就不该使用线性表,都不好,因为是遍历。如果只有5个元素,随便你。如果规模很大呢?索引规模大就不要用线性表。用map

我手里知道索引,请问用什么线性表存储?顺序表更快定位

我手里有索引,我要使用索引插入数据或删除数据,用什么?链接表更合适。如果删除或增加都集中在尾部,更适合用顺序表,但是数据增加较多的话,顺序表开始扩容较为频繁,不如使用链接表。

操作是增删随意且需要经常使用索引定位数据,用什么?如果数据规模小,且增删较少,使用索引定位多,可以考虑使用顺序表。但是规模大增删操作随意且多,就只能使用链接表。

数组

容器不可变,容器的元素个数不能变了。数组必须给出长度,以后数组这个容器不能增删元素了,不能扩容了。长度是死的,必须定义时给出

定义数组

func main() {
	// 注意下面2种区别
	var a0 [3]int		// 零值初始化3个元素的数组
	var a1 = [3]int{}	// 零值初始化3个元素的数组
	fmt.Println(a0, a1)
}
[0 0 0] [0 0 0]

func main() {
	var a2 [3]int = [3]int{1, 3, 5} // 声明且初始化,不推荐,啰嗦
	var a3 = [3]int{1, 3, 5}        // 声明且初始化,推荐
	fmt.Println(a2, a3)
}
[1 3 5] [1 3 5]

package main

import "fmt"

func main() {
	a5 := [...]int{10, 30, 50} // ...让编译器确定当前数组大小
	fmt.Printf("%T %[1]d", a5)
}
[3]int [10 30 50]

func main() {
	a6 := [5]int{100, 200}       // 顺序初始化前面的,其余用零值填充
	a7 := [5]int{1: 300, 3: 400} // 指定索引位置初始化,其余用零值填充
	fmt.Println(a6, a7)
}
[100 200 0 0 0] [0 300 0 400 0]

二维数组
func main() {
	a8 := [2][3]int{{100}} // 两行三列
	fmt.Println(a8)
}
[[100 0 0] [0 0 0]]

// 多维数组,只有第一维才能用...推测
// 第一维有4个,第二维有3个。可以看做4行3列的表


func main() {
	a9 := [...][3]int{{10}, {11, 12}, {13, 14, 15}, {16}}
	fmt.Println(a9)
}
[[10 0 0] [11 12 0] [13 14 15] [16 0 0]]
长度和容量

​ cap即capacity,容量,表示给数组分配的内存空间可以容纳多少个元素

​ len即length,长度,指的是容器中目前有几个元素

由于数组创建时就必须确定的元素个数,且不能改变长度,所以不需要预留多余的内存空间,因此cap和len对数组来说相等

索引

​ Go语言不支持负索引。通过[index]来获取该位置上的值。索引范围就是[0, 长度-1]

​ 0到len(array)-1,使用索引是最快定位方式了

修改
func main() {
	a10 := [...]int{10, 30, 50}
	a10[0] = 100
	fmt.Println(a10)
}
[100 30 50]

func main() {
	a10 := [...]int{10, 30, 50}
	a10[0] += 100
	fmt.Println(a10)
}
[110 30 50]
遍历

索引遍历

func main() {
	a11 := [...]int{10, 20, 30}

	for i := 0; i < len(a11); i++ {
		fmt.Println(i, a11[i])
	}
}
0 10
1 20
2 30

for-range遍历

func main() {
	a11 := [...]int{10, 20, 30}

	for i, v := range a11 { // i是索引,v是元素值
		fmt.Println(i, v, a11[i])
	}
}
0 10 10
1 20 20
2 30 30

func main() {
	a11 := [...]int{10, 20, 30}

	for i := range a11 { // i是索引
		fmt.Println(i, a11[i])
	}
}
0 10 
1 20 
2 30 
内存模型
func main() {
	var a [3]int	// 内存开辟空间存放长度为3的数组,零值填充
	for i := 0; i < len(a); i++ {
		fmt.Println(i, a[i], &a[i])
	}
}
0 0 0xc000012150
1 0 0xc000012158
2 0 0xc000012160

fmt.Printf("%p %p %v\n", &a, &a[0], a)
0xc000012150 0xc000012150 [0 0 0]

a[0] = 1000
fmt.Printf("%p %p, %v\n", &a, &a[0], a)
0xc000126060 0xc000126060, [1000 0 0]

结论:数组必须在编译时就确定长度,之后不能改变长度

*数组首地址就是数组地址*

所有元素一个接一个顺序存储在内存中

元素的值可以改变,但是元素地址不变

上面每个元素间隔8个字节,正好64位,符合int类型定义

字符串类型
var a = [3]string{"abc", "def", "xyz"} // 内存开辟空间存放长度为3的数组
for i := 0; i < len(a); i++ {
fmt.Println(i, a[i], &a[i])
}
fmt.Printf("%p %p, %v\n", &a, &a[0], a)
a[0] = "oooooo"
fmt.Printf("%p %p, %v\n", &a, &a[0], a)
运行结果
0 abc 0xc000138480
1 def 0xc000138490
2 xyz 0xc0001384a0
0xc000138480 0xc000138480, [abc def xyz]
0xc000138480 0xc000138480, [oooooo def xyz]

每个元素间隔16个字节,为什么?"abc"是几个字节?这说明什么?

字符串字面常量,一旦定义不可改变,不同的字符串长度不一,数组采用string为元素,间隔怎么办?测试后发现元素存储空间间隔一样,都是16字节,说明字符串复杂一些。16个字节里面放了一个指针,指向字符串的内存位置

相关推荐
码蜂窝编程官方2 分钟前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
hccee15 分钟前
C# IO文件操作
开发语言·c#
Viktor_Ye18 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm20 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
一二小选手24 分钟前
【Maven】IDEA创建Maven项目 Maven配置
java·maven
J老熊30 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java35 分钟前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
AuroraI'ncoding36 分钟前
时间请求参数、响应
java·后端·spring
寻找码源40 分钟前
【头歌实训:利用kmp算法求子串在主串中不重叠出现的次数】
c语言·数据结构·算法·字符串·kmp
zmd-zk44 分钟前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink