最近在刷leetcode的每日一题的,总会碰到一些问题,因为竞赛时用惯了c++的STL库,有数据结构的实现,可以直接使用,到现在用go总会遇到头疼的事情就是用到数据结构的时候需要自己定义结构并且实现方法,刚好这几天笔试里面也有关于数据结构的题目,所以这一篇主要来讲一下数据结构
数组是一种线性数据结构,它由相同类型的元素组成,每个元素可以通过下标访问。
在Go语言中,数组是一种值类型,声明数组时必须指定长度,长度不能被修改。
// 定义一个长度为5的整型数组
var a [5]int// 访问数组元素,下标从0开始
a[0] = 1
切片是一个引用类型,由三个部分组成:指向底层数组的指针、切片的长度和切片的容量。
切片的底层数组包含了切片中存储的所有元素,而切片则提供了访问这些元素的方法。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:])
链表由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储数据,指针域用于指向下一个节点。
链表分为单向链表和双向链表,单向链表的每个节点只包含指向下一个节点的指针,而双向链表的每个节点既包含指向下一个节点的指针,也包含指向上一个节点的指针。
type ListNode struct {Val intNext *ListNode
}// 在链表末尾插入一个节点
func insert(head *ListNode, val int) *ListNode {if head == nil {return &ListNode{val, nil}}node := headfor 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 := headfor node.Next != nil {if node.Next.Val == val {node.Next = node.Next.Nextbreak}node = node.Next}return head
}
栈是一种线性数据结构,具有后进先出(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)[:len(*s)-1]return x
}// 栈是否为空
func (s *Stack) IsEmpty() bool {return len(*s) == 0
}// 栈大小
func (s *Stack) Size() int {return len(*s)
}// 返回栈顶元素
func (s *Stack) Top() int {if s.IsEmpty() {return -1}return (*s)[len(*s)-1]
}
队列是一种线性数据结构,它按照先进先出(FIFO)的原则存储元素,即新元素总是添加到队列的末尾,然后在队列的另一端删除旧元素。
type Queue []int// 将元素添加到队列的末尾
func (q *Queue) Enqueue(v int) {*q = append(*q, v)
}
// 从队列的头部删除元素并返回
func (q *Queue) Dequeue() int {head := (*q)[0]*q = (*q)[1:]return head
}// 检查队列是否为空
func (q *Queue) IsEmpty() bool {return len(*q) == 0
}
哈希表(Hash Table)是一种基于散列函数(Hash Function)实现的数据结构,它可以提供快速的插入、查找和删除操作。
哈希表的实现基于数组和链表,通过散列函数将键(Key)映射到数组索引上,将对应的值(Value)存储在对应的数组单元格中。哈希表的优点是高效,平均情况下的时间复杂度为O(1),适用于需要快速查找的场景。
// 创建map
m := make(map[string]int)// 向map中添加元素
m["apple"] = 1
m["banana"] = 2// 通过key来获取value
v := m["apple"]// 判断某个key是否存在
_, ok := m["orange"]
集合(Set)是一种不允许重复元素的数据结构,通常用于查找、去重、求交集、求并集等场景。
集合可以用数组、链表、哈希表等数据结构实现。
type Set struct {m map[interface{}]bool
}// 创建一个新的集合
func NewSet() *Set {return &Set{m: make(map[interface{}]bool)}
}// 添加元素到集合中
func (s *Set) Add(item interface{}) {s.m[item] = true
}// 从集合中删除元素
func (s *Set) Remove(item interface{}) {delete(s.m, item)
}// 判断集合中是否包含某个元素
func (s *Set) Contains(item interface{}) bool {_, ok := s.m[item]return ok
}// 获取集合中元素的数量
func (s *Set) Size() int {return len(s.m)
}// 将集合转换成切片
func (s *Set) ToList() []interface{} {list := make([]interface{}, 0, len(s.m))for item := range s.m {list = append(list, item)}return list
}
树是一种非线性数据结构,它由若干个节点组成,每个节点有零个或多个子节点。
树具有以下特点:
type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode
}// 创建一个新的树节点
func NewTreeNode(val int, left *TreeNode, right *TreeNode) *TreeNode {return &TreeNode{val, left, right}
}// 插入一个节点
func (node *TreeNode) Insert(val int) *TreeNode {if node == nil {return NewTreeNode(val, nil, nil)}if val < node.Val {node.Left = node.Left.Insert(val)} else {node.Right = node.Right.Insert(val)}return node
}// 查找一个节点
func (node *TreeNode) Find(val int) *TreeNode {if node == nil || node.Val == val {return node}if val < node.Val {return node.Left.Find(val)}return node.Right.Find(val)
}
堆是一种基于完全二叉树的数据结构,其中每个节点的值都大于等于(最大堆)或小于等于(最小堆)其子节点的值。
它的特点是在任何时刻,堆的根节点所存储的值都是堆中所有元素中最大或最小的。
在Go语言中,可以使用标准库中的 container/heap
包实现堆。具体实现需要实现 Heap 接口的以下方法:
// 定义一个最大堆
type MaxHeap []int// 获取堆的长度
func (h MaxHeap) Len() int { return len(h) }// 比较两个元素的大小
func (h MaxHeap) Less(i, j int) bool { return h[i] > h[j] }// 交换两个元素的位置
func (h MaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }// 把一个元素添加到堆中
func (h *MaxHeap) Push(x interface{}) {*h = append(*h, x.(int))
}// 从堆中删除一个元素
func (h *MaxHeap) Pop() interface{} {n := len(*h)x := (*h)[n-1]*h = (*h)[:n-1]return x
}
在使用时,可以通过以下方式创建一个最大堆并添加元素:
h := &MaxHeap{}
heap.Push(h, 2)
heap.Push(h, 1)
heap.Push(h, 3)// 获取最大值并弹出
fmt.Println(heap.Pop(h).(int)) // 3
也可以使用以下方法进行堆排序:
arr := []int{2, 1, 3}
h := MaxHeap(arr)
heap.Init(&h)
heap.Sort(&h)
fmt.Println(h) // [1 2 3]
图(Graph)是由节点(Vertex)和边(Edge)构成的一种数据结构,用于表示多对多的关系。
节点表示实体,边表示实体之间的关系。图可以用于模拟现实生活中各种复杂的关系网络,例如社交网络、交通网络、物流网络等。
在Go语言中,可以使用邻接矩阵或邻接表来表示图。其中邻接矩阵使用二维数组表示图,邻接表使用链表或数组来表示图。具体实现方法如下:
邻接矩阵:
type Graph struct {vertexNum int // 节点数matrix [][]int // 邻接矩阵visited []bool // 记录节点是否被访问过
}func NewGraph(vertexNum int) *Graph {matrix := make([][]int, vertexNum)for i := range matrix {matrix[i] = make([]int, vertexNum)}visited := make([]bool, vertexNum)return &Graph{vertexNum, matrix, visited}
}func (g *Graph) AddEdge(from, to, weight int) {g.matrix[from][to] = weight
}func (g *Graph) DFS(start int) {g.visited[start] = truefmt.Println(start)for i := 0; i < g.vertexNum; i++ {if !g.visited[i] && g.matrix[start][i] != 0 {g.DFS(i)}}
}func (g *Graph) BFS(start int) {queue := []int{start}g.visited[start] = truefor len(queue) > 0 {node := queue[0]queue = queue[1:]fmt.Println(node)for i := 0; i < g.vertexNum; i++ {if !g.visited[i] && g.matrix[node][i] != 0 {g.visited[i] = truequeue = append(queue, i)}}}
}
邻接表:
type Graph struct {nodes []*Node
}type Node struct {val intnext *Node
}func NewNode(val int) *Node {return &Node{val: val, next: nil}
}func (n *Node) AddNode(val int) {temp := nfor temp.next != nil {temp = temp.next}temp.next = NewNode(val)
}func NewGraph() *Graph {return &Graph{nodes: []*Node{}}
}func (g *Graph) AddNode(val int) {g.nodes = append(g.nodes, NewNode(val))
}func (g *Graph) AddEdge(src, dst int) {g.nodes[src].AddNode(dst)g.nodes[dst].AddNode(src)
}func (g *Graph) Print() {for i, node := range g.nodes {fmt.Printf("Node %d: ", i)temp := nodefor temp != nil {fmt.Printf("%d ", temp.val)temp = temp.next}fmt.Printf("\n")}
}