常见的数据结构有:数组、链表、栈、队列、图、哈希表;
用于存储和处理一组固定大小、相同类型的数据,如存储学生成绩、数组排序等。Go 语言中的数组长度是固定的,在声明时需要指定长度。
特点:
func main() {
// 定义一个长度为5的整数数组
var arr [5]int = [5]int{1, 2, 3, 4, 5}
// 遍历数组并打印元素
for _, v := range arr {
fmt.Println(v)
}
}
时间复杂度O(1):
表示算法的执行时间与输入规模无关,是一个常数。无论输入数据的规模如何增大,算法执行所需的时间都保持不变。
可用于实现队列、栈等数据结构,也可用于解决一些需要频繁插入和删除元素的问题,如内存管理中的空闲链表。在 Go 语言中,可以通过自定义结构体和指针来实现链表。
特点:
package main
import (
"fmt"
)
// 定义链表节点结构体
type ListNode struct {
Val int
Next *ListNode
}
// 定义链表结构体
type LinkedList struct {
Head *ListNode
}
// 在链表末尾插入新节点
func (l *LinkedList) Insert(val int) {
newNode := &ListNode{Val: val, Next: nil}
if l.Head == nil {
l.Head = newNode
return
}
current := l.Head
for current.Next != nil {
current = current.Next
}
current.Next = newNode
}
// 查询链表中是否存在指定值的节点
func (l *LinkedList) Search(val int) bool {
current := l.Head
for current != nil {
if current.Val == val {
return true
}
current = current.Next
}
return false
}
// 删除链表中第一个值为指定值的节点
func (l *LinkedList) Delete(val int) {
if l.Head == nil {
return
}
if l.Head.Val == val {
l.Head = l.Head.Next
return
}
current := l.Head
for current.Next != nil && current.Next.Val != val {
current = current.Next
}
if current.Next != nil {
current.Next = current.Next.Next
}
}
// 打印链表中的所有节点值
func (l *LinkedList) PrintList() {
current := l.Head
for current != nil {
fmt.Print(current.Val, " ")
current = current.Next
}
fmt.Println()
}
func main() {
list := LinkedList{}
// 插入数据
list.Insert(1)
list.Insert(2)
list.Insert(3)
fmt.Print("插入数据后的链表: ")
list.PrintList()
// 查询数据
fmt.Println("是否存在值为 2 的节点:", list.Search(2))
// 删除数据
list.Delete(2)
fmt.Print("删除值为 2 的节点后的链表: ")
list.PrintList()
}
常被用于函数调用栈,Go 语言在函数调用时会自动管理栈空间,用于存储函数的局部变量、参数等。同时,也可用于表达式求值、括号匹配等场景,通过自定义栈结构来实现。
特点:
package main
import (
"errors"
"fmt"
)
// ArrayStack 定义基于数组的栈结构体
type ArrayStack struct {
items []int
top int
}
// NewArrayStack 创建一个新的数组栈
func NewArrayStack(capacity int) *ArrayStack {
return &ArrayStack{
items: make([]int, capacity),
top: -1,
}
}
// Push 入栈操作
func (s *ArrayStack) Push(item int) error {
if s.top == len(s.items)-1 {
return errors.New("栈已满")
}
s.top++
s.items[s.top] = item
return nil
}
// Pop 出栈操作
func (s *ArrayStack) Pop() (int, error) {
if s.top == -1 {
return 0, errors.New("栈为空")
}
item := s.items[s.top]
s.top--
return item, nil
}
// Peek 查看栈顶元素
func (s *ArrayStack) Peek() (int, error) {
if s.top == -1 {
return 0, errors.New("栈为空")
}
return s.items[s.top], nil
}
// IsEmpty 判断栈是否为空
func (s *ArrayStack) IsEmpty() bool {
return s.top == -1
}
// Size 获取栈的大小
func (s *ArrayStack) Size() int {
return s.top + 1
}
// ListNode 定义链表节点结构体
type ListNode struct {
value int
next *ListNode
}
// LinkedListStack 定义基于链表的栈结构体
type LinkedListStack struct {
top *ListNode
}
// NewLinkedListStack 创建一个新的链表栈
func NewLinkedListStack() *LinkedListStack {
return &LinkedListStack{
top: nil,
}
}
// Push 入栈操作
func (s *LinkedListStack) Push(item int) {
newNode := &ListNode{
value: item,
next: s.top,
}
s.top = newNode
}
// Pop 出栈操作
func (s *LinkedListStack) Pop() (int, error) {
if s.top == nil {
return 0, errors.New("栈为空")
}
item := s.top.value
s.top = s.top.next
return item, nil
}
// Peek 查看栈顶元素
func (s *LinkedListStack) Peek() (int, error) {
if s.top == nil {
return 0, errors.New("栈为空")
}
return s.top.value, nil
}
// IsEmpty 判断栈是否为空
func (s *LinkedListStack) IsEmpty() bool {
return s.top == nil
}
// Size 获取栈的大小
func (s *LinkedListStack) Size() int {
count := 0
current := s.top
for current != nil {
count++
current = current.next
}
return count
}
func main() {
// 使用数组栈
arrayStack := NewArrayStack(5)
arrayStack.Push(1)
arrayStack.Push(2)
fmt.Println("数组栈栈顶元素:", arrayStack.Peek())
fmt.Println("数组栈出栈元素:", arrayStack.Pop())
fmt.Println("数组栈是否为空:", arrayStack.IsEmpty())
// 使用链表栈
linkedListStack := NewLinkedListStack()
linkedListStack.Push(3)
linkedListStack.Push(4)
fmt.Println("链表栈栈顶元素:", linkedListStack.Peek())
fmt.Println("链表栈出栈元素:", linkedListStack.Pop())
fmt.Println("链表栈是否为空:", linkedListStack.IsEmpty())
}
常用于任务调度、消息队列等场景。例如,在一个 Web 服务器中,请求可以被放入队列中,然后由多个工作线程从队列中取出请求进行处理。Go 语言标准库中的container/list
包可以方便地实现队列。
特点:
package main
import (
"container/list"
"fmt"
)
var l list.List // list.List 类型的变量,是值类型
var wg sync.WaitGroup
func addToList(i int) {
defer wg.Done()
l.PushBack(i)
}
func readFromList() {
defer wg.Done()
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
}
func main() {
// 创建队列
l = list.New() // 定义的是 *list.List 类型的变量,是指针类型。
// 入队
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
// 遍历队列并打印元素
for e := queue.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
// 模拟多个线程写入数据
for i := 0; i < 10; i++ {
wg.Add(1)
go addToList(i)
}
// 模拟多个线程读取数据
for i := 0; i < 5; i++ {
wg.Add(1)
go readFromList()
}
wg.Wait()
}
}
二叉搜索树可用于实现高效的查找、插入和删除操作,如数据库中的索引结构。Go 语言中可以通过自定义结构体和指针来构建二叉搜索树。此外,树还常用于文件系统的目录结构表示、XML 和 JSON 数据的解析等。
特点:
n
和边数e
之间满足关系e = n - 1
。这是因为除了根节点外,每个节点都有一条边指向它,所以边数比节点数少 1。package main
import "fmt"
// 定义二叉搜索树节点结构体
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
// 插入节点到二叉搜索树
func insert(root *TreeNode, val int) *TreeNode {
if root == nil {
return &TreeNode{Val: val}
}
if val < root.Val {
root.Left = insert(root.Left, val)
} else {
root.Right = insert(root.Right, val)
}
return root
}
// 中序遍历二叉搜索树
func inorderTraversal(root *TreeNode) {
if root != nil {
inorderTraversal(root.Left)
fmt.Println(root.Val)
inorderTraversal(root.Right)
}
}
// Search 在二叉搜索树中查找值为 val 的节点
func Search(root *TreeNode, val int) *TreeNode {
if root == nil || root.Val == val {
return root
}
if val < root.Val {
return Search(root.Left, val)
}
return Search(root.Right, val)
}
// Delete 在二叉搜索树中删除值为 val 的节点
func Delete(root *TreeNode, val int) *TreeNode {
if root == nil {
return root
}
// 找到要删除的节点
if val < root.Val {
root.Left = Delete(root.Left, val)
} else if val > root.Val {
root.Right = Delete(root.Right, val)
} else {
// 情况 1: 没有子节点或只有一个子节点
if root.Left == nil {
return root.Right
} else if root.Right == nil {
return root.Left
}
// 情况 2: 有两个子节点
// 找到右子树中的最小节点
minNode := findMin(root.Right)
root.Val = minNode.Val
root.Right = Delete(root.Right, minNode.Val)
}
return root
}
// findMin 找到以 node 为根的子树中的最小节点
func findMin(node *TreeNode) *TreeNode {
for node.Left != nil {
node = node.Left
}
return node
}
func main() {
// 创建二叉搜索树
root := &TreeNode{Val: 5}
insert(root, 3)
insert(root, 7)
insert(root, 2)
insert(root, 4)
insert(root, 6)
insert(root, 8)
// 查询节点
target := 4
result := Search(root, target)
if result != nil {
fmt.Printf("找到了值为 %d 的节点\n", target)
} else {
fmt.Printf("未找到值为 %d 的节点\n", target)
}
// 中序遍历二叉搜索树
inorderTraversal(root)
// 删除节点
toDelete := 3
root = Delete(root, toDelete)
fmt.Print("删除节点 ", toDelete, " 后中序遍历结果: ")
InorderTraversal(root)
fmt.Println()
}
完全二叉树(Complete Binary Tree)
- 在完全二叉树中,所有的叶节点都集中在树的最底层或者倒数第二层,并且最后一层的叶节点都尽可能地靠左排列。
- 除了最底层,其他层的节点都是满的,即除了最后一层,其他层的节点数都是满的,最后一层的节点尽可能地靠左排列。
满二叉树(Full Binary Tree)
- 在满二叉树中,每个节点要么没有子节点,要么有两个子节点,即每个节点都有0个或2个子节点。
- 满二叉树是一种特殊的完全二叉树,它的所有层都是满的,即除了最后一层,其他层的节点数都是满的。
哈希表(Hash Table),也叫散列表,它可以提供快速的插入、查找和删除操作
广泛应用于缓存系统、数据查找和统计等场景。Go 语言中的map
类型就是哈希表的实现,它可以方便地存储和查找键值对数据。例如,在一个 Web 应用中,可以使用map
来缓存用户信息,提高访问效率。
特点:
package main
import "fmt"
func main() {
// 创建哈希表
hashMap := make(map[string]int)
// 插入键值对
hashMap["apple"] = 1
hashMap["banana"] = 2
hashMap["cherry"] = 3
// 查找键值对
fmt.Println(hashMap["apple"])
// 遍历哈希表
for key, value := range hashMap {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
if value, exists := hashTable["date"]; exists {
fmt.Println("查询到 'date' 的值为:", value)
} else {
fmt.Println("未查询到 'date'")
}
// 删除操作
delete(hashTable, "apple")
fmt.Println("删除 'apple' 后哈希表:", hashTable)
}
图是一种复杂的数据结构,用于表示各种复杂的关系,如社交网络中的人际关系、网络拓扑结构等。在 Go 语言中,可以使用邻接表或邻接矩阵来表示图,通过深度优先搜索或广度优先搜索算法来遍历图。
节点与边的关系
结构多样性
复杂的关系表示:
应用广泛:
在 Go 语言中,可以通过多种方式实现图以及图的常用操作,下面分别介绍使用邻接矩阵和邻接表来表示图,并实现图的创建、添加边、广度优先搜索(BFS)、深度优先搜索(DFS)等常用操作:
package main
import (
"fmt"
)
// GraphAdjMatrix 邻接矩阵表示的图
type GraphAdjMatrix struct {
vertices int
matrix [][]int
}
// NewGraphAdjMatrix 创建一个新的邻接矩阵图
func NewGraphAdjMatrix(vertices int) *GraphAdjMatrix {
matrix := make([][]int, vertices)
for i := range matrix {
matrix[i] = make([]int, vertices)
}
return &GraphAdjMatrix{
vertices: vertices,
matrix: matrix,
}
}
// AddEdge 添加边
func (g *GraphAdjMatrix) AddEdge(u, v int) {
g.matrix[u][v] = 1
g.matrix[v][u] = 1 // 无向图
}
// BFS 广度优先搜索
func (g *GraphAdjMatrix) BFS(start int) {
visited := make([]bool, g.vertices)
queue := []int{start}
visited[start] = true
for len(queue) > 0 {
vertex := queue[0]
queue = queue[1:]
fmt.Print(vertex, " ")
for i := 0; i < g.vertices; i++ {
if g.matrix[vertex][i] == 1 &&!visited[i] {
queue = append(queue, i)
visited[i] = true
}
}
}
fmt.Println()
}
// DFS 深度优先搜索
func (g *GraphAdjMatrix) DFS(start int) {
visited := make([]bool, g.vertices)
g.dfsHelper(start, visited)
fmt.Println()
}
func (g *GraphAdjMatrix) dfsHelper(vertex int, visited []bool) {
visited[vertex] = true
fmt.Print(vertex, " ")
for i := 0; i < g.vertices; i++ {
if g.matrix[vertex][i] == 1 &&!visited[i] {
g.dfsHelper(i, visited)
}
}
}