目录
前言
最近在刷leetcode的每日一题的,总会碰到一些问题,因为竞赛时用惯了c++的STL库,有数据结构的实现,可以直接使用,到现在用go总会遇到头疼的事情就是用到数据结构的时候需要自己定义结构并且实现方法,刚好这几天笔试里面也有关于数据结构的题目,所以这一篇主要来讲一下数据结构
一、数组 (Array)
数组是一种线性数据结构,它由相同类型的元素组成,每个元素可以通过下标访问。
在Go语言中,数组是一种值类型,声明数组时必须指定长度,长度不能被修改。
// 定义一个长度为5的整型数组
var a [5]int
// 访问数组元素,下标从0开始
a[0] = 1
1、优缺点
- 优点:
- 数组的访问速度很快,因为它们是在内存中连续存储的
- 数组是一种简单、清晰的数据结构,易于理解和实现
- 缺点:
- 数组的长度是固定的,无法动态增加或缩小
- 插入或删除元素时需要移动数组中的其他元素,效率较低
2、适用场景和不适用场景
- 适用场景:
- 当需要在一个集合中存储相同类型的元素时,可以使用数组,例如存储身份证号、学号等
- 当需要快速访问数组中的元素时,可以使用数组
- 不适用场景:
- 当需要动态地添加或删除元素时,不适合使用数组
- 当需要支持不同类型的元素时,不适合使用数组
二、切片 (Slice)
切片是一个引用类型,由三个部分组成:指向底层数组的指针、切片的长度和切片的容量。
切片的底层数组包含了切片中存储的所有元素,而切片则提供了访问这些元素的方法。Go 语言中的切片是动态数组,它可以根据需要动态增长和收缩。
// 定义一个长度为0的整型切片
var b []int
// 使用 make 函数初始化一个长度为5的切片
b := make([]int, 5)
// 使用切片的 append 方法向末尾添加元素
b = append(b, 1)
// 获取切片长度
l = len(b)
// 获取切片容量
c = cap(b)
// 删除指定 index 下标的元素
b = append(b[:index], b[index+1:])
// 同理删除区间[left,right] 内的元素
b = append(b[:left], b[right+1:])
1、优缺点
- 优点:
- 切片具有动态扩容的能力,可以在运行时根据实际需求自动增加容量,并且在传递函数参数时可以避免复制整个数组
- 切片还具有可索引、可迭代、可排序等特性
- 缺点:
- 切片对内存的占用会比数组更大,因为切片会存储指向底层数组的指针、切片长度和容量等信息
- 切片的底层数组是由 Go 运行时自动分配的,因此在内存分配和垃圾回收方面可能会对性能产生影响
2、适用场景和不适用场景
- 适用场景:
- 切片适用于需要动态扩容的情况,例如读取未知长度的文件或者从网络上接收数据。由于切片可以在传递函数参数时避免复制整个数组,因此它也适用于需要在函数间传递大量数据的场景
- 不适用场景:
- 切片不适用于需要固定长度的数组场景,例如矩阵计算、游戏开发等。由于切片可能会对性能产生影响,因此在性能要求较高的场景中也需要谨慎使用
三、链表 (Linked List)
链表由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储数据,指针域用于指向下一个节点。
链表分为单向链表和双向链表,单向链表的每个节点只包含指向下一个节点的指针,而双向链表的每个节点既包含指向下一个节点的指针,也包含指向上一个节点的指针。
type ListNode struct {
Val int
Next *ListNode
}
// 在链表末尾插入一个节点
func insert(head *ListNode, val int) *ListNode {
if head == nil {
return &ListNode{
val, nil}
}
node := head
for node.Next != nil {
node = node.Next
}
node.Next = &ListNode{
val, nil}
return head
}
// 删除链表中的指定节点
func deleteNode(head *ListNode, val int) *ListNode {
if head == nil {
return nil
}
if head.Val == val {
return head.Next
}
node := head
for node.Next != nil {
if node.Next.Val == val {
node.Next = node.Next.Next
break
}
node = node.Next
}
return head
}
1、优缺点
- 优点:
- 链表可以高效地进行元素的插入和删除操作,而无需进行元素移动
- 缺点:
- 链表难以进行随机访问,并且需要额外的空间来存储指针
2、适用场景和不适用场景
- 适用场景:
- 链表适合用于需要频繁进行元素插入和删除的场景,比如实现栈、队列和哈希表等数据结构
- 不适用场景:
- 链表不适合用于需要进行随机访问的场景,比如需要根据下标获取元素的场景
四、栈(Stack)
栈是一种线性数据结构,具有后进先出(LIFO)的特点。
栈通常有两个基本操作:push(推入)和pop(弹出)。
type Stack []int
// 将元素添加到栈的顶部
func (s *Stack) Push(x int) {
*s = append(*s, x)
}
// 从栈的顶部移除元素
func (s *Stack) Pop() int {
if s.IsEmpty() {
return -1
}
x := (*s)[len(*s)-1]
*s = (*s)[