这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记
数据结构与算法
常见的数据结构
- **栈(Stack):**栈是一种特殊的线性表,它只能在一个表的一个固定端进行数据结点的插入和删除操作。
- **队列(Queue):**队列和栈类似,也是一种特殊的线性表。和栈不同的是,队列只允许在表的一端进行插入操作,而在另一端进行删除操作。
- **数组(Array):**数组是一种聚合数据类型,它是将具有相同类型的若干变量有序地组织在一起的集合。
- **链表(Linked List):**链表是一种数据元素按照链式存储结构进行存储的数据结构,这种存储结构具有在物理上存在非连续的特点。
- **树(Tree):**树是典型的非线性结构,它是包括,2 个结点的有穷集合 K。
- **图(Graph):**图是另一种非线性数据结构。在图结构中,数据结点一般称为顶点,而边是顶点的有序偶对。
- **堆(Heap):**堆是一种特殊的树形数据结构,一般讨论的堆都是二叉堆。
- **散列表(Hash table):**散列表源自于散列函数(Hash function),其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较操作而直接取得所查记录。
常用算法
- 检索:检索就是在数据结构里查找满足一定条件的节点。一般是给定一个某字段的值,找具有该字段值的节点。
- 插入:往数据结构中增加新的节点。
- 删除:把指定的结点从数据结构中去掉。
- 更新:改变指定节点的一个或多个字段的值。
- 排序:把节点按某种指定的顺序重新排列。例如递增或递减。
插入排序
基本思想:插入排序的工作方式像许多人排序一手扑克牌。开始时,我们的左手为空并且桌子上的牌面向下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。拿在左手上的牌总是排序好的,原来这些牌是桌子上牌堆中顶部的牌 [1] 。
插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序 [3] 。
package main
import (
"fmt"
"math/rand"
"time"
)
/*
插入排序:从小到大排序
*/
func insertSort(arr []int) []int {
len := len(arr)//数组长度
for i := 1; i < len; i++ {
deal := arr[i] //待排序的数
j := i - 1 //待排序左边第一个数的位置
// 如果第一次比较,比左边的已排好序的第个数小,那么进入处理
if deal < arr[j] {
//一直往左边找,比待排序大的数都往后挪,腾空位给待排序插入
for ; j >= 0 && deal < arr[j]; j-- {
arr[j+1] = arr[j] //某数后移,给待排序留空位
}
arr[j+1] = deal //将最后一个空位填充
}
}
return arr
}
func main() {
//构造一个数组
arr := []int{0,0,0,0,0,0,0,0}
//随机数 rand.Seed(种子)
rand.Seed(time.Now().UnixNano())
for i := 0;i < len(arr);i++ {
arr[i] = rand.Intn(20)
}
fmt.Printf("%v",insertSort(arr))
}
快速排序
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
// 第一种写法
func quickSort(values []int, left, right int) {
temp := values[left]
p := left
i, j := left, right
for i <= j {
for j >= p && values[j] >= temp {
j--
}
if j >= p {
values[p] = values[j]
p = j
}
for i <= p && values[i] <= temp {
i++
}
if i <= p {
values[p] = values[i]
p = i
}
}
values[p] = temp
if p-left > 1 {
quickSort(values, left, p-1)
}
if right-p > 1 {
quickSort(values, p+1, right)
}
}
func QuickSort(values []int) {
if len(values) <= 1 {
return
}
quickSort(values, 0, len(values)-1)
}
// 第二种写法
func Quick2Sort(values []int) {
if len(values) <= 1 {
return
}
mid, i := values[0], 1
head, tail := 0, len(values)-1
for head < tail {
fmt.Println(values)
if values[i] > mid {
values[i], values[tail] = values[tail], values[i]
tail--
} else {
values[i], values[head] = values[head], values[i]
head++
i++
}
}
values[head] = mid
Quick2Sort(values[:head])
Quick2Sort(values[head+1:])
}
// 第三种写法
func Quick3Sort(a []int,left int, right int) {
if left >= right {
return
}
explodeIndex := left
for i := left + 1; i <= right ; i++ {
if a[left] >= a[i]{
//分割位定位++
explodeIndex ++;
a[i],a[explodeIndex] = a[explodeIndex],a[i]
}
}
//起始位和分割位
a[left], a[explodeIndex] = a[explodeIndex],a[left]
Quick3Sort(a,left,explodeIndex - 1)
Quick3Sort(a,explodeIndex + 1,right)
}
堆排序
在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作:
- 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
- 创建最大堆(Build Max Heap):将堆中的所有数据重新排序
- 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
package main
import "fmt"
// 堆排序的步骤分为两步:1、构建大(小)根堆 2、调整根堆
// 1、构建堆,把最值元素放到父节点,从最后一个非叶子节点开始调整,直到i=0(非叶子节点=0...(n/2-1))
// 2、把堆顶和未调整堆的最后一个元素交换,然后i--继续执行1和2步骤
// 由于和选择排序一样是交换排序,所以堆排序也是不稳定排序
func main() {
defer fmt.Println("heap sort complete")
cha1n := make(chan []int)
var array = []int{10, 2, 7, 9, 4, 11}
go HeapSort(cha1n, aray)
fmt.Println(<-cha1n)
}
func HeapSort(cha1n chan<-[]int, nums []int) {
// 1、构建堆(这里用大顶堆构建升序)
// 2、调整堆,把堆顶元素和第i-1个元素交换,这样0....i-2就又成为一个堆,继续对这个堆进行构建,调整
Hepify(nums, len(nums)) // 先构建n个元素的大顶堆
for i := len(nums) - 1; i >= 0; i-- {
nums[i],nums[0] = nums[0],nums[i] // 调整堆顶元素,把堆顶元素和最后一个元素交换
Hepify(nums, i)
}
cha1n<-nums
}
// 构建堆,一般从最后一个非叶子节点开始构建,即从下往上调整,从下往上能让最大(小)值元素转移到堆顶
func Hepify(nums []int, unsortCapacity int) {
for i := (unsortCapacity / 2) - 1; i >= 0; i-- { // 非叶子节点的i范围从0...(n/2-1)个
// 调整左子树
leftIndex := 2*i + 1
if leftIndex < unsortCapacity && nums[i] < nums[leftIndex] {
nums[i],nums[leftIndex] = nums[leftIndex], nums[i] // 左孩子值大于父节点,交换
}
// 调整右子树
rightIndex := 2*i + 2
if rightIndex < unsortCapacity && nums[i] < nums[rightIndex] {
nums[i],nums[rightIndex] = nums[rightIndex], nums[i] // 右孩子值大于父节点,交换
}
}
}
运行结果为:[11 10 9 7 4 2] heap sort complete [Finished in 0.7s]