所有实现的数据结构的介绍文件

This commit is contained in:
hlccd 2021-12-27 11:57:39 +08:00 committed by GitHub
parent 8497ed4f51
commit fe26881223
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 9180 additions and 0 deletions

View File

@ -0,0 +1,330 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
一致性哈希consistent hash与一致性哈希相对的是不一致性哈希但常见的所有的哈希几乎都是不一致的即哈希桶的容量的不固定的可以根据需求进行扩容和缩容不一致性哈希可以提高空间的利用率但相应的当进行扩容和缩容操作时需要对桶内存储的所有元素重新计算哈希值这在某些情况是十分麻烦的事情特别是在分布式存储的环境下此时每个哈希结点就相当于一个机器文件分布在哪台机器由哈希算法来决定这个系统想要加一台机器时就需要停下来等所有文件重新分布一次才能对外提供服务而当一台机器掉线的时候尽管只掉了一部分数据但所有数据访问路由都会出问题。这样整个服务就无法平滑的扩缩容成为了有状态的服务。
为了避免分布式存储出现的状态化问题就需要利用一致性哈希去解决一致性哈希是固定了桶的容量即2^32然后将结点的哈希值计算后放入其中当要从一致性哈希中寻找机器结点的时候可以将hash桶视为一个大小为2^32的环状结构要查找的结点也计算出hash并放入其中然后沿顺时针寻找直到遇到第一个机器结点即可。
而当增删机器结点的时候,只需要去调度该机器节点临近的一些结点即可,避免了对所有节点的全部调度。
当机器节点的hash值比较密集的集中在环上的一部分的时候就有可能出现**数据倾斜**的问题,即有较多存储结点会映射到其中一个机器结点上,造成该节点的负担较大。对于该问题一般采用增加**虚拟结点**的方案去解决即对于每一个机器节点可以凭空创造一些结点作为该虚拟节点的等价替代结点并放入hash环内以此来充盈整个hash环从而尽可能减少数据倾斜堆的概率。**虚拟节点数量一般采用32**主要由于hash环的大小为2^32采用32个虚拟节点可以近似使得转换出的32bit的每一位都可以存在一个虚拟节点从而实现充盈hash环的目的。
### 原理
对于一致性hash来说它需要做的一方面是对机器结点和其对应的虚拟节点的key进行hash计算然后将计算机插入到hash环上随后将hash环进行排序另一方面是计算用于查找的结点的hash值并利用hash环找到其对应的机器节点或机器节点的虚拟节点然后映射到机器节点并返回。
考虑到存储的值仅仅只有机器节点及其虚拟结点同时机器节点也可以视为其虚拟节点所以主要需要解决的问题就变成了虚拟节点之间的hash冲突问题。
对于虚拟节点的hash冲突来说由于一致性hash的寻找不应当出现一次找到两个不同的结点即hash冲突出现时不应该通过拓展的方式去解决。所以可以考虑采用再次hash法即当一个虚拟节点与之前的结点发送冲突时则废弃该结点然后重新生成一个虚拟节点在进行计算比较即可。
同时由于虚拟节点可以通过map映射到机器节点上所以理论上生成的虚拟节点都可以进行直接映射而没有太多的额外限制故重新生成虚拟节点放入hash环内一方面可以解决hash冲突另一方面也不会出现找不到对应的机器节点的情况。
除此之外考虑到再hash也需要一定的时间所以在进行hash计算的时候一方面利用**素数做逐位城际累加**,另一方面利用虚拟结点的**id做平方法**去从素数表中找到对应的素数做计算。从而尽可能降低出现hash冲突的概率。
### 实现
##### hash
```go
//最大虚拟节点数量
const (
maxReplicas = 32
)
//素数表
//用以减少hash冲突
var primes = []uint32{3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
37, 41, 43, 47, 53, 59, 61, 67, 71, 73,
79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
131, 137, 139, 149, 151, 157, 163, 167, 173, 179,
121, 191, 193, 197, 199, 211, 223, 227, 229, 233,
239, 241, 251,
}
```
传入一个虚拟节点id和实际结点计算出它的hash值先利用id从素数表中找到对应的素数,然后将id,素数和实际结点转化为[]byte逐层访问并利用素数计算其hash值随后返回。
```go
func hash(id int, v interface{}) (h uint32) {
prime := primes[(id*id+len(primes))%len(primes)]
h = uint32(0)
s := fmt.Sprintf("%d-%v-%d", id*int(prime), v, prime)
bs := []byte(s)
for i := range bs {
h += uint32(bs[i]) * prime
}
return h
}
```
#### 结构体
一致性hash结构体该实例了一致性hash在创建时设定的最小虚拟节点数同时保存了所有虚拟节点的hash值建立了虚拟节点hash值与实际结点之间的映射表每个实际结点也可以映射其所有的虚拟节点的hash值并发控制锁用以保证线程安全。
```go
type ConsistentHash struct {
minReplicas int //最小的虚拟节点数
keys []uint32 //存储的结点和虚拟结点的集合
hashMap map[uint32]interface{} //hash与结点之间的映射
nodeMap map[interface{}][]uint32 //结点所对应的虚拟节点的hash值
mutex sync.Mutex //并发控制锁
}
```
#### 接口
```go
type consistentHasher interface {
Size() (num int) //返回一致性hash中的虚拟节点数量
Clear() //清空一致性hash的所有结点
Empty() (b bool) //返回该一致性hash是否有结点存在
Insert(keys ...interface{}) (nums []int) //向该一致性hash中插入一组结点,同时返回其生成的虚拟节点的数量
Erase(key interface{}) (ok bool) //删除结点key
Get(key interface{}) (ans interface{}) //查找key对应的结点
}
```
#### New
新建一个一致性hash结构体指针并返回传入设定的最少虚拟节点数量,不可小于1也不可大于最大虚拟节点数。
```go
func New(minReplicas int) (ch *ConsistentHash) {
if minReplicas > maxReplicas {
//超过最大虚拟节点数
minReplicas = maxReplicas
}
if minReplicas < 1 {
//最少虚拟节点数量不得小于1
minReplicas = 1
}
ch = &ConsistentHash{
minReplicas: minReplicas,
keys: make([]uint32, 0, 0),
hashMap: make(map[uint32]interface{}),
nodeMap: make(map[interface{}][]uint32),
mutex: sync.Mutex{},
}
return ch
}
```
#### Size
以一致性hash做接收者返回该容器当前含有的虚拟结点数量如果容器为nil返回0。
```go
func (ch *ConsistentHash) Size() (num int) {
if ch == nil {
return 0
}
return len(ch.keys)
}
```
#### Clear
以一致性hash做接收者将该容器中所承载的所有结点清除被清除结点包括实际结点和虚拟节点重建映射表。
```go
func (ch *ConsistentHash) Clear() {
if ch == nil {
return
}
ch.mutex.Lock()
//重建vector并扩容到16
ch.keys = make([]uint32, 0, 0)
ch.hashMap = make(map[uint32]interface{})
ch.nodeMap = make(map[interface{}][]uint32)
ch.mutex.Unlock()
}
```
#### Empty
以一致性hash做接收者判断该一致性hash中是否含有元素如果含有结点则不为空,返回false如果不含有结点则说明为空,返回true如果容器不存在,返回true。
```go
func (ch *ConsistentHash) Empty() (b bool) {
if ch == nil {
return false
}
return len(ch.keys) > 0
}
```
#### Insert
以一致性hash做接收者向一致性hash中插入结点,同时返回每一个结点插入的数量插入每个结点后将其对应的虚拟节点的hash放入映射表内和keys内对keys做排序利用二分排序算法。
```go
func (ch *ConsistentHash) Insert(keys ...interface{}) (nums []int) {
nums = make([]int, 0, len(keys))
ch.mutex.Lock()
//遍历所有待插入的结点
for _, key := range keys {
num := 0
//判断结点是否已经存在
_, exist := ch.nodeMap[key]
if !exist {
//结点不存在,开始插入
for i := 0; i < maxReplicas || num < ch.minReplicas; i++ {
//生成每个虚拟节点的hash值
h := uint32(hash(i, key))
//判断生成的hash值是否存在,存在则不插入
_, ok := ch.hashMap[h]
if !ok {
//hash值不存在,进行插入
num++
ch.keys = append(ch.keys, h)
//同时建立hash值和结点之间的映射关系
ch.hashMap[h] = key
ch.nodeMap[key] = append(ch.nodeMap[key], h)
}
}
}
nums = append(nums, num)
}
//对keys进行排序,以方便后续查找
ch.sort(0, len(ch.keys)-1)
ch.mutex.Unlock()
return nums
}
```
##### sort
以一致性hash做接收者,二分排序,主要用于一致性hash的keys的排序,以保证其有序性,同时方便后续使用二分查找。
```go
func (ch *ConsistentHash) sort(L, R int) {
if L >= R {
//左下标大于右下标,结束
return
}
//找到中间结点,从左右两侧以双指针形式向中间靠近
l, r, m := L-1, R+1, ch.keys[(L+R)/2]
for l < r {
//左侧出现不小于中间结点时停下
l++
for ch.keys[l] < m {
l++
}
//右侧出现不大于中间结点时停下
r--
for ch.keys[r] > m {
r--
}
if l < r {
//左节点仍在右结点左方,交换结点的值
ch.keys[l], ch.keys[r] = ch.keys[r], ch.keys[l]
}
}
//递归排序左右两侧保证去有序性
ch.sort(L, l-1)
ch.sort(r+1, R)
}
```
#### Erase
以一致性hash做接收者删除以key为索引的结点先利用结点映射表判断该key是否存在于一致性hash内若存在则从映射表内删除同时找到其所有的虚拟节点的hash值遍历keys进行删除。
```go
func (ch *ConsistentHash) Erase(key interface{}) (ok bool) {
ch.mutex.Lock()
hs, ok := ch.nodeMap[key]
if ok {
//该结点存在于一致性hash内
//删除该结点
delete(ch.nodeMap, key)
//删除所有该结点的虚拟节点与该结点的映射关系
for i := range hs {
delete(ch.hashMap, hs[i])
}
//将待删除的虚拟结点删除即可
arr := make([]uint32, 0, len(ch.keys)-len(hs))
for i := range ch.keys {
h := ch.keys[i]
flag := true
for j := range hs {
if hs[j] == h {
flag = false
}
}
if flag {
arr = append(arr, h)
}
}
ch.keys = arr
}
ch.mutex.Unlock()
return ok
}
```
#### Get
以一致性hash做接收者从一致性hash中获取以key为索引的下一个结点的索引只要一致性hash内存储了结点则必然可以找到将keys看成一个环要找的key的计算出hash后放入环中按顺时针向下找直到遇到第一个虚拟节点寻找过程利用二分若二分找到的结点为末尾结点的下一个即为首个虚拟节点。
```go
func (ch *ConsistentHash) Get(key interface{}) (ans interface{}) {
if len(ch.keys) == 0 {
return nil
}
ch.mutex.Lock()
//计算key的hash值
h := hash(0, key)
//从现存的所有虚拟结点中找到该hash值对应的下一个虚拟节点对应的结点的索引
idx := ch.search(h)
ans = ch.hashMap[ch.keys[idx]]
ch.mutex.Unlock()
return ans
}
```
##### search
以一致性hash做接收者二分查找找到最近的不小于该值的hash值如果不存在则返回0,即进行取模运算即可。
```go
func (ch *ConsistentHash) search(h uint32) (idx uint32) {
//二分查找寻找不小于该hash值的下一个值的下标
l, m, r := uint32(0), uint32(len(ch.keys)/2), uint32(len(ch.keys))
for l < r {
m = (l + r) / 2
if ch.keys[m] >= h {
r = m
} else {
l = m + 1
}
}
//当找到的下标等同于keys的长度时即为0
return l % uint32(len(ch.keys))
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/consistentHash"
)
func main() {
ch:=consistentHash.New(32)
fmt.Println(ch.Insert("http://localhost:8001","http://localhost:8002","http://localhost:8003"))
fmt.Println(ch.Size())
fmt.Println(ch.Get("group"))
fmt.Println(ch.Get("http://localhost:8002"))
}
```
> [32 32 32]
> 96
> http://localhost:8001
> http://localhost:8002

View File

@ -0,0 +1,504 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
二叉搜索树Binary Search Tree不同于之前使用的线性结构它是一种通过离散的多个点以指针的形式连接起来的**树形结构**。
二叉树由一个根节点和根节点下属的多层次的子结点构成,任意一个结点最多只能拥有两个子结点,即左右子结点。基于此种特性,在实现二叉搜索树时,可以仅持有根节点,然后通过根节点去递归访问其子结点以实现寻找到所有结点的。
对于二叉搜索树,由于其中缀表达式是有序的,即从小到大(**本文章描述中普遍采用此方案**),所以对于任意一个结点来说,其左子树必然是小于它的,其右子树必然是大于它的。
### 原理
对于一个二叉搜索树而言由于其任意结点和其左右子树之间的关系来看在插入结点时可以通过遍历找到对应的位置然后插入新结点即可当然对于允许重复的情况来说可以让结点存储的num+1即可这样就不需要太多的空间去存储以达到节约空间的目的。
而对于删除的情况来说,也可以通过根节点去找到要删除的结点的位置,然后将该结点删除,随后,可以自行选择左子树的结点或右子树的结点替代该结点,对于用于替代的结点,需要去寻找到它的**前缀节点或后继节点**,前缀节点指该子树中最大的比它小的结点,即左子树的最右结点,后继节点指该子树中最小的比它大的结点,即右子树的最左结点。
当要查找到对应的位置的情况时,通过根结点和给定的值,如果小于则进入左子树递归查找,如果大于进入右子树递归查找。
#### 添加策略
对于二叉搜索树的添加结点的情况来说需要考虑的情况主要是该结点是否存在于二叉搜中如果存在只需要结点num+1即可否则找到的位置必然是叶子节点此时只需要插入一个新结点让其父结点指向它即可。
二叉搜索树添加结点的步骤:
1. 通过先以根节点为父结点,再以递归结点为父结点,比对待插入值和父结点之间的大小,小于递归左子树,大于递归右子树,找到对应插入的位置
2. 找到位置后如果该元素已存在则结点num+1即可否则新建结点同时建立父结点与新结点之间的关系
#### 删除策略
对于二叉搜索树的删除来说,首先需要先找到要删除的结点,如果没找到则直接结束即可,当找到后,判断该结点是否有子树,如果没有,直接删除即可,如果有左子树,则寻找其前缀节点,即左子树的最右结点,如果只有右子树,则寻找其后缀节点,即右子树的最左结点,将寻找到的结点替换为当前父结点,然后删除被替换的结点即可。
二叉搜索树删除结点的步骤:
1. 通过递归比对遍历寻找到要删除的结点
2. 如果该结点不存在则直接结束如果有重复则num-1即可结束
3. 如果该结点无左右子树,删除该结点即可结束
4. 如果有左子树,寻找到前缀结点替换该结点,随后删除前缀节点即可结束
5. 如果只有右子树,寻找到后缀结点替换该结点,随后删除后缀节点即可结束
6. 删除完成
### 实现
bsTree二叉搜索树结构体该实例存储二叉树的根节点同时保存该二叉树已经存储了多少个元素二叉树中排序使用的比较器在创建时传入,若不传入则在插入首个节点时从默认比较器中寻找,创建时传入是否允许该二叉树出现重复值,如果不允许则进行覆盖,允许则对节点数目增加即可。
```go
type bsTree struct {
root *node //根节点指针
size uint64 //存储元素数量
cmp comparator.Comparator //比较器
isMulti bool //是否允许重复
mutex sync.Mutex //并发控制锁
}
```
node树节点结构体该节点是二叉搜索树的树节点若该二叉搜索树允许重复则对节点num+1即可,否则对value进行覆盖二叉搜索树节点不做平衡。
```go
type node struct {
value interface{} //节点中存储的元素
num uint64 //该元素数量
left *node //左节点指针
right *node //右节点指针
}
```
#### 接口
```go
type bsTreeer interface {
Iterator() (i *Iterator.Iterator) //返回包含该二叉树的所有元素,重复则返回多个
Size() (num uint64) //返回该二叉树中保存的元素个数
Clear() //清空该二叉树
Empty() (b bool) //判断该二叉树是否为空
Insert(e interface{}) //向二叉树中插入元素e
Erase(e interface{}) //从二叉树中删除元素e
Count(e interface{}) (num uint64) //从二叉树中寻找元素e并返回其个数
}
```
#### New
新建一个bsTree二叉搜索树容器并返回初始根节点为nil传入该二叉树是否为可重复属性,如果为true则保存重复值,否则对原有相等元素进行覆盖,若有传入的比较器,则将传入的第一个比较器设为该二叉树的比较器。
```go
func New(isMulti bool, Cmp ...comparator.Comparator) (bs *bsTree) {
//判断是否有传入比较器,若有则设为该二叉树默认比较器
var cmp comparator.Comparator
if len(Cmp) == 0 {
cmp = nil
} else {
cmp = Cmp[0]
}
return &bsTree{
root: nil,
size: 0,
cmp: cmp,
isMulti: isMulti,
mutex: sync.Mutex{},
}
}
```
新建一个二叉搜索树节点并返回将传入的元素e作为该节点的承载元素该节点的num默认为1,左右子节点设为nil。
```go
func newNode(e interface{}) (n *node) {
return &node{
value: e,
num: 1,
left: nil,
right: nil,
}
}
```
#### Iterator
以bsTree二叉搜索树做接收者将该二叉树中所有保存的元素将从根节点开始以中缀序列的形式放入迭代器中若允许重复存储则对于重复元素进行多次放入。
```go
func (bs *bsTree) Iterator() (i *Iterator.Iterator) {
if bs == nil {
//创建一个允许插入重复值的二叉搜
bs = New(true)
}
bs.mutex.Lock()
es := bs.root.inOrder()
i = Iterator.New(&es)
bs.mutex.Unlock()
return i
}
```
以node二叉搜索树节点做接收者以中缀序列返回节点集合若允许重复存储则对于重复元素进行多次放入。
```go
func (n *node) inOrder() (es []interface{}) {
if n == nil {
return es
}
if n.left != nil {
es = append(es, n.left.inOrder()...)
}
for i := uint64(0); i < n.num; i++ {
es = append(es, n.value)
}
if n.right != nil {
es = append(es, n.right.inOrder()...)
}
return es
}
```
#### Size
以bsTree二叉搜索树做接收者返回该容器当前含有元素的数量如果容器为nil则创建一个并返回其承载的元素个数。
```go
func (bs *bsTree) Size() (num uint64) {
if bs == nil {
//创建一个允许插入重复值的二叉搜
bs = New(true)
}
return bs.size
}
```
#### Clear
以bsTree二叉搜索树做接收者将该容器中所承载的元素清空将该容器的size置0。
```go
func (bs *bsTree) Clear() {
if bs == nil {
//创建一个允许插入重复值的二叉搜
bs = New(true)
}
bs.mutex.Lock()
bs.root = nil
bs.size = 0
bs.mutex.Unlock()
}
```
#### Empty
以bsTree二叉搜索树做接收者判断该二叉搜索树是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true。
```go
func (bs *bsTree) Empty() (b bool) {
if bs == nil {
//创建一个允许插入重复值的二叉搜
bs = New(true)
}
return bs.size == 0
}
```
#### Insert
以bsTree二叉搜索树做接收者向二叉树插入元素e,若不允许重复则对相等元素进行覆盖如果二叉树为空则之间用根节点承载元素e,否则以根节点开始进行查找,不做平衡。
```go
func (bs *bsTree) Insert(e interface{}) {
if bs == nil {
//创建一个允许插入重复值的二叉搜
bs = New(true)
}
bs.mutex.Lock()
if bs.Empty() {
//二叉树为空,用根节点承载元素e
if bs.cmp == nil {
bs.cmp = comparator.GetCmp(e)
}
if bs.cmp == nil {
bs.mutex.Unlock()
return
}
bs.root = newNode(e)
bs.size++
bs.mutex.Unlock()
return
}
//二叉树不为空,从根节点开始查找添加元素e
if bs.root.insert(e, bs.isMulti, bs.cmp) {
bs.size++
}
bs.mutex.Unlock()
}
```
以node二叉搜索树节点做接收者从n节点中插入元素e如果n节点中承载元素与e不同则根据大小从左右子树插入该元素如果n节点与该元素相等,且允许重复值,则将num+1否则对value进行覆盖插入成功返回true,插入失败或不允许重复插入返回false。
```go
func (n *node) insert(e interface{}, isMulti bool, cmp comparator.Comparator) (b bool) {
if n == nil {
return false
}
//n中承载元素小于e,从右子树继续插入
if cmp(n.value, e) < 0 {
if n.right == nil {
//右子树为nil,直接插入右子树即可
n.right = newNode(e)
return true
} else {
return n.right.insert(e, isMulti, cmp)
}
}
//n中承载元素大于e,从左子树继续插入
if cmp(n.value, e) > 0 {
if n.left == nil {
//左子树为nil,直接插入左子树即可
n.left = newNode(e)
return true
} else {
return n.left.insert(e, isMulti, cmp)
}
}
//n中承载元素等于e
if isMulti {
//允许重复
n.num++
return true
}
//不允许重复,直接进行覆盖
n.value = e
return false
}
```
#### Erase
以bsTree二叉搜索树做接收者从搜素二叉树中删除元素e若允许重复记录则对承载元素e的节点中数量记录减一即可若不允许重复记录则删除该节点同时将前缀节点或后继节点更换过来以保证二叉树的不发送断裂如果该二叉树仅持有一个元素且根节点等价于待删除元素,则将二叉树根节点置为nil。
```go
func (bs *bsTree) Erase(e interface{}) {
if bs == nil {
//创建一个允许插入重复值的二叉搜
bs = New(true)
}
if bs.size == 0 {
return
}
bs.mutex.Lock()
if bs.size == 1 && bs.cmp(bs.root.value, e) == 0 {
//二叉树仅持有一个元素且根节点等价于待删除元素,将二叉树根节点置为nil
bs.root = nil
bs.size = 0
bs.mutex.Unlock()
return
}
//从根节点开始删除元素e
//如果删除成功则将size-1
if bs.root.delete(e, bs.isMulti, bs.cmp) {
bs.size--
}
bs.mutex.Unlock()
}
```
以node二叉搜索树节点做接收者从n节点中删除元素e如果n节点中承载元素与e不同则根据大小从左右子树删除该元素如果n节点与该元素相等,且允许重复值,则将num-1否则直接删除该元素删除时先寻找该元素的前缀节点,若不存在则寻找其后继节点进行替换,替换后删除该节点。
```go
func (n *node) delete(e interface{}, isMulti bool, cmp comparator.Comparator) (b bool) {
if n == nil {
return false
}
//n中承载元素小于e,从右子树继续删除
if cmp(n.value, e) < 0 {
if n.right == nil {
//右子树为nil,删除终止
return false
}
if cmp(e, n.right.value) == 0 && (!isMulti || n.right.num == 1) {
if n.right.left == nil && n.right.right == nil {
//右子树可直接删除
n.right = nil
return true
}
}
//从右子树继续删除
return n.right.delete(e, isMulti, cmp)
}
//n中承载元素大于e,从左子树继续删除
if cmp(n.value, e) > 0 {
if n.left == nil {
//左子树为nil,删除终止
return false
}
if cmp(e, n.left.value) == 0 && (!isMulti || n.left.num == 1) {
if n.left.left == nil && n.left.right == nil {
//左子树可直接删除
n.left = nil
return true
}
}
//从左子树继续删除
return n.left.delete(e, isMulti, cmp)
}
//n中承载元素等于e
if (*n).num > 1 && isMulti {
//允许重复且个数超过1,则减少num即可
(*n).num--
return true
}
if n.left == nil && n.right == nil {
//该节点无前缀节点和后继节点,删除即可
*(&n) = nil
return true
}
if n.left != nil {
//该节点有前缀节点,找到前缀节点进行交换并删除即可
ln := n.left
if ln.right == nil {
n.value = ln.value
n.num = ln.num
n.left = ln.left
} else {
for ln.right.right != nil {
ln = ln.right
}
n.value = ln.right.value
n.num = ln.right.num
ln.right = ln.right.left
}
} else if (*n).right != nil {
//该节点有后继节点,找到后继节点进行交换并删除即可
tn := n.right
if tn.left == nil {
n.value = tn.value
n.num = tn.num
n.right = tn.right
} else {
for tn.left.left != nil {
tn = tn.left
}
n.value = tn.left.value
n.num = tn.left.num
tn.left = tn.left.right
}
return true
}
return true
}
```
#### Count
以bsTree二叉搜索树做接收者从搜素二叉树中查找元素e的个数如果找到则返回该二叉树中和元素e相同元素的个数如果不允许重复则最多返回1如果未找到则返回0。
```go
func (bs *bsTree) Count(e interface{}) (num uint64) {
if bs == nil {
//二叉树不存在,返回0
return 0
}
if bs.Empty() {
//二叉树为空,返回0
return 0
}
bs.mutex.Lock()
//从根节点开始查找并返回查找结果
num = bs.root.search(e, bs.isMulti, bs.cmp)
bs.mutex.Unlock()
return num
}
```
以node二叉搜索树节点做接收者从n节点中查找元素e并返回存储的个数如果n节点中承载元素与e不同则根据大小从左右子树查找该元素如果n节点与该元素相等,则直接返回其个数。
```go
func (n *node) search(e interface{}, isMulti bool, cmp comparator.Comparator) (num uint64) {
if n == nil {
return 0
}
//n中承载元素小于e,从右子树继续查找并返回结果
if cmp(n.value, e) < 0 {
return n.right.search(e, isMulti, cmp)
}
//n中承载元素大于e,从左子树继续查找并返回结果
if cmp(n.value, e) > 0 {
return n.left.search(e, isMulti, cmp)
}
//n中承载元素等于e,直接返回结果
return n.num
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/bsTree"
"sync"
)
func main() {
bs := bsTree.New(true)
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
bs.Insert(i)
go func() {
bs.Insert(i)
wg.Done()
}()
}
wg.Wait()
fmt.Println("遍历输出所有插入的元素")
for i := bs.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
fmt.Println("删除一次二叉搜索树中存在的元素,存在重复的将会被剩下")
for i := 0; i < 10; i++ {
bs.Erase(i)
}
fmt.Println("输出剩余的重复元素")
for i := bs.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 遍历输出所有插入的元素
> 0
> 1
> 2
> 3
> 3
> 4
> 5
> 6
> 6
> 6
> 7
> 7
> 7
> 7
> 7
> 8
> 9
> 10
> 10
> 10
> 删除一次二叉搜索树中存在的元素,存在重复的将会被剩下
> 输出剩余的重复元素
> 3
> 6
> 6
> 7
> 7
> 7
> 7
> 10
> 10
> 10
>

View File

@ -0,0 +1,352 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
优先队列priority_queue它虽然名字上是被称之为队列单它底层其实是以堆的方式实现的而堆这个数据结构它是通过建立一棵**完全二叉树**来进行实现的。它在逻辑上并非是一个线性结构,但由于**二叉树可以用数组表示**的特性,**本次实现采用数组的形式实现**,后续会再使用完全二叉搜实现一次。
堆或者说优先队列的主要特点为:它的**父结点必然是小于或等于左右子结点**(该文章叙述中使用**小顶堆**,大顶堆大小情况相反),同时,考虑到用数组来模拟完全二叉搜的情况,它的所有结点必然在数组上是连续的,不会存在上一个结点和下一个结点间存在一段间隔的情况。
### 原理
##### 数组模拟完全二叉树
对于使用数组来模拟完全二叉树的情况来说,首先,完全二叉树除开底部的一层可能存在不满的情况外,其他上层必然是满的,同时,对于完全二叉搜的任意一个结点来说,假设该结点的下标为**p从0开始**,使用数组来模拟的情况下,其左子结点的下标则必然是**2p+1**,而其右子结点的下标则必然是**2p+2**,当然,也可能出现不存在右子结点或不存在左子结点的情况。
对于数组来说可以将数组根据2^n来进行划分n从0开始即0位为第一层1~2为第二层3~6为第三层7~17为第四层······以此可有
$$
2^{(n-1)}-1——2^{n}-2
$$
为第n层同时由于任意结点的左右子结点必然在其下一层所以通过计算也可得知其左右子结点的下标也符合上文所述。当然如果给定一个结点下标为**p**(除首结点,毕竟首节点无父结点),则它父结点的下标必然为**p/2**
##### 优先队列实现
对于一个优先队列来说,它是建立来完全二叉搜的基础上,同时其任意结点的值必然不大于其左右子结点,所以可知道,当插入节点时,可以利用尾部结点和父结点进行比较交换来实现,对于删除节点时,可以利用顶部结点与左右子结点的值进行比较进行下降即可。
#### 添加策略
当插入一个结点的时候可以先将其放入二叉树的末尾然后根据插入的值和它父结点进行比较如果插入值小于父节点时则交换两结点值随后在用交换后的结点与其父结点比较重复该过程直到抵达顶点或不满足条件即可。当然添加的过程中由于底层使用动态数组来存储所以可能出现扩容的情况由于其情况和vector类似则不在赘述仅在下方列出即可。
##### 添加步骤
1. 先判断动态数组中是否有冗余空间,有则直接放入尾部,否则利用扩容策略进行扩容,扩容后再放入尾部即可
2. 结点放入尾部后,通过比较该结点与父结点的值进行交换
3. 满足交换条件重复回到2过程
4. 到达顶部或不满足交换条件,添加结束,插入完成
##### 扩容策略
1. 固定扩容:固定的去增加一定量的空间,该方案时候在数组较大时添加空间,可以避免直接翻倍导致的**冗余过量**问题,到由于增加量是固定的,如果需要一次扩容很多量的话就会比较缓慢。
2. 翻倍扩容:将原有容量直接翻倍,该方案适合在数组不太大的适合添加空间,可以提高扩容量,增加扩容效率,但数组太大时使用的话会导致一次性增加太多空间,进而造成空间的浪费。
#### 删除策略
不同于添加时直接利用尾结点的情况,由于删除的是首结点,而同时删除需要减少一个空间,所以可以考虑将首位结点进行交换,或者其实直接**用尾结点覆写首节点**即可这样就实现了首节点的删除同时减小一位删去移到首位的尾结点实现实际的删除。同时在删除后需要判断实际使用空间是否满足缩容策略的情况如果满足则需要使用缩容策略进行缩容缩容策略同vector已在下方列出。
上一过程完成后,首节点以及被删除了,但移到首节点的新首节点可能并不满足优先队列的父结点必然小于或等于左右子结点的情况,所以要通过比较父结点和其左右子结点来进行下将操作,即当存在一个在数组范围内的结点且大于父结点时则交换两结点,然后递归该过程,直到触底或不满足条件。
比较过程中,先比较左结点与父结点的情况,然后再比较右结点的情况,找到最小的一侧进行下降即可。
##### 删除步骤
1. 将尾结点覆盖首结点
2. 删除首结点,进行缩容判断,并可能利用缩容策略进行缩容
3. 以首节点为父结点,对其左右结点进行下降判断:左右节点是否存在,且存在的左右节点是否存在小于父结点的情况,
4. 比对左右两侧找到满足小于父结点且最小的一侧进行下降即可同时返回3过程进行递归下降
5. 触底或不满足条件时下降结束,删除完成。
##### 缩容策略
1. 固定缩容:释放一个固定的空间,该方案适合在当前数组较大的时候进行,可以减缓需要缩小的量,当空间较大的时候如果采取折半缩减的话;将可能在绝大多数时间内都不会被采用,从而造成空间浪费。
2. 折半缩容:将当前容量进行折半,该方案适合数组不太大的适合进行缩容,可以更快的缩小容量,但对于较大的空间来说并不会频繁使用到。
### 实现
priority_queue优先队列集合结构体包含动态数组和比较器,同时包含其实际使用长度和实际占用空间容量,该数据结构可以存储多个相同的元素,并不会产生冲突,增删节点后会使用比较器保持该动态数组的相对有序性。
```go
type priority_queue struct {
data []interface{} //动态数组
len uint64 //实际使用长度
cap uint64 //实际占用的空间的容量
cmp comparator.Comparator //该优先队列的比较器
mutex sync.Mutex //并发控制锁
}
```
#### 接口
```go
type priority_queueer interface {
Size() (num uint64) //返回该容器存储的元素数量
Clear() //清空该容器
Empty() (b bool) //判断该容器是否为空
Push(e interface{}) //将元素e插入该容器
Pop() //弹出顶部元素
Top() (e interface{}) //返回顶部元素
}
```
#### New
新建一个priority_queue优先队列容器并返回初始priority_queue的切片数组为空如果有传入比较器,则将传入的第一个比较器设为可重复集合默认比较器,如果不传入比较器,在后续的增删过程中将会去寻找默认比较器。
```go
func New(cmps ...comparator.Comparator) (pq *priority_queue) {
var cmp comparator.Comparator
if len(cmps) == 0 {
cmp = nil
} else {
cmp = cmps[0]
}
//比较器为nil时后续的增删将会去寻找默认比较器
return &priority_queue{
data: make([]interface{}, 1, 1),
len: 0,
cap: 1,
cmp: cmp,
mutex: sync.Mutex{},
}
}
```
#### Size
以priority_queue容器做接收者返回该容器当前含有元素的数量由于其数值必然是非负整数所以选用了**uint64**。
```go
func (pq *priority_queue) Size() (num uint64) {
if pq == nil {
pq = New()
}
return pq.len
}
```
#### Clear
以priority_queue容器做接收者将该容器中所承载的元素清空。
```go
func (pq *priority_queue) Clear() {
if pq == nil {
pq = New()
}
pq.mutex.Lock()
//清空已分配的空间
pq.data = make([]interface{}, 1, 1)
pq.len = 0
pq.cap = 1
pq.mutex.Unlock()
}
```
#### Empty
以priority_queue容器做接收者判断该priority_queue容器是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true该判断过程通过含有元素个数进行判断。
```go
func (pq *priority_queue) Empty() bool {
if pq == nil {
pq = New()
}
return pq.len == 0
}
```
#### Push
以priority_queue容器做接收者在该优先队列中插入元素e,利用比较器和交换使得优先队列保持相对有序状态,插入时,首先将该元素放入末尾,然后通过比较其逻辑上的父结点选择是否上移扩容策略同vector,先进行翻倍扩容,在进行固定扩容,界限为2^16。
```go
func (pq *priority_queue) Push(e interface{}) {
if pq == nil {
pq = New()
}
pq.mutex.Lock()
//判断是否存在比较器,不存在则寻找默认比较器,若仍不存在则直接结束
if pq.cmp == nil {
pq.cmp = comparator.GetCmp(e)
}
if pq.cmp == nil {
pq.mutex.Unlock()
return
}
//先判断是否需要扩容,同时使用和vector相同的扩容策略
//即先翻倍扩容再固定扩容,随后在末尾插入元素e
if pq.len < pq.cap {
//还有冗余,直接添加
pq.data[pq.len] = e
} else {
//冗余不足,需要扩容
if pq.cap <= 65536 {
//容量翻倍
if pq.cap == 0 {
pq.cap = 1
}
pq.cap *= 2
} else {
//容量增加2^16
pq.cap += 65536
}
//复制扩容前的元素
tmp := make([]interface{}, pq.cap, pq.cap)
copy(tmp, pq.data)
pq.data = tmp
pq.data[pq.len] = e
}
pq.len++
//到此时,元素以插入到末尾处,同时插入位的元素的下标为pq.len-1,随后将对该位置的元素进行上升
//即通过比较它逻辑上的父结点进行上升
pq.up(pq.len - 1)
pq.mutex.Unlock()
}
```
##### up
以priority_queue容器做接收者用于递归判断任意子结点和其父结点之间的关系满足上升条件则递归上升从而保证父节点必然都大于或都小于子节点。
```go
func (pq *priority_queue) up(p uint64) {
if p == 0 {
//以及上升到顶部,直接结束即可
return
}
if pq.cmp(pq.data[(p-1)/2], pq.data[p]) > 0 {
//判断该结点和其父结点的关系
//满足给定的比较函数的关系则先交换该结点和父结点的数值,随后继续上升即可
pq.data[p], pq.data[(p-1)/2] = pq.data[(p-1)/2], pq.data[p]
pq.up((p - 1) / 2)
}
}
```
#### Pop
以priority_queue容器做接收者在该优先队列中删除顶部元素,利用比较器和交换使得优先队列保持相对有序状态,删除时首先将首结点移到最后一位进行交换,随后删除最后一位即可,然后对首节点进行下降即可缩容时同vector一样,先进行固定缩容在进行折半缩容,界限为2^16。
```go
func (pq *priority_queue) Pop() {
if pq == nil {
pq = New()
}
if pq.Empty() {
return
}
pq.mutex.Lock()
//将最后一位移到首位,随后删除最后一位,即删除了首位,同时判断是否需要缩容
pq.data[0] = pq.data[pq.len-1]
pq.len--
//缩容判断,缩容策略同vector,即先固定缩容在折半缩容
if pq.cap-pq.len >= 65536 {
//容量和实际使用差值超过2^16时,容量直接减去2^16
pq.cap -= 65536
tmp := make([]interface{}, pq.cap, pq.cap)
copy(tmp, pq.data)
pq.data = tmp
} else if pq.len*2 < pq.cap {
//实际使用长度是容量的一半时,进行折半缩容
pq.cap /= 2
tmp := make([]interface{}, pq.cap, pq.cap)
copy(tmp, pq.data)
pq.data = tmp
}
//判断是否为空,为空则直接结束
if pq.Empty() {
pq.mutex.Unlock()
return
}
//对首位进行下降操作,即对比其逻辑上的左右结点判断是否应该下降,再递归该过程即可
pq.down(0)
pq.mutex.Unlock()
}
```
##### down
以priority_queue容器做接收者判断待下沉节点与其左右子节点的大小关系以确定是否进行递归上升从而保证父节点必然都大于或都小于子节点。
```go
func (pq *priority_queue) down(p uint64) {
q := p
//先判断其左结点是否在范围内,然后在判断左结点是否满足下降条件
if 2*p+1 <= pq.len-1 && pq.cmp(pq.data[p], pq.data[2*p+1]) > 0 {
q = 2*p + 1
}
//在判断右结点是否在范围内,同时若判断右节点是否满足下降条件
if 2*p+2 <= pq.len-1 && pq.cmp(pq.data[q], pq.data[2*p+2]) > 0 {
q = 2*p + 2
}
//根据上面两次判断,从最小一侧进行下降
if p != q {
//进行交互,递归下降
pq.data[p], pq.data[q] = pq.data[q], pq.data[p]
pq.down(q)
}
}
```
#### Top
以priority_queue容器做接收者返回该优先队列容器的顶部元素如果容器不存在或容器为空,返回nil。
```go
func (pq *priority_queue) Top() (e interface{}) {
if pq == nil {
pq = New()
}
if pq.Empty() {
return nil
}
pq.mutex.Lock()
e = pq.data[0]
pq.mutex.Unlock()
return e
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/priority_queue"
"sync"
)
func main() {
pq := priority_queue.New()
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(num int) {
pq.Push(num)
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("遍历所有元素同时弹出:")
size:=pq.Size()
for i := uint64(0); i < size; i++ {
fmt.Println(pq.Top())
pq.Pop()
}
}
```
注:虽然添加过程是随机的,但由于其本身是相对有序的,所以不论怎么添加都是一个输出结果
> 遍历所有元素同时弹出:
> 0
> 1
> 2
> 3
> 4
> 5
> 6
> 7
> 8
> 9

View File

@ -0,0 +1,304 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value而Key即是该元素。由于采用了Bit为单位来存储数据因此可以**极大的节省存储空间**。
### 原理
对于计算机来说,它可以分配的最小单位是**一个字节即8位**,一位有且仅有**1和0**两种表示可能。而如果要表示0~7八个数字在某一个集合内是否存在比如表示{035}在集合中是否存在可以使用一个字节进行存储即对应位为1的表示存在为0的表示不存在
| 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
而如果要表示超过8个的元素是否存在的话仅仅使用一个字节是远远不够的。
为了解决这个问题,可以选择使用包含更多字节的基本元素,比如**uint64**选用uint64而不是int64的原因是因为在计算机内部存储数字采用补码的形式对于一个数字如果不是非负整数的话它需要有一位去标注这个数字是否为负数而在使用bitmap的时候使用基本类型仅仅是为了使用它的位至于是否是负数并不重要既然如此舍弃掉正负数标志位就可以多利用一位进行表示。
除此之外,还可以选择多个基本元素进行叠加,即多组混合表示,以表示{03581415}为例:
| **1** | **0** | **0** | **1** | **0** | **1** | **0** | **0** |
| ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
| **1** | **0** | **0** | **0** | **0** | **0** | **1** | **1** |
即一第一行为0~7第二行及以后为n*8+0~7进行表示而对于golang实现来说可以选择用uint64的切片进行表示即在换算时候对于一个数字num它所属的行从0开始
$$
num/64
$$
它所属的列从0开始
$$
num%64
$$
### 用处
bitmap由于其本身的有序性和唯一性一来可以实现**快速排序**:即对于一组无需且**稠密**的数字可以将其放入bitmap中然后再遍历获取出来从而使得其元素是单调递增的而来可以实现**去重**由于bitmap是使用bit进行表示一个元素是否存在其中而bit有且仅有1和0两种情况所以在一个位置插入多个元素时它表示的都是1从而可以实现插入多个保留一个的去重三来可以极大的**节省存储空间**由于对基本类型数组的下标赋予了一定的含义导致可以利用其含义来表示一个数字而不是去存储一个数字即要表示一个2^16的数字时候原本需要16位现在只需要1位即可实现。
### 实现
#### 结构定义
由上文可知所要使用的bitmap内部其实是存储的uint64的切片同时增加一个zero
```go
type bitmap struct {
bits []uint64
}
```
##### 接口集合
```go
type bitmaper interface {
Insert(num uint) //在num位插入元素
Delete(num uint) //删除第num位
Check(num uint) (b bool) //检查第num位是否有元素
All() (nums []uint) //返回所有存储的元素的下标
Clear() //清空
}
```
#### New
创建部分主要做的就是返回一个初始化后的bitmap指针
```go
func New() (bm *bitmap) {
return &bitmap{
bits: make([]uint64, 0, 0),
}
}
```
#### Insert
对于bitmap的插入来说会出现两种情况第一种是要插入的位恰好可以在位图中容纳此时将对应位设为1即可并不需要对位图进行扩容实现起来十分简单找到对应位即可第二章就是要插入的位超过了位图可容纳的范围次数就需要一个扩容策略对其进行扩容以保证插入的值在位图内可以存储。
##### 扩容策略
对于bitmap扩容来说它的扩容主要是由于新要插入的位超出了位图所能表示的位从而需要对位图进行扩增以满足其范围而扩容则可以采取**两种策略**
1. **固定扩容**:对于要扩容的位图,进行一个固定的量进行扩容,该方案的好处实现简单,但坏处也明显,如果固定扩容后还是不足以覆盖还需要再次扩容,同时如果扩容太大也会需要更多的空间;
2. **按需扩容**对于要扩容的位图按照它需要扩容的范围来进行扩容该方案的好处是几乎可以恰好容纳需要存储的位但如果存在连续的几个较短的扩容出现时即先扩容到64->128>256>512>1024这种情况时需要连续的扩容复制会浪费一定的性能。
对此,我们可以考虑将两种方案结合起来,即当要增加的范围并不太大的时候,**牺牲一定量的空间**进行固定扩容,从而避免连续的小范围扩容多次出现降低性能,而对于大范围扩容时,则使用按需扩容,以此来提高单次扩容速度。
本次实现中,固定扩容采用一次增加**2^10个uint64**。
```go
func (bm *bitmap) Insert(num uint) {
//bm不存在时直接结束
if bm == nil {
return
}
//开始插入
if num/64+1 > uint(len(bm.bits)) {
//当前冗余量小于num位,需要扩增
var tmp []uint64
//通过冗余扩增减少扩增次数
if num/64+1 < uint(len(bm.bits)+1024) {
//入的位比冗余的多不足2^16即1024*64时,则新增1024个uint64
tmp = make([]uint64, len(bm.bits)+1024)
} else {
//直接增加到可以容纳第num位的位置
tmp = make([]uint64, num/64+1)
}
//将原有元素复制到新增的切片内,并将bm所指向的修改为扩增后的
copy(tmp, bm.bits)
bm.bits = tmp
}
//将第num位设为1即实现插入
bm.bits[num/64] ^= 1 << (num % 64)
}
```
#### Delete
对于位图的删除会出现三种情况第一种是要删除的范围不在位图范围内直接忽略即可第二种是在位图范围内且位图尾部存在连续为0的uint64时此时就需要执行**缩容策略**。
##### 缩容策略
对于缩容策略,同样采取三种方案:
1. **固定缩容**从尾部删除固定数量的为0的组该方案删除的量是固定的优点在于尾部量很多时可以慢慢减少剩下的可当作冗余量避免多次增加缺点是缓慢减少需要一定时间开销
2. **按需缩容**:当存在需要删除的组时,直接进行删除,优点的空间利用率更高,缺点是冗余基本没有,同时删除过于频繁
3. **折半删除**当需要删除的组超过一半时删掉一半即可该方案删除切合slice底层实现但在普遍场景来说可能并不会经常使用。
为此,可以选择将三种结合起来,当总组数很长时,且需要删除的量过多,可以使用固定缩容,留出一定余量,当删除量不多时,且需要删除的量超过了总组数的一半时,在进行按需删除,即把要删除的全部删完。
为了避免固定扩容和固定缩容循环出现对两者数值进行错位由于固定扩容选择的是1024所以固定缩容选为256。
```go
func (bm *bitmap) Delete(num uint) {
//bm不存在时直接结束
if bm == nil {
return
}
//num超出范围,直接结束
if num/64+1 > uint(len(bm.bits)) {
return
}
//将第num位设为0
bm.bits[num/64] &^= 1 << (num % 64)
if bm.bits[len(bm.bits)-1] == 0 {
//最后一组为0,可能进行缩容
//从后往前遍历判断可缩容内容是否小于总组数
i := len(bm.bits) - 1
for ; i >= 0; i-- {
if bm.bits[i] == 0 && i!=len(bm.bits)-1024{
continue
} else {
//不为0或到1024个时即可返回
break
}
}
if i <= len(bm.bits)/2 || i==len(bm.bits)-1024 {
//小于总组数一半或超过1023个,进行缩容
bm.bits = bm.bits[:i+1]
}
} else {
return
}
}
```
#### Check
检查第num位是否存在于位图种不存在返回false存在返回true超出范围也返回false。
```go
func (bm *bitmap) Check(num uint) (b bool) {
//bm不存在时直接返回false并结束
if bm == nil {
return false
}
//num超出范围,直接返回false并结束
if num/64+1 > uint(len(bm.bits)) {
return false
}
//判断第num是否为1,为1返回true,否则为false
if bm.bits[num/64]&(1<<num%64) > 0 {
return true
}
return false
}
```
#### All
返回所有在位图种存在的元素的下标,同时保证其为单调递增序列。
```go
func (bm *bitmap) All() (nums []uint) {
//对要返回的集合进行初始化,以避免返回nil
nums=make([]uint,0,0)
//bm不存在时直接返回并结束
if bm == nil {
return nums
}
//分组遍历判断某下标的元素是否存在于位图中,即其值是否为1
for j := 0; j < len(bm.bits); j++ {
for i := 0; i < 64; i++ {
if bm.bits[j]&(1<<i) > 0 {
//该元素存在,添加入结果集合内
nums = append(nums, uint(j*64+i))
}
}
}
return nums
}
```
#### Clear
清空初始化位图。
```go
func (bm *bitmap) Clear() {
if bm == nil {
return
}
bm.bits = make([]uint64, 0, 0)
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/bitmap"
)
func main() {
var nums []uint
bm:=bitmap.New()
bm.Insert(1)
bm.Insert(2)
bm.Insert(3)
bm.Insert(64)
bm.Insert(128)
bm.Insert(256)
bm.Insert(320)
nums=bm.All()
for i:=0;i< len(nums);i++{
fmt.Println(nums[i])
}
bm.Delete(320)
fmt.Println()
nums=bm.All()
for i:=0;i< len(nums);i++{
fmt.Println(nums[i])
}
bm.Delete(256)
fmt.Println()
nums=bm.All()
for i:=0;i< len(nums);i++{
fmt.Println(nums[i])
}
bm.Delete(128)
fmt.Println()
nums=bm.All()
for i:=0;i< len(nums);i++{
fmt.Println(nums[i])
}
bm.Clear()
fmt.Println()
nums=bm.All()
for i:=0;i< len(nums);i++{
fmt.Println(nums[i])
}
}
```
#### 结果
> 1
> 2
> 3
> 64
> 128
> 256
> 320
>
> 1
> 2
> 3
> 64
> 128
> 256
>
> 1
> 2
> 3
> 64
> 128
>
> 1
> 2
> 3
> 64

View File

@ -0,0 +1,605 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
前缀基数树Radix又叫基数树是前缀树的一种变种。
它和前缀树不同的地方在于它前缀树是将一个string按char进行分段保存而基数树是将**多个char设为一层**然后将string进行**分层保存**,一般利用**/**作为分层标识。
它可用于string的存储和索引当加上**模糊匹配**时也可用于实现**动态路由**。
### 原理
本次实现的前缀基数树的每个结点分别存储一层的string终结点存储整个的string以作为终结点标识。
string按以/’作为分层标识,在一组‘/之间的string为一层若该层string为空忽略不记。
同时每一层的string如果首字符为:'则视为动态匹配层即该层可以匹配任意的string而当该层首字符为'*'时也是动态匹配,且可匹配后方的所有内容。
为了加快查找速度每一个结点的子结点利用map对子结点对应层的string进行一个映射。
对于一个基数树来说,它需要满足的特征有三条:
- 父结点的前缀必然是子结点的前缀
- 根节点不包含字符除根节点以外每个节点包含一串char
- 终结点保存整个string
- 每一层的首字符为‘:'时为动态匹配即任意的不可分层string皆可
- 每一层的首字符为‘*'时为动态匹配可匹配后续所有的string不论是否可以分蹭
#### 添加策略
从根节点开始插入将string按/进行分段每层插入一个当插入到最后时结点存储的string存在时则说明之前已经插入过故插入失败否则插入成功。
当中间结点在原Radix树中不存在时创建即可。
若插入失败则需要将原Radix树种不存在的结点删除并从map中删除。
#### 删除策略
从根节点开始删除将string按'/'进行分段逐层往下遍历寻找到最终点如果此时有存储的元素则删除同时表示删除成功随后逐层返回将对应结点的num-1即可当num=0时表示无后续结点将该结点删除即可。如果在逐层下推的过程中发现结点不存在可视为删除失败。之间返回即可。
#### 匹配策略
匹配策略主要用于从radix中进行动态匹配即从中找到一个存储的可以用于进行模糊匹配的string然后将其和待匹配的s进行匹配同时将模糊匹配层的name作为key将待匹配层的string作为value放入map中并返回。
于是要解决问题就变成了如何找到一个可以对当前待匹配的string进行模糊匹配的存储结点的string。
- 将待匹配的s进行分层处理
- 从radix树的根节点开始匹配
- 匹配时遍历每一个子结点,如果不支持动态匹配则判断同层是否相等,相等则加入可匹配的子结点数组
- 如果支持动态匹配也加入可匹配的子结点数组
- 继续匹配所有支持匹配的子结点直到找到第一个满足匹配情况的结点并返回
- 从找到的结点获取它存储的string然后进行分层
- 将分层结果中的首字符为’:'或‘*'的层即支持模糊匹配的层与待匹配的string的对应层建立映射关系
- 返回映射关系和匹配是否成功
### 实现
radix前缀基数树结构体该实例存储前缀基数树的根节点同时保存该树已经存储了多少个元素。
```go
type radix struct {
root *node //前缀基数树的根节点指针
size int //当前已存放的元素数量
mutex sync.Mutex //并发控制锁
}
```
node树节点结构体该节点是radix的树节点结点存储到此时的string的前缀数量son存储其下属分叉的子结点指针该节点同时存储其元素。
```go
type node struct {
pattern string //到终点时不为"",其他都为""
part string //以当前结点的string内容
num int //以当前结点为前缀的数量
sons map[string]*node //该结点下属结点的指针
fuzzy bool //模糊匹配?该结点首字符为':'或'*'为模糊匹配
}
```
#### 接口
```go
type radixer interface {
Iterator() (i *Iterator.Iterator) //返回包含该radix的所有string
Size() (num int) //返回该radix中保存的元素个数
Clear() //清空该radix
Empty() (b bool) //判断该radix是否为空
Insert(s string) (b bool) //向radix中插入string
Erase(s string) (b bool) //从radix中删除string
Delete(s string) (num int) //从radix中删除以s为前缀的所有string
Count(s string) (num int) //从radix中寻找以s为前缀的string单词数
Mate(s string) (m map[string]string, ok bool) //利用radix树中的string对s进行模糊匹配,':'可模糊匹配该层,'*'可模糊匹配后面所有
}
```
#### New
新建一个radix前缀基数树容器并返回初始根节点为nil。
```go
func New() (r *radix) {
return &radix{
root: newNode(""),
size: 0,
mutex: sync.Mutex{},
}
}
```
新建一个前缀基数树节点并返回将传入的元素e作为该节点的承载元素。
```go
func newNode(part string) (n *node) {
fuzzy := false
if len(part) > 0 {
fuzzy = part[0] == ':' || part[0] == '*'
}
return &node{
pattern: "",
part: part,
num: 0,
sons: make(map[string]*node),
fuzzy: fuzzy,
}
}
```
##### analysis
将string按'/'进行分段解析,为""部分直接舍弃,返回解析结果同时按规则重组用以解析是string并返回。
```go
func analysis(s string) (ss []string, newS string) {
vs := strings.Split(s, "/")
ss = make([]string, 0)
newS = "/"
for _, item := range vs {
if item != "" {
ss = append(ss, item)
newS = newS + "/" + item
if item[0] == '*' {
break
}
}
}
return ss, newS
}
```
#### Iterator
以radix前缀基数树做接收者将该radix中所有存放的string放入迭代器中并返回。
```go
func (r *radix) Iterator() (i *Iterator.Iterator) {
if r == nil {
return nil
}
r.mutex.Lock()
es := r.root.inOrder("")
i = Iterator.New(&es)
r.mutex.Unlock()
return i
}
```
以node前缀基数树节点做接收者遍历其分叉以找到其存储的所有string。
```go
func (n *node) inOrder(s string) (es []interface{}) {
if n == nil {
return es
}
if n.pattern != "" {
es = append(es, s+n.part)
}
for _, son := range n.sons {
es = append(es, son.inOrder(s+n.part+"/")...)
}
return es
}
```
#### Size
以radix前缀基数树做接收者返回该容器当前含有元素的数量如果容器为nil返回0。
```go
func (r *radix) Size() (num int) {
if r == nil {
return 0
}
if r.root == nil {
return 0
}
return r.size
}
```
#### Clear
以radix前缀基数树做接收者将该容器中所承载的元素清空将该容器的size置0。
```go
func (r *radix) Clear() {
if r == nil {
return
}
r.mutex.Lock()
r.root = newNode("")
r.size = 0
r.mutex.Unlock()
}
```
#### Empty
以radix前缀基数树做接收者判断该radix是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true。
```go
func (r *radix) Empty() (b bool) {
if r == nil {
return true
}
return r.size == 0
}
```
#### Insert
以radix前缀基数树做接收者向radix插入string将对string进行解析,按'/'进行分层,':'为首则为模糊匹配该层,'*'为首则为模糊匹配后面所有,已经存在则无法重复插入。
```go
func (r *radix) Insert(s string) (b bool) {
if r == nil {
return false
}
//解析s并按规则重构s
ss, s := analysis(s)
r.mutex.Lock()
if r.root == nil {
//避免根节点为nil
r.root = newNode("")
}
//从根节点开始插入
b = r.root.insert(s, ss, 0)
if b {
//插入成功,size+1
r.size++
}
r.mutex.Unlock()
return b
}
```
以node前缀基数树节点做接收者从n节点中继续插入以s为索引的元素e,且当前抵达的string位置为p当到达s终点时进行插入,如果此时node承载了string则插入失败,否则成功,当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,当插入失败且对应子结点为新建节点时则需要删除该子结点。
```go
func (n *node) insert(pattern string, ss []string, p int) (b bool) {
if p == len(ss) {
if n.pattern != "" {
//该节点承载了string
return false
}
//成功插入
n.pattern = pattern
n.num++
return true
}
//找到该层的string
s := ss[p]
//从其子结点的map中找到对应的方向
son, ok := n.sons[s]
if !ok {
//不存在,新建并放入map中
son = newNode(s)
n.sons[s] = son
}
//从子结点对应方向继续插入
b = son.insert(pattern, ss, p+1)
if b {
n.num++
} else {
if !ok {
//插入失败且该子节点为新建结点则需要删除该子结点
delete(n.sons, s)
}
}
return b
}
```
#### Erase
以radix前缀基数树做接收者从radix树中删除元素string。
```go
func (r *radix) Erase(s string) (b bool) {
if r.Empty() {
return false
}
if len(s) == 0 {
return false
}
if r.root == nil {
//根节点为nil即无法删除
return false
}
//解析s并按规则重构s
ss, _ := analysis(s)
r.mutex.Lock()
//从根节点开始删除
b = r.root.erase(ss, 0)
if b {
//删除成功,size-1
r.size--
if r.size == 0 {
//所有string都被删除,根节点置为nil
r.root = nil
}
}
r.mutex.Unlock()
return b
}
```
以node前缀基数树节点做接收者从n节点中继续删除以s为索引的元素e,且当前抵达的string位置为p当到达s终点时进行删除,如果此时node未承载元素则删除失败,否则成功,当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,若其分叉为nil则直接失败。
```go
func (n *node) erase(ss []string, p int) (b bool) {
if p == len(ss) {
if n.pattern != "" {
//该结点承载是string是,删除成功
n.pattern = ""
n.num--
return true
}
return false
}
//从map中找到对应下子结点位置并递归进行删除
s := ss[p]
son, ok := n.sons[s]
if !ok || son == nil {
//未找到或son不存在,删除失败
return false
}
b = son.erase(ss, p+1)
if b {
n.num--
if son.num <= 0 {
//删除后子结点的num<=0即该节点无后续存储元素,可以销毁
delete(n.sons, s)
}
}
return b
}
```
#### Delete
以radix前缀基数树做接收者从radix树中删除以s为前缀的所有string。
```go
func (r *radix) Delete(s string) (num int) {
if r.Empty() {
return 0
}
if len(s) == 0 {
return 0
}
if r.root == nil {
return 0
}
//解析s并按规则重构s
ss, _ := analysis(s)
r.mutex.Lock()
//从根节点开始删除
num = r.root.delete(ss, 0)
if num > 0 {
//删除成功
r.size -= num
if r.size <= 0 {
//所有string都被删除,根节点置为nil
r.root = nil
}
}
r.mutex.Unlock()
return num
}
```
以node前缀基数树节点做接收者从n节点中继续删除以s为索引的元素e,且当前抵达的string位置为p当到达s终点时进行删除,删除所有后续元素,并返回其后续元素的数量,当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,若其分叉为nil则直接返回0。
```go
func (n *node) delete(ss []string, p int) (num int) {
if p == len(ss) {
return n.num
}
//从map中找到对应下子结点位置并递归进行删除
s := ss[p]
son, ok := n.sons[s]
if !ok || son == nil {
return 0
}
num = son.delete(ss, p+1)
if num > 0 {
son.num -= num
if son.num <= 0 {
//删除后子结点的num<=0即该节点无后续存储元素,可以销毁
delete(n.sons, s)
}
}
return num
}
```
#### Count
以radix前缀基数树做接收者从radix中查找以s为前缀的所有string的个数如果存在以s为前缀的则返回大于0的值即其数量如果未找到则返回0。
```go
func (r *radix) Count(s string) (num int) {
if r.Empty() {
return 0
}
if r.root == nil {
return 0
}
if len(s) == 0 {
return 0
}
//解析s并按规则重构s
ss, _ := analysis(s)
r.mutex.Lock()
num = r.root.count(ss, 0)
r.mutex.Unlock()
return num
}
```
以node前缀基数树节点做接收者从n节点中继续查找以s为前缀索引的元素e,且当前抵达的string位置为p当到达s终点时返回其值即可当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,当其分叉为nil则直接返回0。
```go
func (n *node) count(ss []string, p int) (num int) {
if p == len(ss) {
return n.num
}
//从map中找到对应下子结点位置并递归进行查找
s := ss[p]
son, ok := n.sons[s]
if !ok || son == nil {
return 0
}
return son.count(ss, p+1)
}
```
#### Mate
以radix前缀基数树做接收者从radix中查找以s为信息的第一个可以模糊匹配到的key和value的映射表key是radix树中的段名,value是s中的段名如果未找到则返回nil和false否则返回一个映射表和true。
```go
func (r *radix) Mate(s string) (m map[string]string, ok bool) {
if r.Empty() {
return nil, false
}
if len(s) == 0 {
return nil, false
}
if r.root == nil {
return nil, false
}
//将s按'/'进行分割,并去掉第一个即去掉"",随后一次按照分层结果进行查找
m, ok = r.root.mate(s, 0)
return m, ok
}
```
以node前缀基数树节点做接收者先从radix树的根节点开始找到第一个可以满足该模糊匹配方案的string结点随后将s和结点的pattern进行模糊映射,将模糊查找的值和匹配值进行映射并返回即可若该结点未找到则直接返回nil和false即可。
```go
func (n *node) mate(s string, p int) (m map[string]string, ok bool) {
//解析url
searchParts, _ := analysis(s)
//从该请求类型中寻找对应的路由结点
q := n.find(searchParts, 0)
if q != nil {
//解析该结点的pattern
parts, _ := analysis(q.pattern)
//动态参数映射表
params := make(map[string]string)
for index, part := range parts {
if part[0] == ':' {
//动态匹配,将参数名和参数内容的映射放入映射表内
params[part[1:]] = searchParts[index]
}
if part[0] == '*' && len(part) > 1 {
//通配符,将后续所有内容全部添加到映射表内同时结束遍历
params[part[1:]] = strings.Join(searchParts[index:], "/")
break
}
}
return params, true
}
return nil, false
}
```
以node前缀基数树节点做接收者从radix树的根节点开始找到第一个可以满足该模糊匹配方案的string结点若该结点未找到则直接返回nil。
```go
func (n *node) find(parts []string, height int) (q *node) {
//根据长度和局部string的首字符进行判断
if len(parts) == height || strings.HasPrefix(n.part, "*") {
if n.pattern == "" {
//匹配失败,该结点处无匹配的信息
return nil
}
//匹配成功,返回该结点
return n
}
//从该结点的所有子结点中查找可用于递归查找的结点
//当局部string信息和当前层string相同时可用于递归查找
//当该子结点是动态匹配时也可以用于递归查找
part := parts[height]
//从所有子结点中找到可用于递归查找的结点
children := make([]*node, 0, 0)
for _, child := range n.sons {
if child.part == part || child.fuzzy {
//局部string相同或动态匹配
children = append(children, child)
}
}
for _, child := range children {
//递归查询,并根据结果进行判断
result := child.find(parts, height+1)
if result != nil {
//存在一个满足时就可以返回
return result
}
}
return nil
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/radix"
)
func main() {
radix := radix.New()
radix.Insert("/test/:name/:key")
radix.Insert("/hlccd/:name/:key")
radix.Insert("/hlccd/1")
radix.Insert("/hlccd/a/*name")
fmt.Println("分层匹配")
m, _ := radix.Mate("/hlccd/test/abc")
for k, v := range m {
fmt.Println(k, v)
}
fmt.Println("匹配全部")
m, _ = radix.Mate("/hlccd/a/abc")
for k, v := range m {
fmt.Println(k, v)
}
fmt.Println("利用迭代器遍历")
for i := radix.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
radix.Erase("/hlccd/a/*name")
fmt.Println("利用迭代器遍历定向删除后的结果")
for i := radix.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
radix.Delete("/hlccd/")
fmt.Println("利用迭代器遍历删除前缀的结果")
for i := radix.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
}
```
> 分层匹配
> name test
> key abc
> 匹配全部
> name a
> key abc
> 利用迭代器遍历
> /test/:name/:key
> /hlccd/:name/:key
> /hlccd/1
> /hlccd/a/*name
> 利用迭代器遍历定向删除后的结果
> /test/:name/:key
> /hlccd/:name/:key
> /hlccd/1
> 利用迭代器遍历删除前缀的结果
> /test/:name/:key

View File

@ -0,0 +1,539 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
双向队列deque是一个封装了动态大小数组的顺序容器。它同其他数据结构一样都可以承载任意类型的元素但相比于队列来说它不同的点主要在于队列只能首部出尾部入而双向队列可以在首部和尾部都进行出入操作。
对于deque的实现来说虽然它也是一个线性容器但考虑到它将会在首部和尾部同时实现增删操作如果仍然使用动态数组的形式去实现虽然也可以做出来但在空间扩容缩容的时候将会进行大量的数据复制的过程而这一过程其实也是可以省去的。方法就是**使用链表的形式对它进行存储**,这样对新元素的增删就可以不需要考虑中间仍然存在的元素的复制过程,从而免除复制导致的时间浪费。
### 原理
对于deque来说考虑到完全使用链表的形式即一个结点承载一个元素除了需要较多的空间分配操作的时间开销外还需要更多的指针去记录该结点的前后结点的位置既要更多的空间分配导致的时间开销也有记录前后节点指针的空间开销。对于纯粹的链表实现来说也并不是特别良好的策略。
于是,可以考虑将**链表和数组两者结合起来**,不采用动态数组的形式,而是将一个固定容量的数组放入一个链表结点中,这样即利用了链表可以减少复制数据从而减少时间开销的优势,也可以减少记录太多指针导致的空间浪费,同时由于一次分配固定量的空间大小,也可以留作一定的冗余量,更进一步的减少分配空间导致的时间开销。
因此,它的扩容和缩容策略相对于队列容器来说有着较大的不同:
#### 添加策略
考虑到上文所说deque的扩容方案其实就只有一种了即新增链表的首节点将之前结点不能承载的元素放入新结点中同时将新结点和链表建立连接。由新的结点称为首节点/尾结点。
对于deque添加元素的过程中只会出现以下三种情况
1. deque中无结点新增一个结点同时作为首尾结点即可并将元素放入其中具体放入首部还是尾部根据添加方向来确定
2. 首/尾结点无空间容纳:新增一个结点,将新结点设为首/尾结点,并将元素放弃新结点中
3. 首/尾结点仍有空间存放:直接放入即可
#### 删除策略
同上文一样,由于采用链表结合固定数组的形式进行存储,所以它的添加和删除策略是十分类似的,即在删除的时候只需要删除一个结点即可,同时需要将**被删除的结点和链表之间的联系全部断开**,这样才能让被删除的结点被回收。
对于deque删除结点的过程中只会出现以下四种情况
1. 链表不存在:无需删除操作
2. 对应结点仍有承载元素将结点begin/end进行移动即可
3. 对应节点无承载元素了:释放该结点,同时解除该结点与链表的连接
4. 所有元素都被删除完成:销毁链表
### 实现
**注整体实现过程中将deque和链表的node进行了分离**
deque双向队列结构体包含链表的头尾节点指针当删除节点时通过头尾节点指针进入链表进行删除当一个节点全部被删除后则释放该节点,同时首尾节点做相应调整,当添加节点时若未占满节点空间时移动下标并做覆盖即可,当添加节点时空间已使用完毕时,根据添加位置新建一个新节点补充上去。
```go
type deque struct {
first *node //链表首节点指针
last *node //链表尾节点指针
size uint64 //当前存储的元素个数
mutex sync.Mutex //并发控制锁
}
```
deque双向队列中链表的node节点结构体包含一个2^10空间的固定数组用以承载元素使用begin和end两个下标用以表示新增的元素的下标,由于begin可能出现-1所以不选用uint16pre和next是该节点的前后两个节点用以保证链表整体是相连的。
```go
type node struct {
data [1024]interface{} //用于承载元素的股东数组
begin int16 //该结点在前方添加结点的下标
end int16 //该结点在后方添加结点的下标
pre *node //该结点的前一个结点
next *node //该节点的后一个结点
}
```
#### 接口
```go
type dequer interface {
Iterator() (i *Iterator.Iterator) //返回包含双向队列中所有元素的迭代器
Size() (size uint64) //返回该双向队列中元素的使用空间大小
Clear() //清空该双向队列
Empty() (b bool) //判断该双向队列是否为空
PushFront(e interface{}) //将元素e添加到该双向队列的首部
PushBack(e interface{}) //将元素e添加到该双向队列的尾部
PopFront() //将该双向队列首元素弹出
PopBack() //将该双向队列首元素弹出
Front() (e interface{}) //获取该双向队列首部元素
Back() (e interface{}) //获取该双向队列尾部元素
}
```
```go
type noder interface {
nextNode() (m *node) //返回下一个结点
preNode() (m *node) //返回上一个结点
value() (es []interface{}) //返回该结点所承载的所有元素
pushFront(e interface{}) (first *node) //在该结点头部添加一个元素,并返回新首结点
pushBack(e interface{}) (last *node) //在该结点尾部添加一个元素,并返回新尾结点
popFront() (first *node) //弹出首元素并返回首结点
popBack() (last *node) //弹出尾元素并返回尾结点
front() (e interface{}) //返回首元素
back() (e interface{}) //返回尾元素
}
```
#### New
创建一个deque容器并初始化同时返回其指针。
```go
func New() (q *queue) {
return &queue{
data: make([]interface{}, 1, 1),
begin: 0,
end: 0,
cap: 1,
mutex: sync.Mutex{},
}
}
```
创建一个首结点并初始化,同时返回其指针。
```go
func createFirst() (n *node) {
return &node{
data: [1024]interface{}{},
begin: 1023,
end: 1024,
pre: nil,
next: nil,
}
}
```
创建一个尾结点并初始化,同时返回其指针。
```go
func createLast() (n *node) {
return &node{
data: [1024]interface{}{},
begin: -1,
end: 0,
pre: nil,
next: nil,
}
}
```
#### Iterator
将双向队列中承载元素传入迭代器中,通过遍历链表中所有结点进行获取,不清除双向队列中的冗余量。返回迭代器指针,用于遍历双向队列。
```go
func (d *deque) Iterator() (i *Iterator.Iterator) {
if d == nil {
d = New()
}
tmp := make([]interface{}, 0, d.size)
//遍历链表的所有节点,将其中承载的元素全部复制出来
for m := d.first; m != nil; m = m.nextNode() {
tmp = append(tmp, m.value()...)
}
return Iterator.New(&tmp)
}
```
返回当前结点的下一个结点。
```go
func (n *node) nextNode() (m *node) {
if n == nil {
return nil
}
return n.next
}
```
返回当前结点的上一个结点。
```go
func (n *node) preNode() (m *node) {
if n == nil {
return nil
}
return n.pre
}
```
返回当前结点所承载的所有元素。
```go
func (n *node) value() (es []interface{}) {
es = make([]interface{}, 0, 0)
if n == nil {
return es
}
if n.begin > n.end {
return es
}
es = n.data[n.begin+1 : n.end]
return es
}
```
#### Size
返回deque中当前所包含的元素个数由于其数值必然是非负整数所以选用了**uint64**。
```go
func (d *deque) Size() (size uint64) {
if d == nil {
d = New()
}
return d.size
}
```
#### Clear
清空了deque中所承载的所有元素同时销毁链表的首尾结点以实现销毁链表。
```go
func (d *deque) Clear() {
if d == nil {
d = New()
return
}
d.mutex.Lock()
d.first = nil
d.last = nil
d.size = 0
d.mutex.Unlock()
}
```
#### Empty
判断deque中是否为空通过Size()是否为0进行判断。
```go
func (d *deque) Empty() (b bool) {
if d == nil {
d = New()
}
return d.Size() == 0
}
```
#### PushFront
以deque为接收者向其首部添加一个元素。
通过链表的首结点进行实现,当链表不存在时则创建一个首结点并以此作为链表首尾结点。
```go
func (d *deque) PushFront(e interface{}) {
if d == nil {
d = New()
}
d.mutex.Lock()
d.size++
//通过首节点进行添加
if d.first == nil {
d.first = createFirst()
d.last = d.first
}
d.first = d.first.pushFront(e)
d.mutex.Unlock()
}
```
以node结点做接收者向该节点前方添加元素e当该结点空间已经使用完毕后,新建一个结点并将新结点设为首结点,将插入元素放入新结点并返回新结点作为新的首结点,否则插入当前结点并返回当前结点,首结点不变。
```go
func (n *node) pushFront(e interface{}) (first *node) {
if n == nil {
return n
}
if n.begin >= 0 {
//该结点仍有空间可用于承载元素
n.data[n.begin] = e
n.begin--
return n
}
//该结点无空间承载,创建新的首结点用于存放
m := createFirst()
m.data[m.begin] = e
m.next = n
n.pre = m
m.begin--
return m
}
```
#### PushBack
以deque为接收者向其尾部添加一个元素。
通过链表的尾结点进行实现,当链表不存在时则创建一个尾结点并以此作为链表首尾结点。
```go
func (d *deque) PushBack(e interface{}) {
if d == nil {
d = New()
}
d.mutex.Lock()
d.size++
//通过尾节点进行添加
if d.last == nil {
d.last = createLast()
d.first = d.last
}
d.last = d.last.pushBack(e)
d.mutex.Unlock()
}
```
以node结点做接收者向该节点后方添加元素e当该结点空间已经使用完毕后,新建一个结点并将新结点设为尾结点,将插入元素放入新结点并返回新结点作为新的尾结点,否则插入当前结点并返回当前结点,尾结点不变。
```go
func (n *node) pushBack(e interface{}) (last *node) {
if n == nil {
return n
}
if n.end < int16(len(n.data)) {
//该结点仍有空间可用于承载元素
n.data[n.end] = e
n.end++
return n
}
//该结点无空间承载,创建新的尾结点用于存放
m := createLast()
m.data[m.end] = e
m.pre = n
n.next = m
m.end++
return m
}
```
#### PopFront
以deque双向队列容器做接收者利用首节点进行弹出元素,可能存在首节点全部释放要进行首节点后移的情况,当元素全部删除后,释放全部空间,将首尾节点都设为nil。
```go
func (d *deque) PopFront() {
if d == nil {
d = New()
}
if d.size == 0 {
return
}
d.mutex.Lock()
//利用首节点删除首元素
//返回新的首节点
d.first = d.first.popFront()
d.size--
if d.size == 0 {
//全部删除完成,释放空间,并将首尾节点设为nil
d.first = nil
d.last = nil
}
d.mutex.Unlock()
}
```
以node结点做接收者利用首节点进行弹出元素,可能存在首节点全部释放要进行首节点后移的情况, 当发生首结点后移后将会返回新首结点,否则返回当前结点。
```go
func (n *node) popFront() (first *node) {
if n == nil {
return nil
}
if n.begin < int16(len(n.data))-2 {
//该结点仍有承载元素
n.begin++
n.data[n.begin] = nil
return n
}
if n.next != nil {
//清除该结点下一节点的前结点指针
n.next.pre = nil
}
return n.next
}
```
#### PopBack
以deque双向队列容器做接收者利用尾节点进行弹出元素,可能存在尾节点全部释放要进行尾节点前移的情况,当元素全部删除后,释放全部空间,将首尾节点都设为nil。
```go
func (d *deque) PopBack() {
if d == nil {
d = New()
}
if d.size == 0 {
return
}
d.mutex.Lock()
//利用尾节点删除首元素
//返回新的尾节点
d.last = d.last.popBack()
d.size--
if d.size == 0 {
//全部删除完成,释放空间,并将首尾节点设为nil
d.first = nil
d.last = nil
}
d.mutex.Unlock()
}
```
以node结点做接收者利用尾节点进行弹出元素,可能存在尾节点全部释放要进行尾节点前移的情况,当发生尾结点前移后将会返回新尾结点,否则返回当前结点。
```go
func (n *node) popBack() (last *node) {
if n == nil {
return nil
}
if n.end > 1 {
//该结点仍有承载元素
n.end--
n.data[n.end] = nil
return n
}
if n.pre != nil {
//清除该结点上一节点的后结点指针
n.pre.next = nil
}
return n.pre
}
```
#### Front
以deque双向队列容器做接收者返回该容器的第一个元素利用首节点进行寻找若该容器当前为空,则返回nil。
```go
func (d *deque) Front() (e interface{}) {
if d == nil {
d = New()
}
return d.first.front()
}
```
以node结点做接收者返回该结点的第一个元素,利用首节点和begin进行查找若该结点为nil,则返回nil。
```go
func (n *node) front() (e interface{}) {
if n == nil {
return nil
}
return n.data[n.begin+1]
}
```
#### Back
以deque双向队列容器做接收者返回该容器的最后一个元素,利用尾节点进行寻找,若该容器当前为空,则返回nil。
```go
func (d *deque) Back() (e interface{}) {
if d == nil {
d = New()
}
return d.last.back()
}
```
以node结点做接收者返回该结点的最后一个元素,利用尾节点和end进行查找若该结点为nil,则返回nil。
```go
func (n *node) back() (e interface{}) {
if n == nil {
return nil
}
return n.data[n.end-1]
}
```
#### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/deque"
"sync"
)
func main() {
d:=deque.New()
wg:=sync.WaitGroup{}
for i := 0; i >=-4; i-- {
wg.Add(1)
go func(num int) {
d.PushFront(num)
wg.Done()
}(i)
}
for i := 0; i <= 4; i++ {
wg.Add(1)
go func(num int) {
d.PushBack(num)
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("使用迭代器遍历:")
for i:=d.Iterator();i.HasNext();i.Next(){
fmt.Println(i.Value())
}
len:=d.Size()
for i := uint64(0); i < len; i += 2 {
fmt.Println("当前首元素:",d.Front())
fmt.Println("当前尾元素:",d.Back())
d.PopFront()
d.PopBack()
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 使用迭代器遍历:
> 0
> -1
> -4
> -3
> -2
> 4
> 0
> 1
> 2
> 3
> 当前首元素: 0
> 当前尾元素: 3
> 当前首元素: -1
> 当前尾元素: 2
> 当前首元素: -4
> 当前尾元素: 1
> 当前首元素: -3
> 当前尾元素: 0
> 当前首元素: -2
> 当前尾元素: 4

View File

@ -0,0 +1,571 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
向量Vector是一个封装了动态大小数组的顺序容器。跟任意其它类型容器一样它能够存放**各种类型的对象**。可以简单的认为,向量是一个能够存放任意类型的动态数组。
对于vector的实现,考虑到它是一个线性容器,其中的元素可以通过下标在O(1)的时间复杂度直接获取访问,可以考虑**底层使用数组**的形式实现。由于vector可以动态的添加和删除元素所以需要对数组进行动态分配以满足需求。
对于golang语言来说官方并未设计泛型但由于interface可以充当任意类型的特性可以使用interface类型作为泛型类型。
### 原理
对于一个vector来说它是可以动态的对元素进行添加和修改的而如果说每一次增删元素都使得数组恰好可以容纳所有元素的话那它在以后的每一次增删的过程中都需要去**重新分配空间**,并将原有的元素复制到新数组中,如果按照这样来操作的话,每次删减都会需要花费大量的时间去进行,将会极大的降低其性能。
为了提高效率,可以选择牺牲一定的空间作为冗余量,即**时空权衡**,通过每次扩容时多分配一定的空间,这样在后续添加时就可以减少扩容次数,而在删除元素时,如果冗余量不多时,可以选择不进行空间分配,仅将后续元素前移一位,同理,在中间插入元素也仅仅将其后移一位,这样可以极大减少的由于容量不足或超出导致的扩容缩容造成的时间开销。
#### 扩容策略
对于动态数组扩容来说由于其添加是线性且连续的即每一次只会增加一个不像bitmap一样可能需要一次添加很多的空间才能保证在其范围内所以它的扩容策略相对于bitmap有所不同。
vector的扩容主要由两种方案
1. 固定扩容:固定的去增加一定量的空间,该方案时候在数组较大时添加空间,可以避免直接翻倍导致的**冗余过量**问题,到由于增加量是固定的,如果需要一次扩容很多量的话就会比较缓慢。
2. 翻倍扩容:将原有容量直接翻倍,该方案适合在数组不太大的适合添加空间,可以提高扩容量,增加扩容效率,但数组太大时使用的话会导致一次性增加太多空间,进而造成空间的浪费。
#### 缩容策略
对于动态数组的缩容来说同扩容类似由于元素的减少只会是线性且连续的即每一次只会减少一个不会存在由于一个元素被删去导致其他已使用的空间也需要被释放。所以vector的缩容方案和vector的扩容方案十分类似即是它的逆过程
1. 固定缩容:释放一个固定的空间,该方案适合在当前数组较大的时候进行,可以减缓需要缩小的量,当空间较大的时候如果采取折半缩减的话;将可能在绝大多数时间内都不会被采用,从而造成空间浪费。
2. 折半缩容:将当前容量进行折半,该方案适合数组不太大的适合进行缩容,可以更快的缩小容量,但对于较大的空间来说并不会频繁使用到。
### 实现
由于vector底层由动态数组实现所以其必然包含一个数组指针同时利用时空权衡去减少时间开销导致实际使用空间和实际占用空间不一致所以分别利用len和cap进行存储。同时为了解决在高并发情况下的数据不一致问题引入了并发控制锁。
```go
type vector struct {
data []interface{} //动态数组
len uint64 //当前已用数量
cap uint64 //可容纳元素数量
mutex sync.Mutex //并发控制锁
}
```
#### 接口
```go
type vectorer interface {
Iterator() (i *Iterator.Iterator) //返回一个包含vector所有元素的迭代器
Sort(Cmp ...comparator.Comparator) //利用比较器对其进行排序
Size() (num uint64) //返回vector的长度
Clear() //清空vector
Empty() (b bool) //返回vector是否为空,为空则返回true反之返回false
PushBack(e interface{}) //向vector末尾插入一个元素
PopBack() //弹出vector末尾元素
Insert(idx uint64, e interface{}) //向vector第idx的位置插入元素e,同时idx后的其他元素向后退一位
Erase(idx uint64) //删除vector的第idx个元素
Reverse() //逆转vector中的数据顺序
At(idx uint64) (e interface{}) //返回vector的第idx的元素
Front() (e interface{}) //返回vector的第一个元素
Back() (e interface{}) //返回vector的最后一个元素
}
```
#### New
创建一个vector容器并初始化同时返回其指针。
```go
func New() (v *vector) {
return &vector{
data: make([]interface{}, 1, 1),
len: 0,
cap: 1,
mutex: sync.Mutex{},
}
}
```
#### Iterator
以vector为接受器返回一个包含vector中所有元素的迭代器同时将vector中暂未使用的空间进行释放。
```go
func (v *vector) Iterator() (i *Iterator.Iterator) {
if v == nil {
v = New()
}
v.mutex.Lock()
if v.data == nil {
//data不存在,新建一个
v.data = make([]interface{}, 1, 1)
v.len = 0
v.cap = 1
} else if v.len < v.cap {
//释放未使用的空间
tmp := make([]interface{}, v.len, v.len)
copy(tmp, v.data)
v.data = tmp
}
//创建迭代器
i = Iterator.New(&v.data)
v.mutex.Unlock()
return i
}
```
#### Sort
以vector为接受器重载了比较器中的Sort实现了让vector利用比较器中的Sort进行排序使用过程中将会释放vector中暂未使用的空间。
```go
func (v *vector) Sort(Cmp ...comparator.Comparator) {
if v == nil {
v = New()
}
v.mutex.Lock()
if v.data == nil {
//data不存在,新建一个
v.data = make([]interface{}, 1, 1)
v.len = 0
v.cap = 1
} else if v.len < v.cap {
//释放未使用空间
tmp := make([]interface{}, v.len, v.len)
copy(tmp, v.data)
v.data = tmp
v.cap = v.len
}
//调用比较器的Sort进行排序
if len(Cmp) == 0 {
comparator.Sort(&v.data)
} else {
comparator.Sort(&v.data, Cmp[0])
}
v.mutex.Unlock()
}
```
#### Size
返回vector中当前所包含的元素个数由于其数值必然是非负整数所以选用了**uint64**。
```go
func (v *vector) Size() (num uint64) {
if v == nil {
v = New()
}
return v.len
}
```
#### Clear
清空了vector中所承载的所有元素。
```go
func (v *vector) Clear() {
if v == nil {
v = New()
}
v.mutex.Lock()
//清空data
v.data = make([]interface{}, 1, 1)
v.len = 0
v.cap = 1
v.mutex.Unlock()
}
```
#### Empty
判断vector中是否为空通过Size()是否为0进行判断。
```go
func (v *vector) Empty() (b bool) {
if v == nil {
v = New()
}
return v.Size() <= 0
}
```
#### PushBack
以vector为接受器向vector尾部添加一个元素添加元素会出现两种情况第一种是还有冗余量此时**直接覆盖**以len为下标指向的位置即可另一种情况是没有冗余量了需要对动态数组进行**扩容**,此时就需要利用扩容策略。
扩容策略上文已做描述,可返回参考,该实现过程种将两者进行了结合使用,可参考下方注释。
固定扩容值设为2^16翻倍扩容上限也为2^16。
```go
func (v *vector) PushBack(e interface{}) {
if v == nil {
v = New()
}
v.mutex.Lock()
if v.len < v.cap {
//还有冗余,直接添加
v.data[v.len] = e
} else {
//冗余不足,需要扩容
if v.cap <= 65536 {
//容量翻倍
if v.cap == 0 {
v.cap = 1
}
v.cap *= 2
} else {
//容量增加2^16
v.cap += 65536
}
//复制扩容前的元素
tmp := make([]interface{}, v.cap, v.cap)
copy(tmp, v.data)
v.data = tmp
v.data[v.len] = e
}
v.len++
v.mutex.Unlock()
}
```
#### PopBack
以vector向量容器做接收者弹出容器最后一个元素,同时长度--即可,若容器为空,则不进行弹出,当弹出元素后,可能进行缩容当容量和实际使用差值超过2^16时,容量直接减去2^16否则,当实际使用长度是容量的一半时,进行折半缩容。其缩容策略可参考前方解释。
```go
func (v *vector) PopBack() {
if v == nil {
v = New()
}
if v.Empty() {
return
}
v.mutex.Lock()
v.len--
if v.cap-v.len >= 65536 {
//容量和实际使用差值超过2^16时,容量直接减去2^16
v.cap -= 65536
tmp := make([]interface{}, v.cap, v.cap)
copy(tmp, v.data)
v.data = tmp
} else if v.len*2 < v.cap {
//实际使用长度是容量的一半时,进行折半缩容
v.cap /= 2
tmp := make([]interface{}, v.cap, v.cap)
copy(tmp, v.data)
v.data = tmp
}
v.mutex.Unlock()
}
```
#### Insert
向vector中任意位置插入一个元素由于其位置必然是非负整数所以选用了**uint64**进行表示,同时,当插入位置**超出了数组范围**时将在最近的位置进行插入由于不可能是负数所以超出范围的情况只有可能是插入下标大于len此时只需要插入尾部即可等同于PushBack的操作。
对于插入的下标在范围中时,只需要将后续的一些元素后移一位即可。
是否需要扩容需要在开始进行判断,扩容策略同上。
```go
func (v *vector) Insert(idx uint64, e interface{}) {
if v == nil {
v = New()
}
v.mutex.Lock()
if v.len >= v.cap {
//冗余不足,进行扩容
if v.cap <= 65536 {
//容量翻倍
if v.cap == 0 {
v.cap = 1
}
v.cap *= 2
} else {
//容量增加2^16
v.cap += 65536
}
//复制扩容前的元素
tmp := make([]interface{}, v.cap, v.cap)
copy(tmp, v.data)
v.data = tmp
}
//从后往前复制,即将idx后的全部后移一位即可
var p uint64
for p = v.len; p > 0 && p > uint64(idx); p-- {
v.data[p] = v.data[p-1]
}
v.data[p] = e
v.len++
v.mutex.Unlock()
}
```
#### Erase
删除下标为idx的元素idx不在数组范围内时则删除距离其最近的元素即下标不大于0删除首部不小于len删除尾部。
删除后进行缩容判断,缩容操作同上方介绍的缩容策略。
```go
func (v *vector) Erase(idx uint64) {
if v == nil {
v = New()
}
if v.Empty() {
return
}
v.mutex.Lock()
for p := idx; p < v.len-1; p++ {
v.data[p] = v.data[p+1]
}
v.len--
if v.cap-v.len >= 65536 {
//容量和实际使用差值超过2^16时,容量直接减去2^16
v.cap -= 65536
tmp := make([]interface{}, v.cap, v.cap)
copy(tmp, v.data)
v.data = tmp
} else if v.len*2 < v.cap {
//实际使用长度是容量的一半时,进行折半缩容
v.cap /= 2
tmp := make([]interface{}, v.cap, v.cap)
copy(tmp, v.data)
v.data = tmp
}
v.mutex.Unlock()
}
```
#### Reverse
以vector为接受器将vector中所承载的元素**逆转**,同时,释放掉所有未使用的空间。
```go
func (v *vector) Reverse() {
if v == nil {
v = New()
}
v.mutex.Lock()
if v.data == nil {
//data不存在,新建一个
v.data = make([]interface{}, 1, 1)
v.len = 0
v.cap = 1
} else if v.len < v.cap {
//释放未使用的空间
tmp := make([]interface{}, v.len, v.len)
copy(tmp, v.data)
v.data = tmp
v.cap=v.len
}
for i := uint64(0); i < v.len/2; i++ {
v.data[i], v.data[v.len-i-1] = v.data[v.len-i-1], v.data[i]
}
v.mutex.Unlock()
}
```
#### At
返回下标为idx的元素当idx不在数组范围内时返回nil当idx在范围内时**返回对应元素**。
```go
func (v *vector) At(idx uint64) (e interface{}) {
if v == nil {
v=New()
return nil
}
v.mutex.Lock()
if idx < 0 && idx >= v.Size() {
v.mutex.Unlock()
return nil
}
if v.Size() > 0 {
e = v.data[idx]
v.mutex.Unlock()
return e
}
v.mutex.Unlock()
return nil
}
```
#### Front
以vector为接受器返回vector所承载的元素中位于**首部**的元素如果vector为nil或者元素数组为nil或为空则返回nil。
```go
func (v *vector) Front() (e interface{}) {
if v == nil {
v=New()
return nil
}
v.mutex.Lock()
if v.Size() > 0 {
e = v.data[0]
v.mutex.Unlock()
return e
}
v.mutex.Unlock()
return nil
}
```
#### Back
以vector为接受器返回vector所承载的元素中位于**尾部**的元素如果vector为nil或者元素数组为nil或为空则返回nil。
```go
func (v *vector) Back() (e interface{}) {
if v == nil {
v=New()
return nil
}
v.mutex.Lock()
if v.Size() > 0 {
e = v.data[v.len-1]
v.mutex.Unlock()
return e
}
v.mutex.Unlock()
return nil
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/vector"
"sync"
)
func main() {
v:=vector.New()
wg:=sync.WaitGroup{}
for i:=0;i<10;i++{
wg.Add(1)
go func(num int) {
v.PushBack(num)
v.Insert(uint64(num),num)
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("随机插入后的结果:")
for i := v.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
v.Sort()
fmt.Println("排序后的结果:")
for i := v.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
fmt.Println("随机删除一半的结果:")
for i:=0;i<5;i++{
wg.Add(1)
go func(num int) {
v.PopBack()
v.Erase(uint64(i))
wg.Wait()
}(i)
}
wg.Done()
for i:=uint64(0); i<v.Size(); i++ {
fmt.Println(v.At(i))
}
fmt.Println("逆转后的结果:")
v.Reverse()
for i:=uint64(0); i<v.Size(); i++ {
fmt.Println(v.At(i))
}
fmt.Println("在下标5处插入元素-1的结果:")
v.Insert(5,-1)
for i:=uint64(0); i<v.Size(); i++ {
fmt.Println(v.At(i))
}
fmt.Println("首元素:",v.Front())
fmt.Println("尾元素:",v.Back())
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 随机插入后的结果:
> 0
> 1
> 0
> 2
> 3
> 5
> 6
> 7
> 4
> 8
> 1
> 9
> 2
> 3
> 4
> 5
> 6
> 9
> 8
> 7
> 排序后的结果:
> 0
> 0
> 1
> 1
> 2
> 2
> 3
> 3
> 4
> 4
> 5
> 5
> 6
> 6
> 7
> 7
> 8
> 8
> 9
> 9
> 随机删除一半的结果:
> 0
> 0
> 1
> 3
> 3
> 5
> 5
> 6
> 6
> 7
> 逆转后的结果:
> 7
> 6
> 6
> 5
> 5
> 3
> 3
> 1
> 0
> 0
> 在下标5处插入元素-1的结果:
> 7
> 6
> 6
> 5
> 5
> -1
> 3
> 3
> 1
> 0
> 0
> 首元素: 7
> 尾元素: 0

View File

@ -0,0 +1,532 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
哈希映射hash map它是一个两层结构即第一层以动态数组作为桶去存储元素第二层存储hash值冲突的元素。
对于插入其中的任意一个元素来说都可以计算其key的hash值然后将其映射到桶内对应位置随后再插入即可。
hash映射最大的特点在于其查找、插入和删除都是O1但可能存在扩容和缩容的问题此时其时间开销会增加。
### 原理
对于哈希映射来说它需要做的主要是对key进行hash计算将计算值映射到hash桶的对应位置插入其中。
本次实现中动态数组即hash桶复用之前实现过的vector而当出现hash冲突时候以avl树存储存在冲突的值。
对于其桶的扩容和缩容来说都可以依靠vector来完成扩容缩容后重新将所有key-value插入即可。
当然对于hash映射来说最优的情况就是所有hash都不会冲突即avl树仅有根节点这样对于插入和删除都会更快一些所以就需要尽可能的避免出现hash冲突的情况即设计一个良好的hash函数。
一般来说hash函数可以采用**平方折半法、逐位累加法**考虑到数值类的遍历在诸位累计会消耗大量时间故对于数值类型采用平方折半法对于string类型采用逐位累加法。
vector的初始容量和最小容量为**16**该值为经验值设为16的时候hash冲突情况最小。
而对于扩容和缩容来说,需要一个**扩容因子**即当前存储容量达到总容量的扩容因子的倍数时候需要扩容反之当当前存储容量小于缩容后的扩容因子的倍数时则需要缩容。对于扩容因子来说若设置的过小虽然冲突的概率降低但空间利用率也急剧下降而当设置过大时空间利用率提高了但hash冲突的情况增多需要的时间成本提高。本次实现中将扩容因子设为**0.75**。该值是根据**泊松分布和指数分布**计算出的出现冲突的可能性最小的最优解。
### 实现
hashMap哈希映射结构体该实例存储第一层vector的指针同时保存hash函数哈希映射中的hash函数在创建时传入,若不传入则在插入首个key-value时从默认hash函数中寻找。
```go
type hashMap struct {
arr *vector.Vector //第一层的vector
hash algorithm.Hasher //hash函数
size uint64 //当前存储数量
cap uint64 //vector的容量
mutex sync.Mutex //并发控制锁
}
```
索引结构体存储key-value结构。
```go
type node struct {
value interface{} //节点中存储的元素
num int //该元素数量
depth int //该节点的深度
left *node //左节点指针
right *node //右节点指针
}
```
#### 接口
```go
type hashMaper interface {
Iterator() (i *Iterator.Iterator) //返回一个包含hashMap容器中所有value的迭代器
Size() (num uint64) //返回hashMap已存储的元素数量
Cap() (num uint64) //返回hashMap中的存放空间的容量
Clear() //清空hashMap
Empty() (b bool) //返回hashMap是否为空
Insert(key, value interface{}) (b bool) //向hashMap插入以key为索引的value,若存在会覆盖
Erase(key interface{}) (b bool) //删除hashMap中以key为索引的value
GetKeys() (keys []interface{}) //返回hashMap中所有的keys
Get(key interface{}) (value interface{}) //以key为索引寻找vlue
}
```
#### New
新建一个hashMap哈希映射容器并返回初始vector长度为16若有传入的hash函数,则将传入的第一个hash函数设为该hash映射的hash函数。
```go
func New(hash ...algorithm.Hasher) (hm *hashMap) {
var h algorithm.Hasher
if len(hash) == 0 {
h = nil
} else {
h = hash[0]
}
cmp := func(a, b interface{}) int {
ka, kb := a.(*indexes), b.(*indexes)
return comparator.GetCmp(ka.key)(ka.key, kb.key)
}
//新建vector并将其扩容到16
v := vector.New()
for i := 0; i < 16; i++ {
//vector中嵌套avl树
v.PushBack(avlTree.New(false, cmp))
}
return &hashMap{
arr: v,
hash: h,
size: 0,
cap: 16,
mutex: sync.Mutex{},
}
}
```
#### Iterator
以hashMap哈希映射做接收者将该hashMap中所有保存的索引指针的value放入迭代器中。
```go
func (hm *hashMap) Iterator() (i *Iterator.Iterator) {
if hm == nil {
return nil
}
if hm.arr == nil {
return nil
}
hm.mutex.Lock()
//取出hashMap中存放的所有value
values := make([]interface{}, 0, 1)
for i := uint64(0); i < hm.arr.Size(); i++ {
avl := hm.arr.At(i).(*avlTree.AvlTree)
ite := avl.Iterator()
es := make([]interface{}, 0, 1)
for j := ite.Begin(); j.HasNext(); j.Next() {
idx := j.Value().(*indexes)
es = append(es, idx.value)
}
values = append(values, es...)
}
//将所有value放入迭代器中
i = Iterator.New(&values)
hm.mutex.Unlock()
return i
}
```
#### Size
以hashMap哈希映射做接收者返回该容器当前含有元素的数量如果容器为nil返回0。
```go
func (hm *hashMap) Size() (num uint64) {
if hm == nil {
return 0
}
return hm.size
}
```
#### Cap
以hashMap哈希映射做接收者返回该容器当前容量如果容器为nil返回0。
```go
func (hm *hashMap) Cap() (num uint64) {
if hm == nil {
return 0
}
return hm.cap
}
```
#### Clear
以hashMap哈希映射做接收者将该容器中所承载的元素清空将该容器的size置0,容量置为16,vector重建并扩容到16。
```go
func (hm *hashMap) Clear() {
if hm == nil {
return
}
hm.mutex.Lock()
//重建vector并扩容到16
v := vector.New()
cmp := func(a, b interface{}) int {
ka, kb := a.(*indexes), b.(*indexes)
return comparator.GetCmp(ka.key)(ka.key, kb.key)
}
for i := 0; i < 16; i++ {
v.PushBack(avlTree.New(false, cmp))
}
hm.arr = v
hm.size = 0
hm.cap = 16
hm.mutex.Unlock()
}
```
#### Empty
以hashMap哈希映射做接收者判断该哈希映射中是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true。
```go
func (hm *hashMap) Empty() (b bool) {
if hm == nil {
return false
}
return hm.size > 0
}
```
#### Insert
以hashMap哈希映射做接收者向哈希映射入元素e,若已存在相同key则进行覆盖,覆盖仍然视为插入成功若不存在相同key则直接插入即可。
```go
func (hm *hashMap) Insert(key, value interface{}) (b bool) {
if hm == nil {
return false
}
if hm.arr == nil {
return false
}
if hm.hash == nil {
hm.hash = algorithm.GetHash(key)
}
if hm.hash == nil {
return false
}
hm.mutex.Lock()
//计算hash值并找到对应的avl树
hash := hm.hash(key) % hm.cap
avl := hm.arr.At(hash).(*avlTree.AvlTree)
idx := &indexes{
key: key,
value: value,
}
//判断是否存在该avl树中
if avl.Count(idx) == 0 {
//avl树中不存在相同key,插入即可
avl.Insert(idx)
hm.size++
if hm.size >= hm.cap/4*3 {
//当达到扩容条件时候进行扩容
hm.expend()
}
} else {
//覆盖
avl.Insert(idx)
}
hm.mutex.Unlock()
return true
}
```
##### expend
以hashMap哈希映射做接收者对原vector进行扩容将所有的key-value取出,让vector自行扩容并清空原有结点扩容后将所有的key-value重新插入vector中。
```go
func (hm *hashMap) expend() {
//取出所有的key-value
idxs := make([]*indexes, 0, hm.size)
for i := uint64(0); i < hm.arr.Size(); i++ {
avl := hm.arr.At(i).(*avlTree.AvlTree)
ite := avl.Iterator()
for j := ite.Begin(); j.HasNext(); j.Next() {
idxs = append(idxs, j.Value().(*indexes))
}
}
cmp := func(a, b interface{}) int {
ka, kb := a.(*indexes), b.(*indexes)
return comparator.GetCmp(ka.key)(ka.key, kb.key)
}
//对vector进行扩容,扩容到其容量上限即可
hm.arr.PushBack(avlTree.New(false, cmp))
for i := uint64(0); i < hm.arr.Size()-1; i++ {
hm.arr.At(i).(*avlTree.AvlTree).Clear()
}
for i := hm.arr.Size(); i < hm.arr.Cap(); i++ {
hm.arr.PushBack(avlTree.New(false, cmp))
}
//将vector容量设为hashMap容量
hm.cap = hm.arr.Cap()
//重新将所有的key-value插入到hashMap中去
for i := 0; i < len(idxs); i++ {
key, value := idxs[i].key, idxs[i].value
hash := hm.hash(key) % hm.cap
avl := hm.arr.At(hash).(*avlTree.AvlTree)
idx := &indexes{
key: key,
value: value,
}
avl.Insert(idx)
}
}
```
#### Erase
以hashMap哈希映射做接收者从hashMap中删除以key为索引的value若存在则删除,否则直接结束,删除成功后size-1删除后可能出现size<0.75/2*cap且大于16,此时要缩容
```go
func (hm *hashMap) Erase(key interface{}) (b bool) {
if hm == nil {
return false
}
if hm.arr == nil {
return false
}
if hm.hash == nil {
return false
}
hm.mutex.Lock()
//计算该key的hash值
hash := hm.hash(key) % hm.cap
avl := hm.arr.At(hash).(*avlTree.AvlTree)
idx := &indexes{
key: key,
value: nil,
}
//从对应的avl树中删除该key-value
b = avl.Erase(idx)
if b {
//删除成功,此时size-1,同时进行缩容判断
hm.size--
if hm.size < hm.cap/8*3 && hm.cap > 16 {
hm.shrink()
}
}
hm.mutex.Unlock()
return b
}
```
##### shrink
以hashMap哈希映射做接收者对原vector进行缩容将所有的key-value取出,让vector自行缩容并清空所有结点当vector容量与缩容开始时不同时则视为缩容结束随容后将所有的key-value重新插入vector中。
```go
func (hm *hashMap) shrink() {
//取出所有key-value
idxs := make([]*indexes, 0, hm.size)
for i := uint64(0); i < hm.arr.Size(); i++ {
avl := hm.arr.At(i).(*avlTree.AvlTree)
ite := avl.Iterator()
for j := ite.Begin(); j.HasNext(); j.Next() {
idxs = append(idxs, j.Value().(*indexes))
}
}
//进行缩容,当vector的cap与初始不同时,说明缩容结束
cap := hm.arr.Cap()
for ; cap == hm.arr.Cap(); {
hm.arr.PopBack()
}
hm.cap = hm.arr.Cap()
//将所有的key-value重新放入hashMap中
for i := 0; i < len(idxs); i++ {
key, value := idxs[i].key, idxs[i].value
hash := hm.hash(key) % hm.cap
avl := hm.arr.At(hash).(*avlTree.AvlTree)
idx := &indexes{
key: key,
value: value,
}
avl.Insert(idx)
}
}
```
#### GetKeys
以hashMap哈希映射做接收者返回该hashMap中所有的key。
```go
func (hm *hashMap) GetKeys() (keys []interface{}) {
if hm == nil {
return nil
}
if hm.arr == nil {
return nil
}
hm.mutex.Lock()
keys = make([]interface{}, 0, 1)
for i := uint64(0); i < hm.arr.Size(); i++ {
avl := hm.arr.At(i).(*avlTree.AvlTree)
ite := avl.Iterator()
es := make([]interface{}, 0, 1)
for j := ite.Begin(); j.HasNext(); j.Next() {
idx := j.Value().(*indexes)
es = append(es, idx.key)
}
keys = append(keys, es...)
}
hm.mutex.Unlock()
return keys
}
```
#### Get
以hashMap哈希映射做接收者以key寻找到对应的value并返回。
```go
func (hm *hashMap) Get(key interface{}) (value interface{}) {
if hm == nil {
return
}
if hm.arr == nil {
return
}
if hm.hash == nil {
hm.hash = algorithm.GetHash(key)
}
if hm.hash == nil {
return
}
hm.mutex.Lock()
//计算hash值
hash := hm.hash(key) % hm.cap
//从avl树中找到对应该hash值的key-value
info := hm.arr.At(hash).(*avlTree.AvlTree).Find(&indexes{key: key, value: nil})
hm.mutex.Unlock()
if info == nil {
return nil
}
return info.(*indexes).value
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/hashMap"
)
func main() {
m:=hashMap.New()
for i:=1;i<=17;i++{
m.Insert(i,i)
}
fmt.Println("size=",m.Size())
keys:=m.GetKeys()
fmt.Println(keys)
for i:=0;i< len(keys);i++{
fmt.Println(m.Get(keys[i]))
}
for i:=m.Iterator().Begin();i.HasNext();i.Next(){
fmt.Println(i.Value())
}
for i:=0;i< len(keys);i++{
m.Erase(keys[i])
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> size= 17
> [1 8 16 2 14 3 4 9 12 5 15 17 6 10 13 7 11]
> 1
> 8
> 16
> 2
> 14
> 3
> 4
> 9
> 12
> 5
> 15
> 17
> 6
> 10
> 13
> 7
> 11
> 1
> 8
> 16
> 2
> 14
> 3
> 4
> 9
> 12
> 5
> 15
> 17
> 6
> 10
> 13
> 7
> 11
#### 时间开销
插入的是随机数即可能出现重复插入覆盖的问题所以数值会≤max
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/hashMap"
"math/rand"
"time"
)
func main() {
max := 3000000
num := 0
r := rand.New(rand.NewSource(time.Now().UnixNano()))
tm := time.Now()
m := make(map[int]bool)
for i := 0; i < max; i++ {
num=r.Intn(4294967295)
m[num] = true
}
fmt.Println("map消耗时间:", time.Since(tm))
thm := time.Now()
hm := hashMap.New()
for i := 0; i < max; i++ {
num=r.Intn(4294967295)
hm.Insert(uint64(num), true)
}
fmt.Println("hashmap添加消耗时间:", time.Since(thm))
thmd := time.Now()
keys:=hm.GetKeys()
for i := 0; i < len(keys); i++ {
hm.Get(keys[i])
}
fmt.Println("size=",hm.Size())
fmt.Println("hashmap遍历消耗时间:", time.Since(thmd))
}
```
> map消耗时间: 484.6992ms
> hashmap添加消耗时间: 4.6206379s
> size= 2999043
> hashmap遍历消耗时间: 2.2020792s

View File

@ -0,0 +1,493 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
本次采用**完全二叉树Complete Binary Tree**的形式实现堆。
heap是一类特殊的数据结构的统称堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质
- 堆中某个结点的值总是不大于或不小于其父结点的值;
- 堆总是一棵完全二叉树。
堆的主要特点为:它的**父结点必然是小于或等于左右子结点**(该文章叙述中使用**小顶堆**,大顶堆大小情况相反)。
在本次实践过程中使用**完全二叉树**实现。
### 原理
##### 完全二叉树实现堆
对于一个完全二叉树来说,首先,完全二叉树除开底部的一层可能存在不满的情况外,其他上层必然是满的。对于完全二叉树的一个节点来说,它的左节点和右节点不使用下标来进行取得,而是使用其指针进行寻找。
同时,在实现中,需要使用**size**来记录该堆中累计存储了多少元素。
在插入和删除过程中,同数组实现类似,插入需要在最后一个节点插入,随后上升即可,删除需要将首节点替换为尾节点,随后下降首届点。而这两个过程中,核心点就在于如何**查找尾节点**。
##### 查找尾节点
在查找尾节点时,可以参考数组实现过程中的下标,同时由于只需要查找尾节点,而尾节点的下标,恰好是**size-1**的值。
所以,我们可以利用堆当前存储的元素数量去寻找最后一个节点和其父节点。
同数组类似“下标”从0开始观察前一组插入的节点size+1且为二进制
- 第一个节点->10
- 第二个节点->11
- 第三个节点->100
- 第四个节点->101
- 第五个节点->110
- 第六个节点->111
- 第七个节点->1000
- 第八个节点->1001
- 第九个节点->1010
- ······
同时观察其在二叉树的位置可知其可以根据其下标转化为二进制的值及1010101串来找到插入节点的父节点
- 第11个节点**1100**该节点从根节点获取的路径为:**右左左**
- 第15给节点**10000**该节点从根节点获取的路径为:**左左左左**
- 第18给节点**10011**该节点从根节点获取的路径为:**左左右右**
- ···
观察可知对于添加节点来说可以通过其下标即当前的size+1转化为二进制随后删掉其第一个bit在根据后续的bit位将1看作转入右节点将0转入左节点即可寻找到对应的节点而若要找到其父节点只需要减少一步跳转即可。
而对于删除节点来说则不需要先减少size直接找到末尾节点即可方法同上。
#### 添加策略
在解决了查找尾节点和其父节点的问题之后,对于添加策略其实和数组形式实现是基本一样的。
即:当插入一个结点的时候,可以先将其放入完全二叉搜的末尾,然后根据插入的值和它父结点进行比较,如果插入值小于父节点时则交换两结点值,随后在用交换后的结点与其父结点比较,重复该过程直到抵达顶点或不满足条件即可。
##### 添加步骤
1. 通过查找尾节点的算法找到对应尾节点和其父节点,利用父节点插入
2. 结点放入尾部后,通过比较该结点与父结点的值进行交换
3. 满足交换条件重复回到2过程
4. 到达顶部或不满足交换条件,添加结束,插入完成
#### 删除策略
完全二叉树实现堆的方式的删除策略类似于数组形式实现堆的删除策略,区别只是在需要用特殊的查找尾节点及其父节点的方法(上文已做介绍)。
即:不同于添加时直接利用尾结点的情况,由于删除的是首结点,而同时删除需要减少一个空间,所以可以考虑将首位结点进行交换,或者其实直接**用尾结点覆写首节点**即可这样就实现了首节点的删除同时利用之前查找到的尾节点的父节点将其对应侧的指针设为nil即实现了尾节点的删除。
上一过程完成后,首节点以及被删除了,但移到首节点的新首节点可能并不满足优先队列的父结点必然小于或等于左右子结点的情况,所以要通过比较父结点和其左右子结点来进行下将操作,即当存在一个在数组范围内的结点且大于父结点时则交换两结点,然后递归该过程,直到触底或不满足条件。
比较过程中,先比较左结点与父结点的情况,然后再比较右结点的情况,找到最小的一侧进行下降即可。
##### 删除步骤
1. 通过查找尾节点的方法找到尾节点的父节点,并用尾节点覆盖首节点。
2. 删除尾节点
3. 以首节点为父结点,对其左右结点进行下降判断:左右节点是否存在,且存在的左右节点是否存在小于父结点的情况,
4. 比对左右两侧找到满足小于父结点且最小的一侧进行下降即可同时返回3过程进行递归下降
5. 触底或不满足条件时下降结束,删除完成。
### 实现
cbTree完全二叉树树结构体该实例存储二叉树的根节点同时保存该二叉树已经存储了多少个元素二叉树中排序使用的比较器在创建时传入,若不传入则在插入首个节点时从默认比较器中寻找。
```go
type cbTree struct {
root *node //根节点指针
size uint64 //存储元素数量
cmp comparator.Comparator //比较器
mutex sync.Mutex //并发控制锁
}
```
node树节点结构体该节点是完全二叉树的树节点该节点除了保存承载元素外,还将保存父节点、左右子节点的指针。
```go
type node struct {
value interface{} //节点中存储的元素
parent *node //父节点指针
left *node //左节点指针
right *node //右节点指针
}
```
#### 接口
```go
type cbTreer interface {
Iterator() (i *Iterator.Iterator) //返回包含该二叉树的所有元素
Size() (num uint64) //返回该二叉树中保存的元素个数
Clear() //清空该二叉树
Empty() (b bool) //判断该二叉树是否为空
Push(e interface{}) //向二叉树中插入元素e
Pop() //从二叉树中弹出顶部元素
Top() (e interface{}) //返回该二叉树的顶部元素
}
```
#### New
新建一个cbTree完全二叉树容器并返回初始根节点为nil若有传入的比较器,则将传入的第一个比较器设为该二叉树的比较器。
```go
func New(Cmp ...comparator.Comparator) (cb *cbTree) {
//判断是否有传入比较器,若有则设为该二叉树默认比较器
var cmp comparator.Comparator
if len(Cmp) > 0 {
cmp = Cmp[0]
}
return &cbTree{
root: nil,
size: 0,
cmp: cmp,
mutex: sync.Mutex{},
}
}
```
##### newNode
新建一个完全二叉树节点并返回将传入的元素e作为该节点的承载元素将传入的parent节点作为其父节点,左右节点设为nil。
```go
func newNode(parent *node, e interface{}) (n *node) {
return &node{
value: e,
parent: parent,
left: nil,
right: nil,
}
}
```
#### Iterator
以cbTree完全二叉树做接收者将该二叉树中所有保存的元素将从根节点开始以前缀序列的形式放入迭代器中。
```go
func (cb *cbTree) Iterator() (i *Iterator.Iterator) {
if cb == nil {
cb = New()
}
cb.mutex.Lock()
es := cb.root.frontOrder()
i = Iterator.New(&es)
cb.mutex.Unlock()
return i
}
```
##### frontOrder
以node节点做接收者以前缀序列返回节点集合。
```go
func (n *node) frontOrder() (es []interface{}) {
if n == nil {
return es
}
es = append(es, n.value)
if n.left != nil {
es = append(es, n.left.frontOrder()...)
}
if n.right != nil {
es = append(es, n.right.frontOrder()...)
}
return es
}
```
#### Size
以cbTree完全二叉树做接收者返回该容器当前含有元素的数量如果容器为nil返回0。
```go
func (cb *cbTree) Size() (num uint64) {
if cb == nil {
cb = New()
}
return cb.size
}
```
#### Clear
以cbTree完全二叉树做接收者将该容器中所承载的元素清空将该容器的size置0。
```go
func (cb *cbTree) Clear() {
if cb == nil {
cb = New()
}
cb.mutex.Lock()
cb.root = nil
cb.size = 0
cb.mutex.Unlock()
}
```
#### Empty
以cbTree完全二叉树做接收者判断该完全二叉树树是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true。
```go
func (cb *cbTree) Empty() (b bool) {
if cb == nil {
cb = New()
}
return cb.size == 0
}
```
#### lastParent
以node节点做接收者根据传入数值通过转化为二进制的方式模拟查找最后一个父节点由于查找父节点的路径等同于转化为二进制后除开首位的中间值,故该方案是可行的。
```go
func (n *node) lastParent(num uint64) (ans *node) {
if num > 3 {
//去掉末尾的二进制值
arr := make([]byte, 0, 64)
ans = n
for num > 0 {
//转化为二进制
arr = append(arr, byte(num%2))
num /= 2
}
//去掉首位的二进制值
for i := len(arr) - 2; i >= 1; i-- {
if arr[i] == 1 {
ans = ans.right
} else {
ans = ans.left
}
}
return ans
}
return n
}
```
#### Push
以cbTree完全二叉树做接收者向二叉树插入元素e,将其放入完全二叉树的最后一个位置,随后进行元素上升,如果二叉树本身为空,则直接将根节点设为插入节点元素即可。
```go
func (cb *cbTree) Push(e interface{}) {
if cb == nil {
cb=New()
}
cb.mutex.Lock()
if cb.Empty() {
if cb.cmp == nil {
cb.cmp = comparator.GetCmp(e)
}
if cb.cmp == nil {
cb.mutex.Unlock()
return
}
cb.root = newNode(nil, e)
cb.size++
} else {
cb.size++
cb.root.insert(cb.size, e, cb.cmp)
}
cb.mutex.Unlock()
}
```
##### insert
以node节点做接收者从该节点插入元素e,并根据传入的num寻找最后一个父节点用于插入最后一位值随后对插入值进行上升处理。
```go
func (n *node) insert(num uint64, e interface{}, cmp comparator.Comparator) {
if n == nil {
return
}
//寻找最后一个父节点
n = n.lastParent(num)
//将元素插入最后一个节点
if num%2 == 0 {
n.left = newNode(n, e)
n = n.left
} else {
n.right = newNode(n, e)
n = n.right
}
//对插入的节点进行上升
n.up(cmp)
}
```
##### up
以node节点做接收者对该节点进行上升当该节点存在且父节点存在时,若该节点小于夫节点,则在交换两个节点值后继续上升即可。
```go
func (n *node) up(cmp comparator.Comparator) {
if n == nil {
return
}
if n.parent == nil {
return
}
//该节点和父节点都存在
if cmp(n.parent.value, n.value) > 0 {
//该节点值小于父节点值,交换两节点值,继续上升
n.parent.value, n.value = n.value, n.parent.value
n.parent.up(cmp)
}
}
```
#### Pop
以cbTree完全二叉树做接收者从完全二叉树中删除顶部元素e将该顶部元素于最后一个元素进行交换随后删除最后一个元素再将顶部元素进行下沉处理即可。
```go
func (cb *cbTree) Pop() {
if cb == nil {
return
}
if cb.Empty() {
return
}
cb.mutex.Lock()
if cb.size == 1 {
//该二叉树仅剩根节点,直接删除即可
cb.root = nil
} else {
//该二叉树删除根节点后还有其他节点可生为跟节点
cb.root.delete(cb.size, cb.cmp)
}
cb.size--
cb.mutex.Unlock()
}
```
##### delete
以node节点做接收者从删除该,并根据传入的num寻找最后一个父节点用于替换删除随后对替换后的值进行下沉处理即可
```go
func (n *node) delete(num uint64, cmp comparator.Comparator) {
if n == nil {
return
}
//寻找最后一个父节点
ln := n.lastParent(num)
if num%2 == 0 {
n.value = ln.left.value
ln.left = nil
} else {
n.value = ln.right.value
ln.right = nil
}
//对交换后的节点进行下沉
n.down(cmp)
}
```
##### down
以node节点做接收者对该节点进行下沉当该存在右节点且小于自身元素时,与右节点进行交换并继续下沉,否则当该存在左节点且小于自身元素时,与左节点进行交换并继续下沉,当左右节点都不存在或都大于自身时下沉停止。
```go
func (n *node) down(cmp comparator.Comparator) {
if n == nil {
return
}
if n.right != nil && cmp(n.left.value, n.right.value) >= 0 {
n.right.value, n.value = n.value, n.right.value
n.right.down(cmp)
return
}
if n.left != nil && cmp(n.value, n.left.value) >= 0 {
n.left.value, n.value = n.value, n.left.value
n.left.down(cmp)
return
}
}
```
#### Top
以cbTree完全二叉树做接收者返回该完全二叉树的顶部元素当该完全二叉树不存在或根节点不存在时返回nil。
```go
func (cb *cbTree) Top() (e interface{}) {
if cb == nil {
cb=New()
}
cb.mutex.Lock()
e = cb.root.value
cb.mutex.Unlock()
return e
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/heap"
"sync"
)
func main() {
h := heap.New()
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(num int) {
h.Push(num)
}(i)
}
fmt.Println("利用迭代器输出堆中存储的所有元素:")
for i := h.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
fmt.Println("依次输出顶部元素:")
for !h.Empty() {
fmt.Println(h.Top())
h.Pop()
}
}
```
注:虽然添加过程是随机的,但由于其本身是相对有序的,所以不论怎么添加都是一个输出结果
> 利用迭代器输出堆中存储的所有元素:
> 0
> 3
> 4
> 6
> 8
> 5
> 9
> 1
> 2
> 7
> 依次输出顶部元素:
> 0
> 1
> 2
> 3
> 4
> 5
> 6
> 7
> 8
> 9

View File

@ -0,0 +1,582 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
单词查找树Tire又叫前缀树字典树是一种有序多叉树。不同于之前实现的二叉搜它是一个具有多个分叉的树的结构同时树结点和其子结点之间并无大小关系只存在前缀关系即其**父结点是其子结点的前缀**一般用于存储string类型对于string类型的增删查效率极高其**增删查时间等价于string的长度**。
### 原理
本次实现的单词查找树的每个结点共有64个分叉a'~'z','A'~'Z','0'~'9','+','/'一共64个字符对应base64的64个字符可用于存储base64。
对于一个前缀树来说,它需要满足的特征有三条:
- 父结点的前缀必然是子结点的前缀
- 根节点不包含字符,除根节点以外每个节点只包含一个字符
- 每个节点的所有子节点包含的字符串不相同。
它的核心策略是以空间换时间即将一个string类型拆开分层保存其byte或叫char使得每次增删查都只需要其长度的时间最坏的查找时间比hash表更好。同时也不会出现冲突并且也必然是满足字典序即按其中序遍历得到的结果必然是有序的。
但同时如果出现了一个较长的string就会让整个链条变得很长造成较多的空间开销。
#### 添加策略
从根节点开始插入将string按byte进行分段每层插入一个当插入到最后时该string指向的value存在时则说明之前已经插入过故插入失败否则插入成功。
插入不允许覆盖。
当中间结点在原Trie树中不存在时创建即可。
#### 删除策略
从根节点开始删除将string按byte进行分段逐层往下遍历寻找到最终点如果此时有存储的元素则删除同时表示删除成功随后逐层返回将对应结点的num-1即可当num=0时表示无后续结点将该结点删除即可。如果在逐层下推的过程中发现结点不存在可视为删除失败。之间返回即可。
### 实现
trie单词查找树结构体该实例存储单词查找树的根节点同时保存该树已经存储了多少个元素整个树不允许重复插入,若出现重复插入则直接失败。
```go
type trie struct {
root *node //根节点指针
size int //存放的元素数量
mutex sync.Mutex //并发控制锁
}
```
node树节点结构体该节点是trie的树节点结点存储到此时的string的前缀数量以son为分叉存储下属的string该节点同时存储其元素。
```go
type node struct {
num int //以当前结点为前缀的string的数量
son [64]*node //分叉
value interface{} //当前结点承载的元素
}
```
#### 接口
```go
type trieer interface {
Iterator() (i *Iterator.Iterator) //返回包含该trie的所有string
Size() (num int) //返回该trie中保存的元素个数
Clear() //清空该trie
Empty() (b bool) //判断该trie是否为空
Insert(s string, e interface{}) (b bool) //向trie中插入string并携带元素e
Erase(s string) (b bool) //从trie中删除以s为索引的元素e
Delete(s string) (num int) //从trie中删除以s为前缀的所有元素
Count(s string) (num int) //从trie中寻找以s为前缀的string单词数
Find(s string) (e interface{}) //从trie中寻找以s为索引的元素e
}
```
#### New
新建一个trie单词查找树容器并返回初始根节点为nil。
```go
func New() (t *trie) {
return &trie{
root: newNode(nil),
size: 0,
mutex: sync.Mutex{},
}
}
```
新建一个单词查找树节点并返回将传入的元素e作为该节点的承载元素。
```go
func newNode(e interface{}) (n *node) {
return &node{
num: 0,
value: e,
}
}
```
#### Iterator
以trie单词查找树做接收者将该trie中所有存放的string放入迭代器中并返回。
```go
func (t *trie) Iterator() (i *Iterator.Iterator) {
if t == nil {
return nil
}
t.mutex.Lock()
//找到trie中存在的所有string
es := t.root.inOrder("")
i = Iterator.New(&es)
t.mutex.Unlock()
return i
}
```
以node单词查找树节点做接收者遍历其分叉以找到其存储的所有string。
```go
func (n *node) inOrder(s string) (es []interface{}) {
if n == nil {
return es
}
if n.value != nil {
es = append(es, s)
}
for i, p := 0, 0; i < 62 && p < n.num; i++ {
if n.son[i] != nil {
if i < 26 {
es = append(es, n.son[i].inOrder(s+string(i+'a'))...)
} else if i < 52 {
es = append(es, n.son[i].inOrder(s+string(i-26+'A'))...)
} else {
es = append(es, n.son[i].inOrder(s+string(i-52+'0'))...)
}
p++
}
}
return es
}
```
#### Size
以trie单词查找树做接收者返回该容器当前含有元素的数量如果容器为nil返回0。
```go
func (t *trie) Size() (num int) {
if t == nil {
return 0
}
return t.size
}
```
#### Clear
以trie单词查找树做接收者将该容器中所承载的元素清空将该容器的size置0。
```go
func (t *trie) Clear() {
if t == nil {
return
}
t.mutex.Lock()
t.root = newNode(nil)
t.size = 0
t.mutex.Unlock()
}
```
#### Empty
以trie单词查找树做接收者判断该trie是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true。
```go
func (t *trie) Empty() (b bool) {
if t == nil {
return true
}
return t.size == 0
}
```
##### getIdx
传入一个byte并根据其值返回其映射到分叉的值当不属于'a'~'z','A'~'Z','0'~'9','+','/'时返回-1。
```go
func getIdx(c byte) (idx int) {
if c >= 'a' && c <= 'z' {
idx = int(c - 'a')
} else if c >= 'A' && c <= 'Z' {
idx = int(c-'A') + 26
} else if c >= '0' && c <= '9' {
idx = int(c-'0') + 52
} else if c == '+' {
idx = 62
} else if c == '/' {
idx = 63
} else {
idx = -1
}
return idx
}
```
#### Insert
以trie单词查找树做接收者向trie插入以string类型的s为索引的元素e若存在重复的s则插入失败,不允许覆盖,否则插入成功。
```go
func (t *trie) Insert(s string, e interface{}) (b bool) {
if t == nil {
return
}
if len(s) == 0 {
return false
}
t.mutex.Lock()
if t.root == nil {
//避免根节点为nil
t.root = newNode(nil)
}
//从根节点开始插入
b = t.root.insert(s, 0, e)
if b {
//插入成功,size+1
t.size++
}
t.mutex.Unlock()
return b
}
```
以node单词查找树节点做接收者从n节点中继续插入以s为索引的元素e,且当前抵达的string位置为p当到达s终点时进行插入,如果此时node承载了元素则插入失败,否则成功,当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可。
```go
func (n *node) insert(s string, p int, e interface{}) (b bool) {
if p == len(s) {
if n.value != nil {
return false
}
n.value = e
n.num++
return true
}
idx := getIdx(s[p])
if idx == -1 {
return false
}
if n.son[idx] == nil {
n.son[idx] = newNode(nil)
}
b = n.son[idx].insert(s, p+1, e)
if b {
n.num++
}
return b
}
```
#### Erase
以trie单词查找树做接收者从trie树中删除元素以s为索引的元素e。
```go
func (t *trie) Erase(s string) (b bool) {
if t == nil {
return false
}
if t.Empty() {
return false
}
if len(s) == 0 {
//长度为0无法删除
return false
}
if t.root == nil {
//根节点为nil即无法删除
return false
}
t.mutex.Lock()
//从根节点开始删除
b = t.root.erase(s, 0)
if b {
//删除成功,size-1
t.size--
if t.size == 0 {
//所有string都被删除,根节点置为nil
t.root = nil
}
}
t.mutex.Unlock()
return b
}
```
以node单词查找树节点做接收者从n节点中继续删除以s为索引的元素e,且当前抵达的string位置为p当到达s终点时进行删除,如果此时node未承载元素则删除失败,否则成功,当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,若其分叉为nil则直接失败。
```go
func (n *node) erase(s string, p int) (b bool) {
if p == len(s) {
if n.value != nil {
n.value = nil
n.num--
return true
}
return false
}
idx := getIdx(s[p])
if idx == -1 {
return false
}
if n.son[idx] == nil {
return false
}
b = n.son[idx].erase(s, p+1)
if b {
n.num--
if n.son[idx].num == 0 {
n.son[idx] = nil
}
}
return b
}
```
#### Delete
以trie单词查找树做接收者从trie树中删除以s为前缀的所有元素。
```go
func (t *trie) Delete(s string) (num int) {
if t == nil {
return 0
}
if t.Empty() {
return 0
}
if len(s) == 0 {
//长度为0无法删除
return 0
}
if t.root == nil {
//根节点为nil即无法删除
return 0
}
t.mutex.Lock()
//从根节点开始删除
num = t.root.delete(s, 0)
if num > 0 {
//删除成功
t.size -= num
if t.size <= 0 {
//所有string都被删除,根节点置为nil
t.root = nil
}
}
t.mutex.Unlock()
return num
}
```
以node单词查找树节点做接收者从n节点中继续删除以s为索引的元素e,且当前抵达的string位置为p当到达s终点时进行删除,删除所有后续元素,并返回其后续元素的数量,当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,若其分叉为nil则直接返回0。
```go
func (n *node) delete(s string, p int) (num int) {
if p == len(s) {
return n.num
}
idx := getIdx(s[p])
if idx == -1 {
return 0
}
if n.son[idx] == nil {
return 0
}
num = n.son[idx].delete(s, p+1)
if num>0 {
n.num-=num
if n.son[idx].num <= 0 {
n.son[idx] = nil
}
}
return num
}
```
#### Count
以trie单词查找树做接收者从trie中查找以s为前缀的所有string的个数如果存在以s为前缀的则返回大于0的值即其数量如果未找到则返回0。
```go
func (t *trie) Count(s string) (num int) {
if t == nil {
return 0
}
if t.Empty() {
return 0
}
if t.root == nil {
return 0
}
t.mutex.Lock()
//统计所有以s为前缀的string的数量并返回
num = int(t.root.count(s, 0))
t.mutex.Unlock()
return num
}
```
以node单词查找树节点做接收者从n节点中继续查找以s为前缀索引的元素e,且当前抵达的string位置为p当到达s终点时返回其值即可当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,当其分叉为nil则直接返回0。
```go
func (n *node) count(s string, p int) (num int) {
if p == len(s) {
return n.num
}
idx := getIdx(s[p])
if idx == -1 {
return 0
}
if n.son[idx] == nil {
return 0
}
return n.son[idx].count(s, p+1)
}
```
#### Find
以trie单词查找树做接收者从trie中查找以s为索引的元素e,找到则返回e如果未找到则返回nil。
```go
func (t *trie) Find(s string) (e interface{}) {
if t == nil {
return nil
}
if t.Empty() {
return nil
}
if t.root == nil {
return nil
}
t.mutex.Lock()
//从根节点开始查找以s为索引的元素e
e = t.root.find(s, 0)
t.mutex.Unlock()
return e
}
```
以node单词查找树节点做接收者从n节点中继续查找以s为前缀索引的元素e,且当前抵达的string位置为p当到达s终点时返回其承载的元素即可当未到达终点时,根据当前抵达的位置去寻找其子结点继续遍历即可,当其分叉为nil则直接返回nil。
```go
func (n *node) find(s string, p int) (e interface{}) {
if p == len(s) {
return n.value
}
idx := getIdx(s[p])
if idx == -1 {
return nil
}
if n.son[idx] == nil {
return nil
}
return n.son[idx].find(s, p+1)
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/trie"
)
func main() {
t:=trie.New()
t.Insert("hlccd","hlccd")
t.Insert("ha","ha")
t.Insert("hb","hb")
t.Insert("hc","hc")
t.Insert("hd","hd")
t.Insert("he","he")
t.Insert("hl","hl")
t.Insert("hlccd1","hlccd1")
t.Insert("hlccd2","hlccd2")
t.Insert("hlccd3","hlccd3")
t.Insert("hlccd+","hlccd")
t.Insert("hlccd/","hlccd")
fmt.Println("当前插入的所有string:")
for i:=t.Iterator().Begin();i.HasNext();i.Next(){
fmt.Println(i.Value())
}
t.Erase("h")
t.Erase("ha")
t.Erase("hb")
t.Erase("hc")
t.Erase("hd")
t.Erase("he")
fmt.Println("定向删除后剩余的string:")
for i:=t.Iterator().Begin();i.HasNext();i.Next(){
fmt.Println(i.Value())
}
t.Delete("h")
fmt.Println("删除以'h'为前缀的所有元素后剩余的数量:")
for i:=t.Iterator().Begin();i.HasNext();i.Next(){
fmt.Println(i.Value())
}
}
```
> 当前插入的所有string:
> ha
> hb
> hc
> hd
> he
> hl
> hlccd
> hlccd1
> hlccd2
> hlccd3
> 定向删除后剩余的string:
> hl
> hlccd
> hlccd1
> hlccd2
> hlccd3
> 删除以'h'为前缀的所有元素后剩余的数量:
#### 时间开销
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/trie"
"math/rand"
"time"
)
func main() {
max := 3000000
ss := ""
r := rand.New(rand.NewSource(time.Now().UnixNano()))
ts := time.Now()
s := make([]string, 0, 0)
for i := 0; i < max; i++ {
ss = fmt.Sprintf("%d", r.Intn(4294967295))
s = append(s, ss)
}
fmt.Println("slice消耗时间:", time.Since(ts))
tm := time.Now()
m := make(map[string]bool)
for i := 0; i < max; i++ {
ss = fmt.Sprintf("%d", r.Intn(4294967295))
m[ss] = true
}
fmt.Println("map消耗时间:", time.Since(tm))
tt := time.Now()
t := trie.New()
for i := 0; i < max; i++ {
ss = fmt.Sprintf("%d", r.Intn(4294967295))
t.Insert(ss, true)
}
fmt.Println("trie消耗时间:", time.Since(tt))
tt1 := time.Now()
t.Iterator()
fmt.Println("trie遍历消耗的时间:", time.Since(tt1))
}
```
> slice消耗时间: 586.3899ms
> map消耗时间: 1.192838s
> trie消耗时间: 6.4676663s
> trie遍历消耗的时间: 4.3793102s

View File

@ -0,0 +1,683 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
自平衡二叉查找树Self-Balancing Binary Search Tree简称为平衡二叉树一般以其发明者的名称缩写命名为avl树。
对于一颗平衡二叉树来说,一方面它需要**满足二叉搜索树的性质**,即父结点大于左结点小于右结点,另一方面,**该树中每个结点的左右子结点的高度差不能超过1**即其平衡因子最大为1当插入或删除结点后导致平衡因子超过1时则需要通过旋转的方式对其进行调节。
### 原理
对于平衡二叉搜来说它的搜索同二叉搜索树一样只需要定向遍历树结点即可而在增加和删除的过程中第一步可以先按照二叉搜索树的方式进行增加和删除即直接插入增加、删除前缀or后继结点即可增删完成后则需要对增删结点的父结点进行平衡因子的判断即重新计算其左右子树的高度差当期高度差超过1时则需要进行旋转旋转策略如下
1. 左旋:将该结点向其左结点方向旋转,使得右结点放在原结点的位置,右节点的左子树结点即为该结点。
2. 右旋:将该结点向其右结点方向旋转,使得左结点放在原结点的位置,左节点的右子树结点即为该结点。
3. 右左旋:将原节点的右节点先进行右旋再将原节点进行左旋
4. 左右旋:将原节点的左结点先进行左旋再将原结点进行右旋
#### 情况分析
以插入结点为例,删除节点可视为插入结点的逆运算,即**删除左结点等于插入右结点,删除右结点等于插入左结点**。
##### 单左旋转
按照{456}的顺序插入树内在插入6时会出现4结点的不平衡情况即单侧不平衡对于此类情况只需要将4结点进行左旋即可即将5结点放到4结点的位置4结点设为5节点的左结点。
##### 单右旋转
在刚刚的基础上,插入{32}结点在插入2结点的时候会出现4结点的不平衡并且也是单侧不平衡对于此类情况只需要将4结点进行右旋即可即将3结点放到4结点的位置上4结点设为3结点的右结点。
##### 右左旋转
在此基础上继续插入{87}结点在插入7结点的时候会出现6结点的不平衡同时由于8结点存在左结点故只能先对8结点进行右旋使得7结点处于8结点的位置8结点设为7结点的右结点然后就变成了单左旋转的情况再进行一次对6结点的单左旋转即可。
##### 左右旋转
在此基础上继续插入{01}结点在插入1结点的时候会出现2结点的不平衡考虑到0结点存在右结点故需要先对0结点进行一次左旋将1结点放到0结点的位置0结点设为1结点的左结点然后就变成了单右旋转的情况再对2结点进行一次单右旋转即可。
### 实现
avlTree平衡二叉树结构体该实例存储平衡二叉树的根节点同时保存该二叉树已经存储了多少个元素二叉树中排序使用的比较器在创建时传入若不传入则在插入首个节点时从默认比较器中寻找创建时传入是否允许该二叉树出现重复值,如果不允许则进行覆盖,允许则对节点数目增加即可。
```go
type avlTree struct {
root *node //根节点指针
size int //存储元素数量
cmp comparator.Comparator //比较器
isMulti bool //是否允许重复
mutex sync.Mutex //并发控制锁
}
```
node树节点结构体该节点是平衡二叉树的树节点若该平衡二叉树允许重复则对节点num+1即可,否则对value进行覆盖平衡二叉树节点当左右子节点深度差超过1时进行左右旋转以实现平衡。
```go
type node struct {
value interface{} //节点中存储的元素
num int //该元素数量
depth int //该节点的深度
left *node //左节点指针
right *node //右节点指针
}
```
#### 接口
```go
type avlTreer interface {
Iterator() (i *Iterator.Iterator) //返回包含该二叉树的所有元素,重复则返回多个
Size() (num int) //返回该二叉树中保存的元素个数
Clear() //清空该二叉树
Empty() (b bool) //判断该二叉树是否为空
Insert(e interface{}) //向二叉树中插入元素e
Erase(e interface{}) //从二叉树中删除元素e
Count(e interface{}) (num int) //从二叉树中寻找元素e并返回其个数
}
```
#### New
新建一个avlTree平衡二叉树容器并返回初始根节点为nil传入该二叉树是否为可重复属性,如果为true则保存重复值,否则对原有相等元素进行覆盖,若有传入的比较器,则将传入的第一个比较器设为该二叉树的比较器。
```go
func New(isMulti bool, cmps ...comparator.Comparator) (avl *avlTree) {
//判断是否有传入比较器,若有则设为该二叉树默认比较器
var cmp comparator.Comparator
if len(cmps) == 0 {
cmp = nil
} else {
cmp = cmps[0]
}
return &avlTree{
root: nil,
size: 0,
cmp: cmp,
isMulti: isMulti,
}
}
```
新建一个平衡二叉树节点并返回将传入的元素e作为该节点的承载元素该节点的num和depth默认为1,左右子节点设为nil。
```go
func newNode(e interface{}) (n *node) {
return &node{
value: e,
num: 1,
depth: 1,
left: nil,
right: nil,
}
}
```
#### Iterator
以avlTree平衡二叉树做接收者将该二叉树中所有保存的元素将从根节点开始以中缀序列的形式放入迭代器中若允许重复存储则对于重复元素进行多次放入。
```go
func (avl *avlTree) Iterator() (i *Iterator.Iterator) {
if avl == nil {
return nil
}
avl.mutex.Lock()
es:=avl.root.inOrder()
i = Iterator.New(&es)
avl.mutex.Unlock()
return i
}
```
以node平衡二叉树节点做接收者以中缀序列返回节点集合若允许重复存储则对于重复元素进行多次放入。
```go
func (n *node) inOrder() (es []interface{}) {
if n == nil {
return es
}
if n.left != nil {
es = append(es, n.left.inOrder()...)
}
for i := 0; i < n.num; i++ {
es = append(es, n.value)
}
if n.right != nil {
es = append(es, n.right.inOrder()...)
}
return es
}
```
#### Size
以avlTree平衡二叉树做接收者返回该容器当前含有元素的数量如果容器为nil返回0。
```go
func (bs *bsTree) Size() (num uint64) {
if bs == nil {
//创建一个允许插入重复值的二叉搜
bs = New(true)
}
return bs.size
}
```
#### Clear
以avlTree平衡二叉树做接收者将该容器中所承载的元素清空将该容器的size置0。
```go
func (avl *avlTree) Clear() {
if avl == nil {
return
}
avl.mutex.Lock()
avl.root = nil
avl.size = 0
avl.mutex.Unlock()
}
```
#### Empty
以avlTree平衡二叉树做接收者判断该二叉搜索树是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true。
```go
func (avl *avlTree) Empty() (b bool) {
if avl == nil {
return true
}
if avl.size > 0 {
return false
}
return true
}
```
#### 旋转
##### getDepth
以node平衡二叉树节点做接收者返回该节点的深度,节点不存在返回0。
```go
func (n *node) getDepth() (depth int) {
if n == nil {
return 0
}
return n.depth
}
```
##### max
返回a和b中较大的值
```go
func max(a, b int) (m int) {
if a > b {
return a
} else {
return b
}
}
```
##### leftRotate
以node平衡二叉树节点做接收者将该节点向左节点方向转动,使右节点作为原来节点,并返回右节点,同时将右节点的左节点设为原节点的右节点。
```go
func (n *node) leftRotate() (m *node) {
//左旋转
headNode := n.right
n.right = headNode.left
headNode.left = n
//更新结点高度
n.depth = max(n.left.getDepth(), n.right.getDepth()) + 1
headNode.depth = max(headNode.left.getDepth(), headNode.right.getDepth()) + 1
return headNode
}
```
##### rightRotate
以node平衡二叉树节点做接收者将该节点向右节点方向转动,使左节点作为原来节点,并返回左节点,同时将左节点的右节点设为原节点的左节点。
```go
func (n *node) rightRotate() (m *node) {
//右旋转
headNode := n.left
n.left = headNode.right
headNode.right = n
//更新结点高度
n.depth = max(n.left.getDepth(), n.right.getDepth()) + 1
headNode.depth = max(headNode.left.getDepth(), headNode.right.getDepth()) + 1
return headNode
}
```
##### rightLeftRotate
以node平衡二叉树节点做接收者将原节点的右节点先进行右旋再将原节点进行左旋。
```go
func (n *node) rightLeftRotate() (m *node) {
//右旋转,之后左旋转
//以失衡点右结点先右旋转
sonHeadNode := n.right.rightRotate()
n.right = sonHeadNode
//再以失衡点左旋转
return n.leftRotate()
}
```
##### leftRightRotate
以node平衡二叉树节点做接收者将原节点的左节点先进行左旋再将原节点进行右旋。
```go
func (n *node) leftRightRotate() (m *node) {
//左旋转,之后右旋转
//以失衡点左结点先左旋转
sonHeadNode := n.left.leftRotate()
n.left = sonHeadNode
//再以失衡点左旋转
return n.rightRotate()
}
```
##### getMin
以node平衡二叉树节点做接收者返回n节点的最小元素的承载的元素和数量即该节点父节点的后缀节点的元素和数量。
```go
func (n *node) getMin() (e interface{}, num int) {
if n == nil {
return nil, 0
}
if n.left == nil {
return n.value, n.num
} else {
return n.left.getMin()
}
}
```
##### adjust
以node平衡二叉树节点做接收者对n节点进行旋转以保持节点左右子树平衡。
```go
func (n *node) adjust() (m *node) {
if n.right.getDepth()-n.left.getDepth() >= 2 {
//右子树高于左子树且高度差超过2,此时应当对n进行左旋
if n.right.right.getDepth() > n.right.left.getDepth() {
//由于右右子树高度超过右左子树,故可以直接左旋
n = n.leftRotate()
} else {
//由于右右子树不高度超过右左子树
//所以应该先右旋右子树使得右子树高度不超过左子树
//随后n节点左旋
n = n.rightLeftRotate()
}
} else if n.left.getDepth()-n.right.getDepth() >= 2 {
//左子树高于右子树且高度差超过2,此时应当对n进行右旋
if n.left.left.getDepth() > n.left.right.getDepth() {
//由于左左子树高度超过左右子树,故可以直接右旋
n = n.rightRotate()
} else {
//由于左左子树高度不超过左右子树
//所以应该先左旋左子树使得左子树高度不超过右子树
//随后n节点右旋
n = n.leftRightRotate()
}
}
return n
}
```
#### Insert
以avlTree平衡二叉树做接收者向二叉树插入元素e,若不允许重复则对相等元素进行覆盖如果二叉树为空则之间用根节点承载元素e,否则以根节点开始进行查找当节点左右子树高度差超过1时将进行旋转以保持平衡。
```go
func (avl *avlTree) Insert(e interface{}) {
if avl == nil {
return
}
avl.mutex.Lock()
if avl.Empty() {
if avl.cmp == nil {
avl.cmp = comparator.GetCmp(e)
}
if avl.cmp == nil {
return
}
//二叉树为空,用根节点承载元素e
avl.root = newNode(e)
avl.size = 1
avl.mutex.Unlock()
return
}
//从根节点进行插入,并返回节点,同时返回是否插入成功
var b bool
avl.root, b = avl.root.insert(e, avl.isMulti, avl.cmp)
if b {
//插入成功,数量+1
avl.size++
}
avl.mutex.Unlock()
}
```
以node平衡二叉树节点做接收者从n节点中插入元素e如果n节点中承载元素与e不同则根据大小从左右子树插入该元素如果n节点与该元素相等,且允许重复值,则将num+1否则对value进行覆盖插入成功返回true,插入失败或不允许重复插入返回false。
```go
func (n *node) insert(e interface{}, isMulti bool, cmp comparator.Comparator) (m *node, b bool) {
//节点不存在,应该创建并插入二叉树中
if n == nil {
return newNode(e), true
}
if cmp(e, n.value) < 0 {
//从左子树继续插入
n.left, b = n.left.insert(e, isMulti, cmp)
if b {
//插入成功,对该节点进行平衡
n = n.adjust()
}
n.depth = max(n.left.getDepth(), n.right.getDepth()) + 1
return n, b
}
if cmp(e, n.value) > 0 {
//从右子树继续插入
n.right, b = n.right.insert(e, isMulti, cmp)
if b {
//插入成功,对该节点进行平衡
n = n.adjust()
}
n.depth = max(n.left.getDepth(), n.right.getDepth()) + 1
return n, b
}
//该节点元素与待插入元素相同
if isMulti {
//允许重复,数目+1
n.num++
return n, true
}
//不允许重复,对值进行覆盖
n.value = e
return n, false
}
```
#### Erase
以avlTree平衡二叉树做接收者从平衡二叉树中删除元素e若允许重复记录则对承载元素e的节点中数量记录减一即可若不允许重复记录则删除该节点同时将前缀节点或后继节点更换过来以保证二叉树的不发送断裂如果该二叉树仅持有一个元素且根节点等价于待删除元素,则将二叉树根节点置为nil。
```go
func (avl *avlTree) Erase(e interface{}) {
if avl == nil {
return
}
if avl.Empty() {
return
}
avl.mutex.Lock()
if avl.size == 1 && avl.cmp(avl.root.value, e) == 0 {
//二叉树仅持有一个元素且根节点等价于待删除元素,将二叉树根节点置为nil
avl.root = nil
avl.size = 0
avl.mutex.Unlock()
return
}
//从根节点进行插入,并返回节点,同时返回是否删除成功
var b bool
avl.root, b = avl.root.erase(e, avl.cmp)
if b {
avl.size--
}
avl.mutex.Unlock()
}
```
以node平衡二叉树节点做接收者从n节点中删除元素e如果n节点中承载元素与e不同则根据大小从左右子树删除该元素如果n节点与该元素相等,且允许重复值,则将num-1否则直接删除该元素删除时先寻找该元素的前缀节点,若不存在则寻找其后继节点进行替换,替换后删除该节点。
```go
func (n *node) erase(e interface{}, cmp comparator.Comparator) (m *node, b bool) {
if n == nil {
//待删除值不存在,删除失败
return n, false
}
if cmp(e, n.value) < 0 {
//从左子树继续删除
n.left, b = n.left.erase(e, cmp)
} else if cmp(e, n.value) > 0 {
//从右子树继续删除
n.right, b = n.right.erase(e, cmp)
} else if cmp(e, n.value) == 0 {
//存在相同值,从该节点删除
b = true
if n.num > 1 {
//有重复值,节点无需删除,直接-1即可
n.num--
} else {
//该节点需要被删除
if n.left != nil && n.right != nil {
//找到该节点后继节点进行交换删除
n.value, n.num = n.right.getMin()
//从右节点继续删除,同时可以保证删除的节点必然无左节点
n.right, b = n.right.erase(n.value, cmp)
} else if n.left != nil {
n = n.left
} else {
n = n.right
}
}
}
//当n节点仍然存在时,对其进行调整
if n != nil {
n.depth = max(n.left.getDepth(), n.right.getDepth()) + 1
n = n.adjust()
}
return n, b
}
```
#### Count
以avlTree平衡二叉树做接收者从搜素二叉树中查找元素e的个数如果找到则返回该二叉树中和元素e相同元素的个数如果不允许重复则最多返回1如果未找到则返回0。
```go
func (avl *avlTree) Count(e interface{}) (num int) {
if avl == nil {
//二叉树为空,返回0
return 0
}
if avl.Empty() {
return 0
}
avl.mutex.Lock()
num = avl.root.count(e, avl.isMulti, avl.cmp)
avl.mutex.Unlock()
return num
}
```
以node二叉搜索树节点做接收者从n节点中查找元素e并返回存储的个数如果n节点中承载元素与e不同则根据大小从左右子树查找该元素如果n节点与该元素相等,则直接返回其个数。
```go
func (n *node) count(e interface{}, isMulti bool, cmp comparator.Comparator) (num int) {
if n == nil {
return 0
}
//n中承载元素小于e,从右子树继续查找并返回结果
if cmp(n.value, e) < 0 {
return n.right.count(e, isMulti, cmp)
}
//n中承载元素大于e,从左子树继续查找并返回结果
if cmp(n.value, e) > 0 {
return n.left.count(e, isMulti, cmp)
}
//n中承载元素等于e,直接返回结果
return n.num
}
```
#### Find
以avlTree平衡二叉树做接收者从搜素二叉树中查找以元素e为索引信息的全部信息如果找到则返回该二叉树中和索引元素e相同的元素的全部信息如果未找到则返回nil。
```go
func (avl *avlTree) Find(e interface{}) (ans interface{}) {
if avl == nil {
//二叉树为空,返回0
return 0
}
if avl.Empty() {
return 0
}
avl.mutex.Lock()
ans = avl.root.find(e, avl.isMulti, avl.cmp)
avl.mutex.Unlock()
return ans
}
```
以node二叉搜索树节点做接收者从n节点中查找元素e并返回其存储的全部信息如果n节点中承载元素与e不同则根据大小从左右子树查找该元素如果n节点与该元素相等,则直接返回其信息即可若未找到该索引信息则返回nil。
```go
func (n *node) find(e interface{}, isMulti bool, cmp comparator.Comparator) (ans interface{}) {
if n == nil {
return nil
}
//n中承载元素小于e,从右子树继续查找并返回结果
if cmp(n.value, e) < 0 {
return n.right.count(e, isMulti, cmp)
}
//n中承载元素大于e,从左子树继续查找并返回结果
if cmp(n.value, e) > 0 {
return n.left.count(e, isMulti, cmp)
}
//n中承载元素等于e,直接返回结果
return n.value
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/avlTree"
"sync"
)
func main() {
bs := avlTree.New(true)
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
bs.Insert(i)
go func() {
bs.Insert(i)
wg.Done()
}()
}
wg.Wait()
fmt.Println("遍历输出所有插入的元素")
for i := bs.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
fmt.Println("删除一次平衡二叉树中存在的元素,存在重复的将会被剩下")
for i := 0; i < 10; i++ {
bs.Erase(i)
}
fmt.Println("输出剩余的重复元素")
for i := bs.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 遍历输出所有插入的元素
> 0
> 1
> 2
> 3
> 4
> 5
> 6
> 7
> 7
> 8
> 8
> 8
> 8
> 9
> 9
> 9
> 9
> 9
> 10
> 10
> 删除一次平衡二叉树中存在的元素,存在重复的将会被剩下
> 输出剩余的重复元素
> 7
> 8
> 8
> 8
> 9
> 9
> 9
> 9
> 10
> 10
#### 时间开销
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/avlTree"
"time"
)
func main() {
max:=10000000
tv := time.Now()
v := make([]interface{},max,max)
num:=0
for i := 0; i < max; i++ {
if i%2==1{
num=max-i
}else{
num=i
}
v[num]=num
}
fmt.Println("插入切片的消耗时间:",time.Since(tv))
tt := time.Now()
t := avlTree.New(false)
for i := 0; i < max; i++ {
if i%2==1{
num=max-i
}else{
num=i
}
t.Insert(num)
}
fmt.Println("插入平衡二叉树的消耗时间:",time.Since(tt))
}
```
> 插入切片的消耗时间: 195.5018ms
> 插入平衡二叉树的消耗时间: 5.6738274s

View File

@ -0,0 +1,262 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
LRU-Least Recently Used最近最少使用链结构相对于仅考虑时间因素的FIFO和仅考虑访问频率的LFULRU算法可以认为是相对平衡的一种淘汰算法LRU认为如果数据最近被访问过那么将来被访问的概率也会更高LRU 算法的实现非常简单,维护一个队列,如果某条记录被访问了,则移动到队尾,那么队首则是最近最少访问的数据,淘汰该条记录即可。
它适用于作为一个cache缓存去保存带有索引的数据对于其要存储的数据值来说通过实现一个Len()的函数的泛型来表示。
### 原理
本次实现过程中采用双向链表的形式做为用于存储值的队列同时利用map建立key和存储值的链表结点之间的联系。
该方案可以加快从队列中找到对应的值并做修改同时也便于将最近访问的value移到队首。
本次实现不做定时淘汰若要实现定时淘汰可自行创建一个协程同时添加一个map去保存其上次访问时间去完成定时淘汰即可。
对于LRU的修改策略来说
- 初次添加/访问,直接放到队首
- 添加后若容量超过上限值则移除最远访问的value即队尾元素移除过程直至使用容量低于上限值时才结束
- 再次访问时从map中找到刚刚访问的结点随后将其移动到队首
- 删除时直接利用map找到对应的结点进行删除即可
### 实现
LRU链结构体包含了该LRU结构中能承载的byte数上限和当前已承载的数量以链表的形式存储承载元素使用map建立key索引和链表结点之间的联系,链表结点中存放其valueonRemove函数是用于在删除时的执行,可用于将数据持久化,使用并发控制所保证线程安全。
```go
type LRU struct {
maxBytes int64 //所能承载的最大byte数量
nowBytes int64 //当前承载的byte数
ll *list.List //用于存储的链表
cache map[string]*list.Element //链表元素与key的映射表
onRemove func(key string, value Value) //删除元素时的执行函数
mutex sync.Mutex //并发控制锁
}
```
索引结构体保存了一个待存储值的索引和值value值为一个interface{},需要实现它的长度函数即Len()。
```go
type indexes struct {
key string //索引
value Value //存储值
}
```
存储值的函数。
```go
type Value interface {
Len() int //用于计算该存储值所占用的长度
}
```
#### 接口
```go
type lruer interface {
Size() (num int64) //返回lru中当前存放的byte数
Cap() (num int64) //返回lru能存放的byte树的最大值
Clear() //清空lru,将其中存储的所有元素都释放
Empty() (b bool) //判断该lru中是否存储了元素
Insert(key string, value Value) //向lru中插入以key为索引的value
Erase(key string) //从lru中删除以key为索引的值
Get(key string) (value Value, ok bool) //从lru中获取以key为索引的value和是否获取成功?
}
```
#### New
以LRU链容器做接收者返回该LRU链当前所存储的byte数。
```go
func (l *LRU) Size() (num int64) {
if l == nil {
return 0
}
return l.nowBytes
}
```
#### Cap
以LRU链容器做接收者返回该LRU链能存储的最大byte数。
```go
func (l *LRU) Cap() (num int64) {
if l == nil {
return 0
}
return l.maxBytes
}
```
#### Clear
以LRU链容器做接收者将LRU链中的所有承载的元素清除同时将map清空,将当前存储的byte数清零。
```go
func (l *LRU) Clear() {
if l == nil {
return
}
l.mutex.Lock()
l.ll = list.New()
l.cache = make(map[string]*list.Element)
l.nowBytes = 0
l.mutex.Unlock()
}
```
#### Empty
以LRU链容器做接收者判断该LRU链中是否存储了元素没存储则返回true,LRU不存在也返回true否则返回false。
```go
func (l *LRU) Empty() (b bool) {
if l == nil {
return true
}
return l.nowBytes <= 0
}
```
#### Insert
以LRU链容器做接收者向该LRU中插入以key为索引的value若已经存在则将其放到队尾若空间充足且不存在则直接插入队尾若空间不足则淘汰队首元素再将其插入队尾插入完成后将当前已存的byte数增加。
```go
func (l *LRU) Insert(key string, value Value) {
l.mutex.Lock()
//利用map从已存的元素中寻找
if ele, ok := l.cache[key]; ok {
//该key已存在,直接替换即可
l.ll.MoveToFront(ele)
kv := ele.Value.(*indexes)
//此处是一个替换,即将cache中的value替换为新的value,同时根据实际存储量修改其当前存储的实际大小
l.nowBytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
//该key不存在,需要进行插入
ele := l.ll.PushFront(&indexes{key, value})
l.cache[key] = ele
//此处是一个增加操作,即原本不存在,所以直接插入即可,同时在当前数值范围内增加对应的占用空间
l.nowBytes += int64(len(key)) + int64(value.Len())
}
//添加完成后根据删除掉尾部一部分数据以保证实际使用空间小于设定的空间上限
for l.maxBytes != 0 && l.maxBytes < l.nowBytes {
//删除队尾元素
//删除后执行创建时传入的删除执行函数
ele := l.ll.Back()
if ele != nil {
l.ll.Remove(ele)
kv := ele.Value.(*indexes)
//删除最末尾的同时将其占用空间减去
delete(l.cache, kv.key)
l.nowBytes -= int64(len(kv.key)) + int64(kv.value.Len())
if l.onRemove != nil {
//删除后的回调函数,可用于持久化该部分数据
l.onRemove(kv.key, kv.value)
}
}
}
l.mutex.Unlock()
}
```
#### Erase
以LRU链容器做接收者从LRU链中删除以key为索引的value如果不存在则直接结束删除完成后将其占用的byte数减去删除后执行函数回调函数,一般用于持久化。
```go
func (l *LRU) Erase(key string) {
l.mutex.Lock()
if ele, ok := l.cache[key]; ok {
l.ll.Remove(ele)
kv := ele.Value.(*indexes)
//删除的同时将其占用空间减去
delete(l.cache, kv.key)
l.nowBytes -= int64(len(kv.key)) + int64(kv.value.Len())
if l.onRemove != nil {
//删除后的回调函数,可用于持久化该部分数据
l.onRemove(kv.key, kv.value)
}
}
l.mutex.Unlock()
}
```
#### Get
以LRU链容器做接收者从LRU链中寻找以key为索引的value找到对应的value后将其移到首部,随后返回其value如果没找到以key为索引的value,直接结束即可。
```go
func (l *LRU) Get(key string) (value Value, ok bool) {
l.mutex.Lock()
if ele, ok := l.cache[key]; ok {
//找到了value,将其移到链表首部
l.ll.MoveToFront(ele)
kv := ele.Value.(*indexes)
l.mutex.Unlock()
return kv.value, true
}
l.mutex.Unlock()
return nil, false
}
```
### 使用示例
```go
package main
import (
"fmt"
lru2 "github.com/hlccd/goSTL/data_structure/lru"
)
type Value struct {
Bytes []byte
}
func (v Value) Len() int {
return len(v.Bytes)
}
func main() {
space :=0
lru := lru2.New(2<<10, nil)
for i := 0; i < 10000; i++ {
v := Value{Bytes: []byte(string(i))}
lru.Insert(string(i), v)
space +=v.Len()
}
fmt.Println("应该占用空间:",space)
fmt.Println("LRU中存放的byte数量:",lru.Size())
fmt.Println("LRU的byte数量上限:",lru.Cap())
lru.Erase(string(9999))
fmt.Println("删除后的LRU中存放的byte数量:",lru.Size())
fmt.Println("从LRU中找9998")
if v,ok:=lru.Get(string(9998));ok{
fmt.Println(string(v.(Value).Bytes))
fmt.Println(string(9998))
}
fmt.Println("从LRU中找1")
if v,ok:=lru.Get(string(1));ok{
fmt.Println(string(v.(Value).Bytes))
fmt.Println(string(1))
}
}
```
> 应该占用空间: 27824
> LRU中存放的byte数量: 2046
> LRU的byte数量上限: 2048
> 删除后的LRU中存放的byte数量: 2040
> 从LRU中找9998
> ✎
> ✎
> 从LRU中找1
>
>

View File

@ -0,0 +1,298 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
stack是一个**线性容器**,但不同于其他容器的特点在于,栈是仅仅支持从顶部插入和从顶部删除的操作,即**单向删除和添加**。
对于stack的实现考虑到它是一个线性容器并且其中的元素仅可通过顶部添加和删除即单项增删所以可以考虑其**底层使用动态数组**的形式实现考虑到动态数组需要自行对分配空间进行操作同时它也类似于vector进行单项操作的情况所以它的扩容缩容策略同vector一样但不同点在于它只能使用单侧增删所以不需要考虑从中间插入的一些情况。
### 原理
对于一个栈来说,可以将其看作是一个**木桶**,你要在这个容器里放入一些元素,就只能从已有元素的顶部放入,而不是插入的原有的元素之间去,当你要删除元素的时候,也只能从顶部一个一个删除。
它是可以动态的对元素进行添加和修改的,而如果说每一次增删元素都使得数组恰好可以容纳所有元素的话,那它在以后的每一次增删的过程中都需要去**重新分配空间**,并将原有的元素复制到新数组中,如果按照这样来操作的话,每次删减都会需要花费大量的时间去进行,将会极大的降低其性能。
为了提高效率,可以选择牺牲一定的空间作为冗余量,即**时空权衡**,通过每次扩容时多分配一定的空间,这样在后续添加时就可以减少扩容次数,而在删除元素时,如果冗余量不多时,可以选择不进行缩容,仅将顶部指针下移一位即可,,这样可以极大减少的由于容量不足或超出导致的扩容缩容造成的时间开销。
#### 扩容策略
对于动态数组扩容来说由于其添加是线性且连续的虽然stack一次只会添加一个但为了减少频繁分配空间造成的时间浪费选择多保留一些冗余空间以方便后续增删操作。
stack的扩容主要由两种方案
**同vector的扩容策略**
1. 固定扩容:固定的去增加一定量的空间,该方案时候在数组较大时添加空间,可以避免直接翻倍导致的**冗余过量**问题,到由于增加量是固定的,如果需要一次扩容很多量的话就会比较缓慢。
2. 翻倍扩容:将原有容量直接翻倍,该方案适合在数组不太大的适合添加空间,可以提高扩容量,增加扩容效率,但数组太大时使用的话会导致一次性增加太多空间,进而造成空间的浪费。
#### 缩容策略
对于动态数组的缩容来说同扩容类似由于元素的减少只会是线性且连续的即每一次只会减少一个不会存在由于一个元素被删去导致其他已使用的空间也需要被释放。所以stack的缩容方案和stack的扩容方案十分类似即是它的逆过程
**同vector的缩容策略**
1. 固定缩容:释放一个固定的空间,该方案适合在当前数组较大的时候进行,可以减缓需要缩小的量,当空间较大的时候如果采取折半缩减的话;将可能在绝大多数时间内都不会被采用,从而造成空间浪费。
2. 折半缩容:将当前容量进行折半,该方案适合数组不太大的适合进行缩容,可以更快的缩小容量,但对于较大的空间来说并不会频繁使用到。
### 实现
stack栈结构体包含动态数组和该数组的顶部指针顶部指针指向实际顶部元素的下一位置当删除节点时仅仅需要下移顶部指针一位即可当新增结点时优先利用冗余空间当冗余空间不足时先倍增空间至2^16超过后每次增加2^16的空间删除结点后如果冗余超过2^16,则释放掉,删除后若冗余量超过使用量,也释放掉冗余空间。
```go
type stack struct {
data []interface{} //用于存储元素的动态数组
top uint64 //顶部指针
cap uint64 //动态数组的实际空间
mutex sync.Mutex //并发控制锁
}
```
#### 接口
```go
type stacker interface {
Iterator() (i *Iterator.Iterator) //返回一个包含栈中所有元素的迭代器
Size() (num uint64) //返回该栈中元素的使用空间大小
Clear() //清空该栈容器
Empty() (b bool) //判断该栈容器是否为空
Push(e interface{}) //将元素e添加到栈顶
Pop() //弹出栈顶元素
Top() (e interface{}) //返回栈顶元素
}
```
#### New
新建一个stack栈容器并返回初始stack的动态数组容量为1初始stack的顶部指针置0容量置1。
```go
func New() (s *stack) {
return &stack{
data: make([]interface{}, 1, 1),
top: 0,
cap: 1,
mutex: sync.Mutex{},
}
}
```
#### Iterator
以stack栈容器做接收者将stack栈容器中不使用空间释放掉返回一个包含容器中所有使用元素的迭代器。
```go
func (s *stack) Iterator() (i *Iterator.Iterator) {
if s == nil {
s = New()
}
s.mutex.Lock()
if s.data == nil {
//data不存在,新建一个
s.data = make([]interface{}, 1, 1)
s.top = 0
s.cap = 1
} else if s.top < s.cap {
//释放未使用的空间
tmp := make([]interface{}, s.top, s.top)
copy(tmp, s.data)
s.data = tmp
}
//创建迭代器
i = Iterator.New(&s.data)
s.mutex.Unlock()
return i
}
```
#### Size
以stack栈容器做接收者吗返回该容器当前含有元素的数量。
```go
func (s *stack) Size() (num uint64) {
if s == nil {
s = New()
}
return s.top
}
```
#### Clear
以stack栈容器做接收者将该容器中所承载的元素清空将该容器的尾指针置0。
```go
func (s *stack) Clear() {
if s == nil {
s = New()
}
s.mutex.Lock()
s.data = make([]interface{}, 0, 0)
s.top = 0
s.cap = 1
s.mutex.Unlock()
}
```
#### Empty
以stack栈容器做接收者判断该stack栈容器是否含有元素如果含有元素则不为空,返回false 如果不含有元素则说明为空,返回true如果容器不存在,返回true该判断过程通过顶部指针数值进行判断当顶部指针数值为0时说明不含有元素当顶部指针数值大于0时说明含有元素。
```go
func (s *stack) Empty() (b bool) {
if s == nil {
return true
}
return s.Size() == 0
}
```
#### Push
以stack栈容器做接收者在容器顶部插入元素若存储冗余空间则在顶部指针位插入元素随后上移顶部指针否则进行扩容扩容后获得冗余空间重复上一步即可。
固定扩容值设为2^16翻倍扩容上限也为2^16。
```go
func (s *stack) Push(e interface{}) {
if s == nil {
s = New()
}
s.mutex.Lock()
if s.top < s.cap {
//还有冗余,直接添加
s.data[s.top] = e
} else {
//冗余不足,需要扩容
if s.cap <= 65536 {
//容量翻倍
if s.cap == 0 {
s.cap = 1
}
s.cap *= 2
} else {
//容量增加2^16
s.cap += 65536
}
//复制扩容前的元素
tmp := make([]interface{}, s.cap, s.cap)
copy(tmp, s.data)
s.data = tmp
s.data[s.top] = e
}
s.top++
s.mutex.Unlock()
}
```
#### Pop
以stack栈容器做接收者弹出容器顶部元素,同时顶部指针下移一位,当顶部指针小于容器切片实际使用空间的一半时,重新分配空间释放未使用部分,若容器为空,则不进行弹出。
```go
func (s *stack) Pop() {
if s == nil {
s = New()
return
}
if s.Empty() {
return
}
s.mutex.Lock()
s.top--
if s.cap-s.top >= 65536 {
//容量和实际使用差值超过2^16时,容量直接减去2^16
s.cap -= 65536
tmp := make([]interface{}, s.cap, s.cap)
copy(tmp, s.data)
s.data = tmp
} else if s.top*2 < s.cap {
//实际使用长度是容量的一半时,进行折半缩容
s.cap /= 2
tmp := make([]interface{}, s.cap, s.cap)
copy(tmp, s.data)
s.data = tmp
}
s.mutex.Unlock()
}
```
#### Top
以stack栈容器做接收者返回该容器的顶部元素若该容器当前为空,则返回nil。
```go
func (s *stack) Top() (e interface{}) {
if s == nil {
return nil
}
if s.Empty() {
return nil
}
s.mutex.Lock()
e = s.data[s.top-1]
s.mutex.Unlock()
return e
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/stack"
"sync"
)
func main() {
s := stack.New()
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(num int) {
s.Push(num)
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("使用迭代器遍历全部:")
for i := s.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
fmt.Println("使用size变删除边遍历")
size:=s.Size()
for i := uint64(0); i < size; i++ {
fmt.Println(s.Top())
s.Pop()
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 使用迭代器遍历全部:
> 9
> 3
> 4
> 5
> 6
> 7
> 8
> 0
> 1
> 2
> 使用size变删除边遍历
> 2
> 1
> 0
> 8
> 7
> 6
> 5
> 4
> 3
> 9

View File

@ -0,0 +1,648 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
树堆Treap是一个比较特殊的结构它同之前实现的二叉搜索树和完全二叉搜有着类似的性质它**即满足二叉搜索树的查找性质,又满足完全二叉树的极值处于堆顶的性质**。
树堆由一个根节点和根节点下属的多层次的子结点构成,任意一个结点最多只能拥有两个子结点,即左右子结点。它的这一特性**同二叉搜索树完全一致**,但除此之外,它还有另一个特质:给每一个结点赋予一个随机的优先级,使得其在插入的时候,通过**左右旋转**的方式,保证其满足二叉搜索树的性质,同时使得优先级更大的结点旋转至更靠近顶部的方式,即**满足完全二叉树的性质**。
除开以上两点之外,它还有一个比较特殊的优势,考虑到所有结点的优先级都是随机生成的,即其优先级和其结点值之前完全没有关系,故只要随机算法合理的情况下,整个树堆的形状就会更加扁平,从概率上来说,当插入结点数量够多的情况下,**增加和删除结点的时间复杂的仅为Ologn**,即实现了**依概率平衡**。
### 原理
对于一个树堆来说,它需要满足的条件主要有两条:
1. 父结点的值必然大于左结点小于右结点
2. 父结点的优先级必然大于左右两结点
而为了实现这两个情况,我们可以考虑先实现分别实现第一点和第二点。对于第一点来说其实就是指的**二叉搜索树**的性质,第二点指的是完全二叉树的,考虑到之前的文章已经提及,便不在此处赘述。
于是,现在的核心问题就变成了,如何在满足二叉搜索树的性质的情况下,使得它同时也满足完全二叉树的性质。
我们考虑这样一种情况,对于一个二叉搜索树来说,父结点的左结点的值必然是小于父结点同时也小于右结点的,对应的,父结点的右结点的值必然也是大于父结点同时也大于左结点的,所以,对于前一个情况来说,将父结点设为右结点的左结点,同时将父结点的右结点设为右节点的左节点,其大小情况是保持不变的;对应的,将父结点设为左结点的右结点,同时将父结点的左结点设为左节点的右节点,其大小情况仍然是满足二叉搜索树的条件的。
以上两种情况分别是**右旋和左旋**,到此时可以知道,可以通过左旋或右旋的方式,保持该结构仍然满足二叉搜索树的特征的同时,转换结点的位置。
同时我们可以知道,对于一个完全二叉树来说,即对于一个堆来说,它的核心特征是父结点的优先级必然大于左右两个结点,所以只需要通过旋转的方式满足该特征即可,即当父结点的左结点的优先级大于父结点的时候,进行左旋,当父结点的右节点的优先级大于父结点的时候进行右旋,当左右结点的优先级都大于父结点时,选择更大的一侧旋转即可。
到此即可根据左右旋转的方式同时满足二叉搜索树和完全二叉树的性质了只不过不同点在于满足二叉搜索树的值是结点承载的值而满足完全二叉树的优先级并非承载值而是随机生成的值同时由于其是随机生成的从概率上来说当数量足够大的情况下可以实现O(logn)级别的增删查。
#### 添加策略
对于树堆的添加元素的情况来说需要的情况主要可以分为该元素是否已经存在于树堆中如果已经存在则只需要将结点的num+1即可否则则需要新增结点同时赋予一个随机的优先级再根据情况左右旋转以满足性质。
步骤:
1. 先以根节点为父结点,递归找到等于该元素的结点位置,若插入元素小于当前父结点元素,则转入左子树,否则转入右子树,以此方式找到对应的插入位置
2. 找到后如果该元素已经存在则结点num+1即可否则新建结点同时建立父结点和新结点之间的关系此时整个结构是满足于二叉搜索树的性质的
3. 随后,根据新增结点的优先级和其父结点的优先级进行判断,如果大于父结点优先级则进行对应方向的旋转即可。
#### 删除策略
对于树堆的元素删除来说,首先要做的还是先找到要删除的结点,如果没找到则可以之间结束。找到后需要判断该节点是否存在子结点,如果不存在则可以直接删除,否则,如果右左子树则找左子树的前缀节点即左子树的最右结点,如果只有右子树则朝气后缀结点即右子树的最右结点,将寻找到的结点替换为当前父结点,然后删除被替换的结点即可。但对于左右两个结点都存在的情况来说,则找到优先级更小的一侧进行旋转,然后将结点转入优先级更小的一侧结点进行持续递归,直到找到一个只有左节点或只有右节点或无结点的情况,对于存在结点的情况来说,把其子结点替换为自己即可,对于无结点的情况来说,删除即可。
树堆删除结点的步骤:
1. 通过递归比对遍历寻找到要删除的结点
2. 如果该结点不存在则直接结束如果有重复则num-1即可结束
3. 如果同时有左右子树,转换为优先级更小的一侧,然后进行递归直到不同时存在左右子树为止
4. 如果有左子树,将左子树替换为自己即可
5. 如果只有右子树,将右子树替换为自己即可
6. 如果无子树,直接删除即可
### 实现
treap树堆结构体该实例存储树堆的根节点同时保存该树堆中已经存储了多少个元素二叉树中排序使用的比较器在创建时传入,若不传入则在插入首个节点时从默认比较器中寻找,该树堆实例中存储随机数生成器,用于后续新建节点时生成随机数,创建时传入是否允许该树堆出现重复值,如果不允许则进行覆盖,允许则对节点数目增加即可。
```go
type treap struct {
root *node //根节点指针
size int //存储元素数量
cmp comparator.Comparator //比较器
rand *rand.Rand //随机数生成器
isMulti bool //是否允许重复
mutex sync.Mutex //并发控制锁
}
```
node树节点结构体该节点是树堆的树节点若该树堆允许重复则对节点num+1即可,否则对value进行覆盖树堆节点将针对堆的性质通过左右旋转的方式做平衡。
```go
type node struct {
value interface{} //节点中存储的元素
priority uint32 //该节点的优先级,随机生成
num int //该节点中存储的数量
left *node //左节点指针
right *node //右节点指针
}
```
#### 接口
```go
type treaper interface {
Iterator() (i *Iterator.Iterator) //返回包含该树堆的所有元素,重复则返回多个
Size() (num int) //返回该树堆中保存的元素个数
Clear() //清空该树堆
Empty() (b bool) //判断该树堆是否为空
Insert(e interface{}) //向树堆中插入元素e
Erase(e interface{}) //从树堆中删除元素e
Count(e interface{}) (num int) //从树堆中寻找元素e并返回其个数
}
```
#### New
新建一个treap树堆容器并返回初始根节点为nil传入该树堆是否为可重复属性,如果为true则保存重复值,否则对原有相等元素进行覆盖,若有传入的比较器,则将传入的第一个比较器设为该树堆的比较器。
```go
func New(isMulti bool, Cmp ...comparator.Comparator) (t *treap) {
//设置默认比较器
var cmp comparator.Comparator
if len(Cmp) > 0 {
cmp = Cmp[0]
}
//创建随机数生成器
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return &treap{
root: nil,
size: 0,
cmp: cmp,
rand: r,
isMulti: isMulti,
mutex: sync.Mutex{},
}
}
```
新建一个树堆节点并返回将传入的元素e作为该节点的承载元素该节点的num默认为1,左右子节点设为nil该节点优先级随机生成,范围在0~2^16内。
```go
func newNode(e interface{}, rand *rand.Rand) (n *node) {
return &node{
value: e,
priority: uint32(rand.Intn(4294967295)),
num: 1,
left: nil,
right: nil,
}
}
```
#### Iterator
以treap树堆做接收者将该树堆中所有保存的元素将从根节点开始以中缀序列的形式放入迭代器中若允许重复存储则对于重复元素进行多次放入。
```go
func (t *treap) Iterator() (i *Iterator.Iterator) {
if t == nil {
return nil
}
t.mutex.Lock()
es := t.root.inOrder()
i = Iterator.New(&es)
t.mutex.Unlock()
return i
}
```
以node树堆节点做接收者以中缀序列返回节点集合若允许重复存储则对于重复元素进行多次放入。
```go
func (n *node) inOrder() (es []interface{}) {
if n == nil {
return es
}
if n.left != nil {
es = append(es, n.left.inOrder()...)
}
for i := 0; i < n.num; i++ {
es = append(es, n.value)
}
if n.right != nil {
es = append(es, n.right.inOrder()...)
}
return es
}
```
#### Size
以treap树堆做接收者返回该容器当前含有元素的数量如果容器为nil返回0。
```go
func (t *treap) Size() (num int) {
if t == nil {
return 0
}
return t.size
}
```
#### Clear
以treap树堆做接收者将该容器中所承载的元素清空将该容器的size置0。
```go
func (t *treap) Clear() {
if t == nil {
return
}
t.mutex.Lock()
t.root = nil
t.size = 0
t.mutex.Unlock()
}
```
#### Empty
以treap树堆做接收者判断该二叉搜索树是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true。
```go
func (t *treap) Empty() (b bool) {
if t == nil {
return true
}
if t.size > 0 {
return false
}
return true
}
```
#### rightRotate
以node树堆节点做接收者新建一个节点作为n节点的右节点,同时将n节点的数值放入新建节点中作为右转后的n节点右转后的n节点的左节点是原n节点左节点的右节点,右转后的右节点保持不变原n节点改为原n节点的左节点,同时右节点指向新建的节点即右转后的n节点该右转方式可以保证n节点的双亲节点不用更换节点指向。
```go
func (n *node) rightRotate() {
if n == nil {
return
}
if n.left == nil {
return
}
//新建节点作为更换后的n节点
tmp := &node{
value: n.value,
priority: n.priority,
num: n.num,
left: n.left.right,
right: n.right,
}
//原n节点左节点上移到n节点位置
n.right = tmp
n.value = n.left.value
n.priority = n.left.priority
n.num = n.left.num
n.left = n.left.left
}
```
#### leftRotate
以node树堆节点做接收者新建一个节点作为n节点的左节点,同时将n节点的数值放入新建节点中作为左转后的n节点左转后的n节点的右节点是原n节点右节点的左节点,左转后的左节点保持不变原n节点改为原n节点的右节点,同时左节点指向新建的节点即左转后的n节点该左转方式可以保证n节点的双亲节点不用更换节点指向。
```go
func (n *node) leftRotate() {
if n == nil {
return
}
if n.right == nil {
return
}
//新建节点作为更换后的n节点
tmp := &node{
value: n.value,
priority: n.priority,
num: n.num,
left: n.left,
right: n.right.left,
}
//原n节点右节点上移到n节点位置
n.left = tmp
n.value = n.right.value
n.priority = n.right.priority
n.num = n.right.num
n.right = n.right.right
}
```
#### Insert
以treap树堆做接收者向二叉树插入元素e,若不允许重复则对相等元素进行覆盖如果二叉树为空则之间用根节点承载元素e,否则以根节点开始进行查找,对于该树堆来说,通过赋予随机的优先级根据堆的性质来实现平衡。
```go
func (t *treap) Insert(e interface{}) {
//判断容器是否存在
if t == nil {
return
}
t.mutex.Lock()
if t.Empty() {
//判断比较器是否存在
if t.cmp == nil {
t.cmp = comparator.GetCmp(e)
}
if t.cmp == nil {
t.mutex.Unlock()
return
}
//插入到根节点
t.root = newNode(e, t.rand)
t.size = 1
t.mutex.Unlock()
return
}
//从根节点向下插入
if t.root.insert(newNode(e, t.rand), t.isMulti, t.cmp) {
t.size++
}
t.mutex.Unlock()
}
```
以node二叉搜索树节点做接收者从n节点中插入元素e如果n节点中承载元素与e不同则根据大小从左右子树插入该元素如果n节点与该元素相等,且允许重复值,则将num+1否则对value进行覆盖插入成功返回true,插入失败或不允许重复插入返回false。
```go
func (n *node) insert(e *node, isMulti bool, cmp comparator.Comparator) (b bool) {
if cmp(n.value, e.value) > 0 {
if n.left == nil {
//将左节点直接设为e
n.left = e
b = true
} else {
//对左节点进行递归插入
b = n.left.insert(e, isMulti, cmp)
}
if n.priority > e.priority {
//对n节点进行右转
n.rightRotate()
}
return b
} else if cmp(n.value, e.value) < 0 {
if n.right == nil {
//将右节点直接设为e
n.right = e
b = true
} else {
//对右节点进行递归插入
b = n.right.insert(e, isMulti, cmp)
}
if n.priority > e.priority {
//对n节点进行左转
n.leftRotate()
}
return b
}
if isMulti {
//允许重复
n.num++
return true
}
//不允许重复,对值进行覆盖
n.value = e.value
return false
}
```
#### Erase
以treap树堆做接收者从树堆中删除元素e若允许重复记录则对承载元素e的节点中数量记录减一即可若不允许重复记录则删除该节点同时将前缀节点或后继节点更换过来以保证树堆的不发送断裂交换后根据优先级进行左右旋转以保证符合堆的性质如果该树堆仅持有一个元素且根节点等价于待删除元素,则将根节点置为nil。
```go
func (t *treap) Erase(e interface{}) {
if t == nil {
return
}
if t.Empty() {
return
}
t.mutex.Lock()
if t.size == 1 && t.cmp(t.root.value, e) == 0 {
//该树堆仅持有一个元素且根节点等价于待删除元素,则将根节点置为nil
t.root = nil
t.size = 0
t.mutex.Unlock()
return
}
//从根节点开始删除元素
if t.root.delete(e, t.isMulti, t.cmp) {
//删除成功
t.size--
}
t.mutex.Unlock()
}
```
以node二叉搜索树节点做接收者从n节点中删除元素e如果n节点中承载元素与e不同则根据大小从左右子树删除该元素如果n节点与该元素相等,且允许重复值,则将num-1否则直接删除该元素删除时先寻找该元素的前缀节点,若不存在则寻找其后继节点进行替换,替换后删除该节点。
```go
func (n *node) delete(e interface{}, isMulti bool, cmp comparator.Comparator) (b bool) {
if n == nil {
return false
}
//n中承载元素小于e,从右子树继续删除
if cmp(n.value, e) < 0 {
if n.right == nil {
//右子树为nil,删除终止
return false
}
if cmp(e, n.right.value) == 0 && (!isMulti || n.right.num == 1) {
//待删除节点无子节点,直接删除即可
if n.right.left == nil && n.right.right == nil {
//右子树可直接删除
n.right = nil
return true
}
}
//从右子树继续删除
return n.right.delete(e, isMulti, cmp)
}
//n中承载元素大于e,从左子树继续删除
if cmp(n.value, e) > 0 {
if n.left == nil {
//左子树为nil,删除终止
return false
}
if cmp(e, n.left.value) == 0 && (!isMulti || n.left.num == 1) {
//待删除节点无子节点,直接删除即可
if n.left.left == nil && n.left.right == nil {
//左子树可直接删除
n.left = nil
return true
}
}
//从左子树继续删除
return n.left.delete(e, isMulti, cmp)
}
if isMulti && n.num > 1 {
//允许重复且数量超过1
n.num--
return true
}
//删除该节点
tmp := n
//左右子节点都存在则选择优先级较小一个进行旋转
for tmp.left != nil && tmp.right != nil {
if tmp.left.priority < tmp.right.priority {
tmp.rightRotate()
if tmp.right.left == nil && tmp.right.right == nil {
tmp.right = nil
return false
}
tmp = tmp.right
} else {
tmp.leftRotate()
if tmp.left.left == nil && tmp.left.right == nil {
tmp.left = nil
return false
}
tmp = tmp.left
}
}
if tmp.left == nil && tmp.right != nil {
//到左子树为nil时直接换为右子树即可
tmp.value = tmp.right.value
tmp.num = tmp.right.num
tmp.priority = tmp.right.priority
tmp.left = tmp.right.left
tmp.right = tmp.right.right
} else if tmp.right == nil && tmp.left != nil {
//到右子树为nil时直接换为左子树即可
tmp.value = tmp.left.value
tmp.num = tmp.left.num
tmp.priority = tmp.left.priority
tmp.right = tmp.left.right
tmp.left = tmp.left.left
}
//当左右子树都为nil时直接结束
return true
}
```
#### Count
以treap树堆做接收者从树堆中查找元素e的个数如果找到则返回该树堆中和元素e相同元素的个数如果不允许重复则最多返回1如果未找到则返回0。
```go
func (t *treap) Count(e interface{}) (num int) {
if t == nil {
//树堆不存在,直接返回0
return 0
}
if t.Empty() {
return
}
t.mutex.Lock()
num = t.root.search(e, t.cmp)
t.mutex.Unlock()
//树堆存在,从根节点开始查找该元素
return num
}
```
以node二叉搜索树节点做接收者从n节点中查找元素e并返回存储的个数如果n节点中承载元素与e不同则根据大小从左右子树查找该元素如果n节点与该元素相等,则直接返回其个数。
```go
func (n *node) search(e interface{}, cmp comparator.Comparator) (num int) {
if n == nil {
return 0
}
if cmp(n.value, e) > 0 {
return n.left.search(e, cmp)
} else if cmp(n.value, e) < 0 {
return n.right.search(e, cmp)
}
return n.num
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/treap"
"sync"
)
func main() {
bs := treap.New(true)
wg := sync.WaitGroup{}
for i := 0; i < 20; i++ {
wg.Add(1)
bs.Insert(i)
go func() {
bs.Insert(i)
wg.Done()
}()
}
wg.Wait()
fmt.Println("遍历输出所有插入的元素")
for i := bs.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
fmt.Println("删除一次树堆中存在的元素,存在重复的将会被剩下")
for i := 0; i < 20; i++ {
bs.Erase(i)
}
fmt.Println("输出剩余的重复元素")
for i := bs.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 遍历输出所有插入的元素
> 0
> 1
> 2
> 3
> 4
> 5
> 6
> 7
> 8
> 8
> 9
> 10
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 12
> 12
> 13
> 14
> 15
> 16
> 16
> 16
> 16
> 16
> 17
> 17
> 18
> 19
> 20
> 20
> 20
> 删除一次树堆中存在的元素,存在重复的将会被剩下
> 输出剩余的重复元素
> 8
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 11
> 12
> 16
> 16
> 16
> 16
> 17
> 20
> 20
> 20
#### 时间开销
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/treap"
"time"
)
func main() {
max:=10000000
tv := time.Now()
v := make([]interface{},max,max)
num:=0
for i := 0; i < max; i++ {
if i%2==1{
num=max-i
}else{
num=i
}
v[num]=num
}
fmt.Println("插入切片的消耗时间:",time.Since(tv))
tt := time.Now()
t := treap.New(false)
for i := 0; i < max; i++ {
if i%2==1{
num=max-i
}else{
num=i
}
t.Insert(num)
}
fmt.Println("插入树堆的消耗时间:",time.Since(tt))
}
```
> 插入切片的消耗时间: 201.4934ms
> 插入树堆的消耗时间: 7.0841462s

View File

@ -0,0 +1,727 @@
github仓库存储地址https://github.com/hlccd/goSTL
### Comparator
#### 概述
对于某些存在大小比较的数据结构,如果每次都要特定的实现一些大小比较是十分繁琐的,特别是对于一些官方已经设定的类型,如果将基本类型引入数据结构中时需要实现其元素的比较会简单。
同时对于一些常用的函数比如排序、查找、排序第n个以及寻找上下边界的函数这些函数需要通过比较器进行配合实现为了更进一步简化使用可以在比较器中实现。
#### 定义
对于一个比较器除开基本类型外必须传入比较函数当然基本数据类型也可以传入自定的比较函数进行覆盖对于待使用的比较函数需要传入两个元素a和b有先后同时返回一个int其中**0表示相等正数表示a>b负数表示a<b**
```go
type Comparator func(a, b interface{}) int
```
除比较器外,同时等一一个判断相等的工具——相等器,用于其他数据结构中不需要比较大小只需要判断是否相等的工具。
```
type Equaler func(a, b interface{}) (B bool)
```
#### GetCmp
对于一些基本数据类型,可以预先设定好比较函数以节约使用时必须重写基本数据类型的比较函数的过程。
当然,对于要进行比较的基本类型来说,需要传入一个对象以获取其数据类型从而返回对应的默认比较器。
以下部分**直接复制即可**,其实现仅仅只是判断类型并返回默认比较器,并无需要理解的部分。
```go
func GetCmp(e interface{}) (cmp Comparator) {
if e==nil{
return nil
}
switch e.(type) {
case bool:
return boolCmp
case int:
return intCmp
case int8:
return int8Cmp
case uint8:
return uint8Cmp
case int16:
return int16Cmp
case uint16:
return uint16Cmp
case int32:
return int32Cmp
case uint32:
return uint32Cmp
case int64:
return int64Cmp
case uint64:
return uint64Cmp
case float32:
return float32Cmp
case float64:
return float64Cmp
case complex64:
return complex64Cmp
case complex128:
return complex128Cmp
case string:
return stringCmp
}
return nil
}
```
##### basicCmp
```go
//以下为系统自带类型的默认比较器
func boolCmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(bool) {
return 1
} else if b.(bool) {
return -1
}
return 0
}
func intCmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(int) > b.(int) {
return 1
} else if a.(int) < b.(int) {
return -1
}
return 0
}
func int8Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(int8) > b.(int8) {
return 1
} else if a.(int8) < b.(int8) {
return -1
}
return 0
}
func uint8Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(uint8) > b.(uint8) {
return 1
} else if a.(uint8) < b.(uint8) {
return -1
}
return 0
}
func int16Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(int16) > b.(int16) {
return 1
} else if a.(int16) < b.(int16) {
return -1
}
return 0
}
func uint16Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(uint16) > b.(uint16) {
return 1
} else if a.(uint16) < b.(uint16) {
return -1
}
return 0
}
func int32Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(int32) > b.(int32) {
return 1
} else if a.(int32) < b.(int32) {
return -1
}
return 0
}
func uint32Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(uint32) > b.(uint32) {
return 1
} else if a.(uint32) < b.(uint32) {
return -1
}
return 0
}
func int64Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(int64) > b.(int64) {
return 1
} else if a.(int64) < b.(int64) {
return -1
}
return 0
}
func uint64Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(uint64) > b.(uint64) {
return 1
} else if a.(uint64) < b.(uint64) {
return -1
}
return 0
}
func float32Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(float32) > b.(float32) {
return 1
} else if a.(float32) < b.(float32) {
return -1
}
return 0
}
func float64Cmp(a, b interface{}) int {
if a == b {
return 0
}
if a.(float64) > b.(float64) {
return 1
} else if a.(float64) < b.(float64) {
return -1
}
return 0
}
func complex64Cmp(a, b interface{}) int {
if a == b {
return 0
}
if real(a.(complex64)) > real(b.(complex64)) {
return 1
} else if real(a.(complex64)) < real(b.(complex64)) {
return -1
} else {
if imag(a.(complex64)) > imag(b.(complex64)) {
return 1
} else if imag(a.(complex64)) < imag(b.(complex64)) {
return -1
}
}
return 0
}
func complex128Cmp(a, b interface{}) int {
if a == b {
return 0
}
if real(a.(complex128)) > real(b.(complex128)) {
return 1
} else if real(a.(complex128)) < real(b.(complex128)) {
return -1
} else {
if imag(a.(complex128)) > imag(b.(complex128)) {
return 1
} else if imag(a.(complex128)) < imag(b.(complex128)) {
return -1
}
}
return 0
}
func stringCmp(a, b interface{}) int {
if a == b {
return 0
}
if len(a.(string)) > len(b.(string)) {
return 1
} else if len(a.(string)) < len(b.(string)) {
return -1
} else {
if a.(string) > b.(string) {
return 1
} else if a.(string) < b.(string) {
return -1
}
}
return 0
}
```
#### GetEqual
获取默认的相等器。
```go
func GetEqual() (equ Equaler) {
return basicEqual
}
```
##### basicEqual
默认的相等器,即两个元素完全相等。
```go
func basicEqual(a, b interface{}) (B bool) {
return a == b
}
```
#### Sort
排序对于传入的数组通过传入的比较函数基本类型可通过GetCmp直接获取不需要传入进行排序。
为了简化实现,可以将待比较元素组限定为线性数组,而对于一些非线性结构,可以通过将其指针列为线性表,再通过传入的比较函数进行比较即可,但一般不建议非线性结构使用比较器,其结构最好自己去维护大小分布,这样可以更好的提高效率。
排序中分别实现了**二分排序**和**归并排序**,由于二分排序本身不稳定,所以更加适合数据量不太大的数组,而对于归并排序,其性能是十分稳定的,更加适合用于数据量较大的数组,所以对待排序数组根据长度进行了区分,以根据情况使用合适的排序方式。
传入的数组是其**指针**,传入指针可以减少复制的情况,以节约时间。
```go
func Sort(arr *[]interface{}, Cmp ...Comparator) {
//如果传入一个空数组或nil,则直接结束
if arr==nil || (*arr)==nil || len((*arr)) == 0 {
return
}
var cmp Comparator
cmp = nil
if len(Cmp) > 0 {
cmp = Cmp[0]
} else {
cmp = GetCmp((*arr)[0])
}
if cmp == nil {
//未传入比较比较函数且并非默认类型导致未找到默认比较器则直接终止排序
return
}
//根据数组长度进行分类选择排序函数
if len((*arr)) < 2^26 {
//当长度小于2^16时使用二分排序
binary(arr,0,len((*arr))-1, cmp)
} else {
merge(arr,0,len((*arr))-1, cmp)
}
}
```
##### Binary
对于二分排序,其原理主要是将一个无须数组,通过寻找一个中间量(一般是数组中间点的值)作为参考,通过比较和交换使得数组中间量左侧始终不大于中间量,右侧始终不小于中间量,即**相对有序的状态**,再对两侧的情况进行递归排序,从而使得每一部分都是相对有序的,依次保证整体的有序性。
当中间值取的较差甚至是极值的时候,二分排序将会退化为冒泡排序,该排序方案并不稳定,但由于不需要额外空间进行存储,所以可以在较小的数组中进行使用。
```go
func binary(arr *[]interface{},l,r int, cmp Comparator) {
//对当前部分进行预排序,使得两侧都大于或小于中间值
if l >= r {
return
}
m := (*arr)[(r + l) / 2]
i, j := l-1, r+1
for i < j {
i++
for cmp((*arr)[i], m) < 0 {
i++
}
j--
for cmp((*arr)[j],m) > 0 {
j--
}
if i < j {
(*arr)[i],(*arr)[j]=(*arr)[j],(*arr)[i]
}
}
//对分好的两侧进行迭代二分排序
binary(arr,l,j, cmp)
binary(arr,j+1,r, cmp)
}
```
##### Merge
对于归并排序,其原理主要是将一个无须数组,先通过对数组的左右拆分,随后从最小部分(一般是仅存在两个或一个元素)进行初步排序,然后依次向上合并,由于合并的两个小数组的内部都是有序的,所以只需要依次遍历比较其大小即可,与此同时,需要一个临时数组去存储比较结果,随后再将临时数组中的值放入待排序的数组中去,依次合并到整个数组来保证其有序性。
由于该方案是将数组逐步拆分为最小单元在进行合并,其情况必然是相对稳定的,虽然需要一定的额外空间进行存储,但相较于其稳定性来说也是值得的空间成本。
```go
func merge(arr *[]interface{},l,r int, cmp Comparator) {
//对当前部分进行分组排序,将该部分近似平均的拆为两部分进行比较排序
if l >= r {
return
}
m := (r + l) / 2
//对待排序内容进行二分
merge(arr,l,m, cmp)
merge(arr,m+1,r, cmp)
//二分结束后依次比较进行归并
i, j := l, m+1
var tmp []interface{}=make([]interface{},0,r-l+1)
for i <= m && j <= r {
if cmp((*arr)[i], (*arr)[j]) <= 0 {
tmp = append(tmp, (*arr)[i])
i++
} else {
tmp = append(tmp, (*arr)[j])
j++
}
}
//当一方比较到头时将另一方剩余内容全部加入进去
for ; i <= m; i++ {
tmp = append(tmp, (*arr)[i])
}
for ; j <= r; j++ {
tmp = append(tmp, (*arr)[j])
}
//将局部排序结果放入迭代器中
for i, j = l, 0; i <= r; i, j = i+1, j+1 {
(*arr)[i]=tmp[j]
}
}
```
##### 示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/utils/comparator"
)
func main() {
var arr =make([]interface{},0,0)
arr=append(arr,5)
arr=append(arr,3)
arr=append(arr,2)
arr=append(arr,4)
arr=append(arr,1)
arr=append(arr,4)
arr=append(arr,3)
arr=append(arr,1)
arr=append(arr,5)
arr=append(arr,2)
comparator.Sort(&arr)
for i:=0;i< len(arr);i++{
println(arr[i].(int))
}
}
```
#### Search
对于一个**有序线性表**来说,如果要从中查找某个元素,可以通过二分的方式进行查找,即通过比对该元素和当前区间的中值的大小,从而判断去掉左侧或右侧区间,然后继续进行比较直至剩下一个元素即可,此时只需要比较该元素和待查找元素是否相等,相等则返回该元素下标,不等返回-1表示未找到该元素。
先对传入是参数进行判断以保证在查找时不会出现error判断我完成后再调用search函数进行查找
```go
func Search(arr *[]interface{}, e interface{}, Cmp ...Comparator) (idx int) {
if arr==nil || (*arr)==nil || len((*arr)) == 0 {
return
}
//判断比较函数是否有效,若无效则寻找默认比较器
var cmp Comparator
cmp = nil
if len(Cmp) == 0 {
cmp = GetCmp(e)
} else {
cmp = Cmp[0]
}
if cmp == nil {
//若并非默认类型且未传入比较比较函数则直接结束
return -1
}
//查找开始
return search(arr, e, cmp)
}
```
##### search
二分查找函数:
```go
func search(arr *[]interface{}, e interface{}, cmp Comparator) (idx int) {
//通过二分查找的方式寻找该元素
l, m, r := 0, (len((*arr))-1)/2, len((*arr))
for l < r {
m = (l + r) / 2
if cmp((*arr)[m], e) < 0 {
l = m + 1
} else {
r = m
}
}
//查找结束
if (*arr)[l] == e {
//该元素存在,返回下标
return l
}
//该元素不存在,返回-1
return -1
}
```
##### 示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/utils/comparator"
)
func main() {
var arr =make([]interface{},0,0)
arr=append(arr,5)
arr=append(arr,3)
arr=append(arr,2)
arr=append(arr,4)
arr=append(arr,1)
arr=append(arr,4)
arr=append(arr,3)
arr=append(arr,1)
arr=append(arr,5)
arr=append(arr,2)
comparator.Sort(&arr)
for i:=0;i< len(arr);i++{
println(arr[i].(int))
}
fmt.Println("search:",comparator.Search(&arr,3))
}
```
#### NthElement
对于要找的第n个元素**下标从0开始**可以使用和二分排序类似的方式只不过由于只需要将第n个元素放到第n位所以只需要排序第n位所在的区间即对利用中间值进行二分之后所获得的两个区间**只需要对包含n的区间进行二分即可**,另一个区间可以直接不管。
该函数结果将会返回位于n的元素该过程分两部分进行第一部分验证其可执行情况即指针是否为nil数组是否为niln是否超出数组范围之类的情况若出现类似情况则直接返回nil即可。否则则对数组进行有限排序。
```go
func NthElement(arr *[]interface{}, n int, Cmp ...Comparator) (value interface{}){
if arr==nil || (*arr)==nil || len((*arr)) == 0 {
return nil
}
//判断比较函数是否有效
var cmp Comparator
cmp = nil
if len(Cmp) > 0 {
cmp = Cmp[0]
} else {
cmp = GetCmp((*arr)[0])
}
if cmp == nil {
return nil
}
//判断待确认的第n位是否在该集合范围内
if len((*arr)) < n || n<0 {
return nil
}
//进行查找
nthElement(arr,0,len((*arr))-1, n, cmp)
return (*arr)[n]
}
```
##### nthElement
实现部分不返回任何值仅仅是进行有限排序即仅排序包含n的区间。
```go
func nthElement(arr *[]interface{},l,r int, n int, cmp Comparator){
//二分该区域并对此进行预排序
if l >= r {
return
}
m := (*arr)[(r + l) / 2]
i, j := l-1, r+1
for i < j {
i++
for cmp((*arr)[i], m) < 0 {
i++
}
j--
for cmp((*arr)[j], m) > 0 {
j--
}
if i < j {
(*arr)[i],(*arr)[j]=(*arr)[j],(*arr)[i]
}
}
//确认第n位的范围进行局部二分
if n-1 >= i {
nthElement(arr,j+1,r, n, cmp)
} else {
nthElement(arr,l,j, n, cmp)
}
}
```
##### 示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/utils/comparator"
)
func main() {
var arr =make([]interface{},0,0)
arr=append(arr,5)
arr=append(arr,3)
arr=append(arr,2)
arr=append(arr,4)
arr=append(arr,1)
arr=append(arr,4)
arr=append(arr,3)
arr=append(arr,1)
arr=append(arr,5)
arr=append(arr,2)
for i:=0;i< len(arr);i++{
fmt.Println("n:",comparator.NthElement(&arr,i))
}
}
```
#### Bound
对于一组有序线性表,可以通过二分查找的方式,获得它的上下界,当待查找元素不存在于该线性表内时,则返回的上届是小于它的最大值,返回的下届是大于它的最小值。当元素存在于线性表内时,返回的上界是该元素最右侧的下标,下届是该元素最左侧的下标
查找方法是二分查找的变形,返回查找值的边界。
##### UpperBound
```go
func UpperBound(arr *[]interface{}, e interface{}, Cmp ...Comparator) (idx int) {
if arr==nil || (*arr)==nil || len((*arr)) == 0 {
return -1
}
//判断比较函数是否有效
var cmp Comparator
cmp = nil
if len(Cmp) == 0 {
cmp = GetCmp(e)
} else {
cmp = Cmp[0]
}
if cmp == nil {
return -1
}
//寻找该元素的上界
return upperBound(arr, e, cmp)
}
```
##### upperBound
```go
func upperBound(arr *[]interface{}, e interface{}, cmp Comparator) (idx int) {
l, m, r := 0, len((*arr)) / 2, len((*arr))-1
for l < r {
m = (l + r + 1) / 2
if cmp((*arr)[m], e) <= 0 {
l = m
} else {
r = m - 1
}
}
return l
}
```
##### LowerBound
```go
func LowerBound(arr *[]interface{}, e interface{}, Cmp ...Comparator) (idx int) {
if arr==nil || (*arr)==nil || len((*arr)) == 0 {
return -1
}
//判断比较函数是否有效
var cmp Comparator
cmp = nil
if len(Cmp) == 0 {
cmp = GetCmp(e)
} else {
cmp = Cmp[0]
}
if cmp == nil {
return -1
}
//寻找该元素的下界
return lowerBound(arr, e, cmp)
}
```
##### lowerBound
```go
func lowerBound(arr *[]interface{}, e interface{}, cmp Comparator) (idx int) {
l, m, r := 0, len((*arr)) / 2, len((*arr))
for l < r {
m = (l + r) / 2
if cmp((*arr)[m], e) >= 0 {
r = m
} else {
l = m + 1
}
}
return l
}
```
##### 示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/utils/comparator"
)
func main() {
var arr =make([]interface{},0,0)
arr=append(arr,5)
arr=append(arr,3)
arr=append(arr,2)
arr=append(arr,4)
arr=append(arr,1)
arr=append(arr,4)
arr=append(arr,3)
arr=append(arr,1)
arr=append(arr,5)
arr=append(arr,2)
comparator.Sort(&arr)
for i:=0;i< len(arr);i++{
fmt.Println(i,"=",arr[i])
}
fmt.Println("\n\n\n")
for i:=0;i< len(arr);i++{
fmt.Println(i)
fmt.Println("upper:",comparator.UpperBound(&arr,i))
fmt.Println("lower:",comparator.LowerBound(&arr,i))
fmt.Println()
}
}
```

View File

@ -0,0 +1,461 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
ring是一种**离散的环状线性结构**,即**首尾连接的线性结构**,它是又多个分布在不同物理空间的结点,通过指针链接建立逻辑连接而形成的线性结构。
但不同的地方在于环本身没有首尾结点之分,甚至说,它没有首尾结点,它可以看作是**将链表的首尾结点连接起来**,即任何一个结点都可以看作是首节点也可以看作是尾结点,可以通过对环中任意一个结点向前或向后遍历得到全部结点。
同链表一样,它的所有结点之间都是相互分离的,基本不存在在物理上临接的可能(不绝对),所以它要增加或删除结点的过程也是十分简单的,在添加时建立待添加结点和前后节点的连接即可,删除时建立前后节点的连接即可。适用于频繁删减的情况,或者循环遍历的情况。
### 原理
对于一个环状线性结构来说,主要是要建立其它的连接,相比较于用数组的形式实现来说,数组实现需要分配一段连续的空间用以存储数据,但当数组分配的空间不足时,则需要再重新分配空间并将原有的元素复制过去,考虑到复制数据的时间开销也是不容忽略的,所以需要找到另一个结构去尽量减少复制元素的时间开销。
同时,对于使用数组去实现环状结构来说,它需要通过逻辑上去建立首尾结点的连接,而不是将这两个点连起来,直观上并不是十分友好,特别是在扩容缩容的时候需要做的事情太多了,时间开销太大了,所以本次**实现并不采用数组形式实现**,但**数组形式仍然是可行的**。
#### 添加策略
对于环来说,由于它的结点之间的物理间隔并不是连续的,而是根据需要随机的分布在整个内存中的,同时,对于一个环来说,**环并不存在一个核心结点**,它是任意一个结点都可以当作核心结点去使用。
对于此种特性ring在添加结点时只会出现两种情况
1. 环不存在:创造一个自环的结点并持有即可,自环指自己前后节点均指向自己
2. 环存在:在当前持有的结点后面插入一个结点即可,同时建立新结点和前后节点之间的关系,此时持有结点不变
#### 删除策略
同添加策略类似,考虑到环本身无核心的特性,只需要判断销毁该结点后是否仍有结点即可,有则更换持有结点,否则销毁环即可。
ring删除主要会有以下三种情况
1. 环不存在:结束
2. 环仅有一个结点:销毁环
3. 环有多个结点:销毁当前持有结点,并将持有结点切换为原持有节点的**下一个结点**,同时通过在新持有结点前插入原持有结点的前结点实现连接。
### 实现
ring环结构体包含环的头尾节点指针当增删结点时只需要移动到对应位置进行操作即可当一个节点进行增删时需要同步修改其临接结点的前后指针结构体中记录该环中当前所持有的结点的指针即可同时记录该环中存在多少元素即size使用并发控制锁以保证数据一致性。
```go
type ring struct {
now *node //环当前持有的结点指针
size uint64 //当前存储的元素个数
mutex sync.Mutex //并发控制锁
}
```
环的node节点结构体pre和next是该节点的前后两个节点的指针用以保证环整体是相连的。
```go
type node struct {
data interface{} //结点所承载的元素
pre *node //前结点指针
next *node //后结点指针
}
```
#### 接口
```go
type ringer interface {
Iterator() (i *Iterator.Iterator) //创建一个包含环中所有元素的迭代器并返回其指针
Size() (size uint64) //返回环所承载的元素个数
Clear() //清空该环
Empty() (b bool) //判断该环是否位空
Insert(e interface{}) //向环当前位置后方插入元素e
Erase() //删除当前结点并持有下一结点
Value() (e interface{}) //返回当前持有结点的元素
Set(e interface{}) //在当前结点设置其承载的元素为e
Next() //持有下一节点
Pre() //持有上一结点
}
```
```go
type noder interface {
preNode() (m *node) //返回前结点指针
nextNode() (m *node) //返回后结点指针
insertPre(pre *node) //在该结点前插入结点并建立连接
insertNext(next *node) //在该结点后插入结点并建立连接
erase() //删除该结点,并使该结点前后两结点建立连接
value() (e interface{}) //返回该结点所承载的元素
setValue(e interface{}) //修改该结点承载元素为e
}
```
#### New
新建一个ring环容器并返回初始持有的结点不存在,即为nil初始size为0。
```go
func New() (r *ring) {
return &ring{
now: nil,
size: 0,
mutex: sync.Mutex{},
}
}
```
新建一个自环结点并返回其指针,初始首结点的前后结点指针都为自身。
```go
func newNode(e interface{}) (n *node) {
n = &node{
data: e,
pre: nil,
next: nil,
}
n.pre = n
n.next = n
return n
}
```
#### Iterator
以ring环容器做接收者将ring环容器中所承载的元素放入迭代器中从该结点开始向后遍历获取全部承载的元素。
```go
func (r *ring) Iterator() (i *Iterator.Iterator) {
if r == nil {
r = New()
}
r.mutex.Lock()
//将所有元素复制出来放入迭代器中
tmp := make([]interface{}, r.size, r.size)
//从当前结点开始向后遍历
for n, idx := r.now, uint64(0); n != nil && idx < r.size; n, idx = n.nextNode(), idx+1 {
tmp[idx] = n.value()
}
i = Iterator.New(&tmp)
r.mutex.Unlock()
return i
}
```
以node结点做接收者返回该结点的前结点。
```go
func (n *node) preNode() (pre *node) {
if n == nil {
return
}
return n.pre
}
```
以node结点做接收者返回该结点的后结点。
```go
func (n *node) nextNode() (next *node) {
if n == nil {
return
}
return n.next
}
```
#### Size
以ring环容器做接收者返回该容器当前含有元素的数量。
```go
func (r *ring) Size() (size uint64) {
if r == nil {
r = New()
}
return r.size
}
```
#### Clear
以ring环容器做接收者将该容器中所承载的元素清空将该容器的当前持有的结点置为nil,长度初始为0。
```go
func (r *ring) Clear() {
if r == nil {
r = New()
}
r.mutex.Lock()
//销毁环
r.now = nil
r.size = 0
r.mutex.Unlock()
}
```
#### Empty
以ring环容器做接收者判断该ring环容器是否含有元素该判断过程通过size进行判断,size为0则为true,否则为false。
```go
func (r *ring) Empty() (b bool) {
if r == nil {
r = New()
}
return r.size == 0
}
```
#### Insert
以ring环容器做接收者通过环中当前持有的结点进行添加如果环为建立,则新建一个自环结点设为环,存在持有的结点,则在其后方添加即可。
```go
func (r *ring) Insert(e interface{}) {
if r == nil {
r = New()
}
r.mutex.Lock()
//新建自环结点
n := newNode(e)
if r.size == 0 {
//原本无环,设为新环
r.now = n
} else {
//持有结点,在后方插入
r.now.insertNext(n)
}
r.size++
r.mutex.Unlock()
}
```
以node结点做接收者对该结点插入前结点并建立前结点和该结点之间的连接。
```go
func (n *node) insertPre(pre *node) {
if n == nil || pre == nil {
return
}
pre.next = n
pre.pre = n.pre
if n.pre != nil {
n.pre.next = pre
}
n.pre = pre
}
```
以node结点做接收者对该结点插入后结点并建立后结点和该结点之间的连接。
```go
func (n *node) insertNext(next *node) {
if n == nil || next == nil {
return
}
next.pre = n
next.next = n.next
if n.next != nil {
n.next.pre = next
}
n.next = next
}
```
#### Erase
以ring环容器做接收者先判断是否仅持有一个结点若仅有一个结点,则直接销毁环,否则将当前持有结点设为下一节点,并前插原持有结点的前结点即可。
```go
func (r *ring) Erase() {
if r == nil {
r = New()
}
if r.size == 0 {
return
}
r.mutex.Lock()
//删除开始
if r.size == 1 {
//环内仅有一个结点,销毁环即可
r.now = nil
} else {
//环内还有其他结点,将持有结点后移一位
//后移后将当前结点前插原持有结点的前结点
r.now = r.now.nextNode()
r.now.insertPre(r.now.preNode().preNode())
}
r.size--
r.mutex.Unlock()
}
```
以node结点做接收者销毁该结点同时建立该节点前后节点之间的连接。
```go
func (n *node) erase() {
if n == nil {
return
}
if n.pre == nil && n.next == nil {
return
} else if n.pre == nil {
n.next.pre = nil
} else if n.next == nil {
n.pre.next = nil
} else {
n.pre.next = n.next
n.next.pre = n.pre
}
n = nil
}
```
#### Value
以ring环容器做接收者获取环中当前持有节点所承载的元素若环中持有的结点不存在,直接返回nil。
```go
func (r *ring) Value() (e interface{}) {
if r == nil {
r = New()
}
if r.now == nil {
//无持有结点,直接返回nil
return nil
}
return r.now.value()
}
```
以node结点做接收者返回该结点所要承载的元素。
```go
func (n *node) value() (e interface{}) {
if n == nil {
return nil
}
return n.data
}
```
#### Set
以ring环容器做接收者修改当前持有结点所承载的元素若未持有结点,直接结束即可。
```go
func (r *ring) Set(e interface{}) {
if r == nil {
r = New()
}
if r.now == nil {
return
}
r.mutex.Lock()
r.now.setValue(e)
r.mutex.Unlock()
}
```
以node结点做接收者对该结点设置其承载的元素。
```go
func (n *node) setValue(e interface{}) {
if n == nil {
return
}
n.data = e
}
```
#### Next
以ring环容器做接收者将当前持有的结点后移一位若当前无持有结点,则直接结束。
```go
func (r *ring) Next() {
if r == nil {
r = New()
}
if r.now == nil {
return
}
r.mutex.Lock()
r.now = r.now.nextNode()
r.mutex.Unlock()
}
```
#### Pre
以ring环容器做接收者将当前持有的结点前移一位若当前无持有结点,则直接结束。
```go
func (r *ring) Pre() {
if r == nil {
r = New()
}
if r.size == 0 {
return
}
r.mutex.Lock()
r.now = r.now.preNode()
r.mutex.Unlock()
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/ring"
"sync"
)
func main() {
r:=ring.New()
wg:=sync.WaitGroup{}
for i:=0;i<10;i++{
wg.Add(1)
go func(num int) {
r.Insert(num)
wg.Done()
}(i)
}
wg.Wait()
for i:=uint64(0);i<r.Size();i++{
fmt.Println(r.Value())
r.Pre()
}
fmt.Println("ring当前持有结点的元素:",r.Value())
r.Set(-1)
fmt.Println("ring修改有持有的结点元素:",r.Value())
fmt.Println("删除后")
r.Erase()
for i:=r.Iterator();i.HasNext();i.Next(){
fmt.Println(i.Value())
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 2
> 0
> 1
> 5
> 3
> 4
> 7
> 6
> 8
> 9
> ring当前持有结点的元素: 2
> ring修改有持有的结点元素: -1
> 删除后
> 9
> 8
> 6
> 7
> 4
> 3
> 5
> 1
> 0

View File

@ -0,0 +1,351 @@
github仓库存储地址https://github.com/hlccd/goSTL
### Iterator
#### 概述
iterator模式提供一种方法使之能依次访问容器内的各个元素而又不暴露该聚合物内部的表述方式。
STL的中心思想是将算法与数据结构分离彼此独立设计最后在用iterator将他们结合在一起获得最大的适配性。
由于golang官方未实现泛型而interface存在可以替换为任意结构的特性故而可以**使用interface实现泛型**。
一个迭代器需要包括其存储的元素序列以及内部存储一个该迭代器当前指向的元素下标。
```go
//Iterator迭代器
//包含泛型切片和该迭代器当前指向元素的下标
//可通过下标和泛型切片长度来判断是否可以前移或后移
//当index不小于0时迭代器可前移
//当index小于data的长度时可后移
type Iterator struct {
data *[]interface{} //该迭代器中存放的元素集合的指针
index int //该迭代器当前指向的元素下标,-1即不存在元素
}
```
对于一个迭代器,它需要执行的主要方法有:返回该首尾迭代器,获取指向某位的迭代器,获取指定位置的元素,判断能否前后移动以及进行前后移动。
```go
type Iteratorer interface {
Begin() (I *Iterator) //将该迭代器设为位于首节点并返回新迭代器
End() (I *Iterator) //将该迭代器设为位于尾节点并返回新迭代器
Get(idx int) (I *Iterator) //将该迭代器设为位于第idx节点并返回该迭代器
Value() (e interface{}) //返回该迭代器下标所指元素
HasNext() (b bool) //判断该迭代器是否可以后移
Next() (b bool) //将该迭代器后移一位
HasPre() (b bool) //判罚该迭代器是否可以前移
Pre() (b bool) //将该迭代器前移一位
}
```
#### 接口实现
##### New
对于迭代器的初始化需要传入一个所要承载的元素集合的指针可以自己选择性传入一个index下标。该函数会返回一个承载了该元素集合的迭代器。
```go
func New(data *[]interface{}, Idx ...int) (i *Iterator) {
//迭代器下标
var idx int
if len(Idx) <= 0 {
//没有传入下标则将下标设为0
idx = 0
} else {
//有传入下标,则将传入下标第一个设为迭代器下标
idx = Idx[0]
}
if len((*data)) > 0 {
//如果元素集合非空,则判断下标是否超过元素集合范围
if idx >= len((*data)) {
//如果传入下标超过元素集合范围则寻找最近的下标值
idx = len((*data)) - 1
}
} else {
//如果元素集合为空则将下标设为-1
idx = -1
}
//新建并返回迭代器
return &Iterator{
data: data,
index: idx,
}
}
```
##### Bgein
以原有的迭代器做接受器,返回一个承载相同元素但下标位于首位的迭代器。
```go
func (i *Iterator) Begin() (I *Iterator) {
if i == nil {
//迭代器为空,直接结束
return nil
}
if len((*i.data)) == 0 {
//迭代器元素集合为空,下标设为-1
i.index = -1
} else {
//迭代器元素集合非空下标设为0
i.index = 0
}
//返回修改后的新指针
return &Iterator{
data: i.data,
index: i.index,
}
}
```
##### End
以原有的迭代器做接受器,返回一个承载相同元素但下标位于末尾的迭代器。
```go
func (i *Iterator) End() (I *Iterator) {
if i == nil {
//迭代器为空,直接返回
return nil
}
if len((*i.data)) == 0 {
//元素集合为空,下标设为-1
i.index = -1
} else {
//元素集合非空,下标设为最后一个元素的下标
i.index = len((*i.data)) - 1
}
//返回修改后的该指针
return &Iterator{
data: i.data,
index: i.index,
}
}
```
##### Get
以原有的迭代器做接受器返回一个承载相同元素但下标为自己传入的idx的迭代器当idx不在元素集合的范围内时下标设为距离idx最近的值即小于0设为首位大于元素集合长度则设为尾部其他情况设为idx。
```go
func (i *Iterator) Get(idx int) (I *Iterator) {
if i == nil {
//迭代器为空,直接返回
return nil
}
if idx <= 0 {
//预设下标超过元素集合范围,将下标设为最近元素的下标,此状态下为首元素下标
idx = 0
} else if idx >= len((*i.data))-1 {
//预设下标超过元素集合范围,将下标设为最近元素的下标,此状态下为尾元素下标
idx = len((*i.data)) - 1
}
if len((*i.data)) > 0 {
//元素集合非空,迭代器下标设为预设下标
i.index = idx
} else {
//元素集合为空,迭代器下标设为-1
i.index = -1
}
//返回修改后的迭代器指针
return i
}
```
##### Value
以原有的迭代器做接受器,返回该迭代器当前下标指向的元素
```go
func (i *Iterator) Value() (e interface{}) {
if i == nil {
//迭代器为nil返回nil
return nil
}
if len((*i.data)) == 0 {
//元素集合为空返回nil
return nil
}
if i.index <= 0 {
//下标超过元素集合范围下限,最近元素为首元素
i.index = 0
}
if i.index >= len((*i.data)) {
//下标超过元素集合范围上限,最近元素为尾元素
i.index = len((*i.data)) - 1
}
//返回下标指向元素
return (*i.data)[i.index]
}
```
##### HasNext
以原有的迭代器做接受器判断该迭代器是否可以进行后移可以则返回true否则返回false。
```go
func (i *Iterator) HasNext() (b bool) {
if i == nil {
//迭代器为nil时不能后移
return false
}
if len((*i.data)) == 0 {
//元素集合为空时不能后移
return false
}
//下标到达元素集合上限时不能后移,否则可以后移
return i.index < len((*i.data))
}
```
##### Next
以原有的迭代器做接受器将迭代器下标后移当满足后移条件时进行后移同时返回true当不满足后移条件时将下标设为尾元素下标同时返回false当迭代器为nil时返回false。
```go
func (i *Iterator) Next() (b bool) {
if i == nil {
//迭代器为nil时返回false
return false
}
if i.HasNext() {
//满足后移条件时进行后移
i.index++
return true
}
if len((*i.data)) == 0 {
//元素集合为空时下标设为-1同时返回false
i.index = -1
return false
}
//不满足后移条件时将下标设为尾元素下标并返回false
i.index = len((*i.data)) - 1
return false
}
```
##### HasPre
以原有的迭代器做接受器判断该迭代器是否可以进行前移可以则返回true否则返回false。
```go
func (i *Iterator) End() (I *Iterator) {
if i == nil {
//迭代器为空,直接返回
return nil
}
if len((*i.data)) == 0 {
//元素集合为空,下标设为-1
i.index = -1
} else {
//元素集合非空,下标设为最后一个元素的下标
i.index = len((*i.data)) - 1
}
//返回修改后的该指针
return &Iterator{
data: i.data,
index: i.index,
}
}
```
##### Pre
以原有的迭代器做接受器将迭代器下标前移当满足前移条件时进行前移同时返回true当不满足前移条件时将下标设为首元素下标同时返回false当迭代器为nil时返回false当元素集合为空时下标设为-1同时返回false。
```go
func (i *Iterator) Pre() (b bool) {
if i == nil {
//迭代器为nil时返回false
return false
}
if i.HasPre() {
//满足后移条件时进行前移
i.index--
return true
}
if len((*i.data)) == 0 {
//元素集合为空时下标设为-1同时返回false
i.index = -1
return false
}
//不满足后移条件时将下标设为尾元素下标并返回false
i.index = 0
return false
}
```
#### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/utils/iterator"
)
func main() {
var arr =make([]interface{},0,0)
arr=append(arr,5)
arr=append(arr,3)
arr=append(arr,2)
arr=append(arr,4)
arr=append(arr,1)
arr=append(arr,4)
arr=append(arr,3)
arr=append(arr,1)
arr=append(arr,5)
arr=append(arr,2)
i:=Iterator.New(&arr)
fmt.Println("begin")
for i:=i.Begin();i.HasNext();i.Next(){
fmt.Println(i.Value())
}
fmt.Println()
fmt.Println("end")
for i:=i.End();i.HasPre();i.Pre(){
fmt.Println(i.Value())
}
fmt.Println()
fmt.Println("get4")
for i:=i.Get(4);i.HasNext();i.Next(){
fmt.Println(i.Value())
}
fmt.Println()
}
```
##### 示例结果
> begin
> 5
> 3
> 2
> 4
> 1
> 4
> 3
> 1
> 5
> 2
>
> end
> 2
> 5
> 1
> 3
> 4
> 1
> 4
> 2
> 3
> 5
>
> get4
> 1
> 4
> 3
> 1
> 5
> 2

View File

@ -0,0 +1,611 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
链表list是一种**离散的线性结构**,它是又多个分布在不同物理空间的结点,通过指针链接建立逻辑连接而形成的线性结构。
由于它的一个个结点相互之间是分离开的,所以它增加和删除结点的过程就会变得十分简单,只需要找到对应节点并将其增加/删除即可,同时修改该结点前后结点的指针以保证整个链表不断开即可,对整个链表的大多数元素来说几乎没有影响,适用于频繁增删的情况,但它也会频繁的分配空间。
### 原理
对于一个线性结构来说,主要是要建立其它的连接,相比较于用数组的形式实现来说,数组实现需要分配一段连续的空间用以存储数据,但当数组分配的空间不足时,则需要再重新分配空间并将原有的元素复制过去,考虑到复制数据的时间开销也是不容忽略的,所以需要找到另一个结构去尽量减少复制元素的时间开销。
对此,可以选择使用链表,它是**将多个分离的点通过指针连接起来**,对于增删元素都只需要修改对应的结点和它前后连接的结点即可,对于链表中存储的其他结点不需要做修改,即可以极大的省去频繁复制元素的时间开销,但相应的,由于每个结点是离散的,每次增删结点都需要分配空间,增加了分配空间的时间开销。
链表和数组两种数据结构各有千秋,当在需要承载的元素过多的时候,可以选择使用链表以减少频繁复制的时间开销,当元素不多时可以选择之间分配一个较大的数组去存储以减少分配空间的时间开销。
#### 添加策略
对于链表来说,由于它的结点之间的物理间隔并不是连续的,而是根据需要随机的分布在整个内存中的,同时,对于一个链表来说,核心的主要是它的首尾结点,可以通过首尾结点进行元素的添加,添加时需要先遍历到对应的位置再进行添加,可以根据位置和总承载元素的情况选择从前或从后进行遍历以减少时间开销。
list添加主要会有以下四种情况
1. 链表不存在:创建新的结点设为头尾结点即可
2. 添加在首部:从头结点添加并修改链表的头结点即可
3. 添加在尾部:从尾结点添加并修改链表的尾结点即可
4. 添加的中部:从最近的首尾结点开始遍历找到对应位置后插入即可,同时要**修改前后结点之间的指针以指向自身**,保持链表的连续性。
#### 删除策略
同添加策略一样,链表的删除特性和添加是类似的,只需要删除一个结点即可,同时修改前后连接结点的指针以保证链表的连续性,当删除的是头尾结点时在修改记录的头尾结点即可。当所有元素删除完毕后,需要**销毁链表**。
list删除主要会有以下五种情况
1. 链表不存在:结束
2. 删除首结点:将首节点设为首节点的下一结点,同时销毁首结点
3. 删除尾结点:将尾节点设为尾节点的上一结点,同时销毁尾结点
4. 删除中间结点:从最近的首尾结点开始遍历找到对应位置后删除即可,同时**将前后结点连接起来**,以保证链表的连续性。
5. 删除后链表中不存在元素:销毁链表
### 实现
list链表结构体包含链表的头尾节点指针当增删结点时只需要找到对应位置进行操作即可当一个节点进行增删时需要同步修改其临接结点的前后指针结构体中记录整个链表的首尾指针,同时记录其当前已承载的元素,使用并发控制锁以保证数据一致性。
```go
type list struct {
first *node //链表首节点指针
last *node //链表尾节点指针
size uint64 //当前存储的元素个数
mutex sync.Mutex //并发控制锁
}
```
链表的node节点结构体pre和next是该节点的前后两个节点的指针用以保证链表整体是相连的。
```go
type node struct {
data interface{} //结点所承载的元素
pre *node //前结点指针
next *node //后结点指针
}
```
#### 接口
```go
type lister interface {
Iterator() (i *Iterator.Iterator) //创建一个包含链表中所有元素的迭代器并返回其指针
Sort(Cmp ...comparator.Comparator) //将链表中所承载的所有元素进行排序
Size() (size uint64) //返回链表所承载的元素个数
Clear() //清空该链表
Empty() (b bool) //判断该链表是否位空
Insert(idx uint64, e interface{}) //向链表的idx位(下标从0开始)插入元素组e
Erase(idx uint64) //删除第idx位的元素(下标从0开始)
Get(idx uint64) (e interface{}) //获得下标为idx的元素
Set(idx uint64, e interface{}) //在下标为idx的位置上放置元素e
IndexOf(e interface{}, Equ ...comparator.Equaler) (idx uint64) //返回和元素e相同的第一个下标
SubList(begin, num uint64) (newList *list) //从begin开始复制最多num个元素以形成新的链表
}
```
```go
type noder interface {
preNode() (m *node) //返回前结点指针
nextNode() (m *node) //返回后结点指针
insertPre(pre *node) //在该结点前插入结点并建立连接
insertNext(next *node) //在该结点后插入结点并建立连接
erase() //删除该结点,并使该结点前后两结点建立连接
value() (e interface{}) //返回该结点所承载的元素
setValue(e interface{}) //修改该结点承载元素为e
}
```
#### New
新建一个list链表容器并返回初始链表首尾节点为nil初始size为0。
```go
func New() (l *list) {
return &list{
first: nil,
last: nil,
size: 0,
mutex: sync.Mutex{},
}
}
```
新建一个结点并返回其指针, 初始首结点的前后结点指针都为nil。
```go
func newNode(e interface{}) (n *node) {
return &node{
data: e,
pre: nil,
next: nil,
}
}
```
#### Iterator
以list链表容器做接收者将list链表容器中所承载的元素放入迭代器中。
```go
func (l *list) Iterator() (i *Iterator.Iterator) {
if l == nil {
l = New()
}
l.mutex.Lock()
//将所有元素复制出来放入迭代器中
tmp := make([]interface{}, l.size, l.size)
for n, idx := l.first, uint64(0); n != nil && idx < l.size; n, idx = n.nextNode(), idx+1 {
tmp[idx] = n.value()
}
i = Iterator.New(&tmp)
l.mutex.Unlock()
return i
}
```
以node结点做接收者返回该结点的前结点。
```go
func (n *node) preNode() (pre *node) {
if n == nil {
return
}
return n.pre
}
```
以node结点做接收者返回该结点的后结点。
```go
func (n *node) nextNode() (next *node) {
if n == nil {
return
}
return n.next
}
```
#### Sort
以list链表容器做接收者将list链表容器中所承载的元素利用比较器进行排序可以自行传入比较函数,否则将调用默认比较函数。
```go
func (l *list) Sort(Cmp ...comparator.Comparator) {
if l == nil {
l = New()
}
l.mutex.Lock()
//将所有元素复制出来用于排序
tmp := make([]interface{}, l.size, l.size)
for n, idx := l.first, uint64(0); n != nil && idx < l.size; n, idx = n.nextNode(), idx+1 {
tmp[idx] = n.value()
}
if len(Cmp) > 0 {
comparator.Sort(&tmp, Cmp[0])
} else {
comparator.Sort(&tmp)
}
//将排序结果再放入链表中
for n, idx := l.first, uint64(0); n != nil && idx < l.size; n, idx = n.nextNode(), idx+1 {
n.setValue(tmp[idx])
}
l.mutex.Unlock()
}
```
#### Size
以list链表容器做接收者返回该容器当前含有元素的数量。
```go
func (l *list) Size() (size uint64) {
if l == nil {
l = New()
}
return l.size
}
```
#### Clear
以list链表容器做接收者将该容器中所承载的元素清空将该容器的首尾指针均置nil,将size重置为0。
```go
func (l *list) Clear() {
if l == nil {
l = New()
}
l.mutex.Lock()
//销毁链表
l.first = nil
l.last = nil
l.size = 0
l.mutex.Unlock()
}
```
#### Empty
以list链表容器做接收者判断该list链表容器是否含有元素如果含有元素则不为空,返回false如果不含有元素则说明为空,返回true如果容器不存在,返回true该判断过程通过size进行判断,为0则为true,否则为false。
```go
func (l *list) Empty() (b bool) {
if l == nil {
l = New()
}
return l.size == 0
}
```
#### Insert
以list链表容器做接收者通过链表的首尾结点进行元素插入插入的元素可以有很多个通过判断idx。
```go
func (l *list) Insert(idx uint64, e interface{}) {
if l == nil {
l = New()
}
l.mutex.Lock()
n := newNode(e)
if l.size == 0 {
//链表中原本无元素,新建链表
l.first = n
l.last = n
} else {
//链表中存在元素
if idx == 0 {
//插入头节点
n.insertNext(l.first)
l.first = n
} else if idx >= l.size {
//插入尾节点
l.last.insertNext(n)
l.last = n
} else {
//插入中间节点
//根据插入的位置选择从前或从后寻找
if idx < l.size/2 {
//从首节点开始遍历寻找
m := l.first
for i := uint64(0); i < idx-1; i++ {
m = m.nextNode()
}
m.insertNext(n)
} else {
//从尾节点开始遍历寻找
m := l.last
for i := l.size - 1; i > idx; i-- {
m = m.preNode()
}
m.insertPre(n)
}
}
}
l.size++
l.mutex.Unlock()
}
```
以node结点做接收者对该结点插入前结点并建立前结点和该结点之间的连接。
```go
func (n *node) insertPre(pre *node) {
if n == nil || pre == nil {
return
}
pre.next = n
pre.pre = n.pre
if n.pre != nil {
n.pre.next = pre
}
n.pre = pre
}
```
以node结点做接收者对该结点插入后结点并建立后结点和该结点之间的连接。
```go
func (n *node) insertNext(next *node) {
if n == nil || next == nil {
return
}
next.pre = n
next.next = n.next
if n.next != nil {
n.next.pre = next
}
n.next = next
}
```
#### Erase
以list链表容器做接收者先判断是否为首尾结点,如果是首尾结点,在删除后将设置新的首尾结点当链表所承载的元素全部删除后则销毁链表删除时通过idx与总元素数量选择从前或从后进行遍历以找到对应位置删除后,将该位置的前后结点连接起来,以保证链表不断裂。
```go
func (l *list) Erase(idx uint64) {
if l == nil {
l = New()
}
l.mutex.Lock()
if l.size > 0 && idx < l.size {
//链表中存在元素,且要删除的点在范围内
if idx == 0 {
//删除头节点
l.first = l.first.next
} else if idx == l.size-1 {
//删除尾节点
l.last = l.last.pre
} else {
//删除中间节点
//根据删除的位置选择从前或从后寻找
if idx < l.size/2 {
//从首节点开始遍历寻找
m := l.first
for i := uint64(0); i < idx; i++ {
m = m.nextNode()
}
m.erase()
} else {
//从尾节点开始遍历寻找
m := l.last
for i := l.size - 1; i > idx; i-- {
m = m.preNode()
}
m.erase()
}
}
l.size--
if l.size == 0 {
//所有节点都被删除,销毁链表
l.first = nil
l.last = nil
}
}
l.mutex.Unlock()
}
```
以node结点做接收者销毁该结点同时建立该节点前后节点之间的连接。
```go
func (n *node) erase() {
if n == nil {
return
}
if n.pre == nil && n.next == nil {
return
} else if n.pre == nil {
n.next.pre = nil
} else if n.next == nil {
n.pre.next = nil
} else {
n.pre.next = n.next
n.next.pre = n.pre
}
n = nil
}
```
#### Get
以list链表容器做接收者获取第idx位结点所承载的元素,若不在链表范围内则返回nil
```go
func (l *list) Get(idx uint64) (e interface{}) {
if l == nil {
l = New()
}
if idx >= l.size {
return nil
}
l.mutex.Lock()
if idx < l.size/2 {
//从首节点开始遍历寻找
m := l.first
for i := uint64(0); i < idx; i++ {
m = m.nextNode()
}
e = m.value()
} else {
//从尾节点开始遍历寻找
m := l.last
for i := l.size - 1; i > idx; i-- {
m = m.preNode()
}
e = m.value()
}
l.mutex.Unlock()
return e
}
```
以node结点做接收者返回该结点所要承载的元素。
```go
func (n *node) value() (e interface{}) {
if n == nil {
return nil
}
return n.data
}
```
#### Set
以list链表容器做接收者修改第idx为结点所承载的元素,超出范围则不修改。
```go
func (l *list) Set(idx uint64, e interface{}) {
if l == nil {
l = New()
}
if idx >= l.size {
return
}
l.mutex.Lock()
if idx < l.size/2 {
//从首节点开始遍历寻找
m := l.first
for i := uint64(0); i < idx; i++ {
m = m.nextNode()
}
m.setValue(e)
} else {
//从尾节点开始遍历寻找
m := l.last
for i := l.size - 1; i > idx; i-- {
m = m.preNode()
}
m.setValue(e)
}
l.mutex.Unlock()
}
```
以node结点做接收者对该结点设置其承载的元素。
```go
func (n *node) setValue(e interface{}) {
if n == nil {
return
}
n.data = e
}
```
#### IndexOf
以list链表容器做接收者返回与e相同的元素的首个位置可以自行传入用于判断相等的相等器进行处理遍历从头至尾,如果不存在则返回l.size。
```go
func (l *list) IndexOf(e interface{}, Equ ...comparator.Equaler) (idx uint64) {
if l == nil {
l = New()
}
l.mutex.Lock()
var equ comparator.Equaler
if len(Equ) > 0 {
equ = Equ[0]
} else {
equ = comparator.GetEqual()
}
n := l.first
//从头寻找直到找到相等的两个元素即可返回
for idx = 0; idx < l.size && n != nil; idx++ {
if equ(n.value(), e) {
break
}
n = n.nextNode()
}
l.mutex.Unlock()
return idx
}
```
#### SubList
以list链表容器做接收者以begin为起点(包含),最多复制num个元素进入新链表并返回**新链表指针**。
```go
func (l *list) SubList(begin, num uint64) (newList *list) {
if l == nil {
l = New()
}
newList = New()
l.mutex.Lock()
if begin < l.size {
//起点在范围内,可以复制
n := l.first
for i := uint64(0); i < begin; i++ {
n = n.nextNode()
}
m := newNode(n.value())
newList.first = m
newList.size++
for i := uint64(0); i < num-1 && i+begin < l.size-1; i++ {
n = n.nextNode()
m.insertNext(newNode(n.value()))
m = m.nextNode()
newList.size++
}
newList.last = m
}
l.mutex.Unlock()
return newList
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/list"
"sync"
)
func main() {
l := list.New()
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx uint64) {
l.Insert(idx, idx)
wg.Done()
}(uint64(i))
}
wg.Wait()
fmt.Println("使用迭代器访问全部元素;")
for i := l.Iterator(); i.HasNext(); i.Next() {
fmt.Println(i.Value())
}
l.Set(5, "测试")
fmt.Println("输出刚设定的测试元素的位置:", l.IndexOf("测试"))
fmt.Println("从测试位生产新list长度上限为10并从头部输出")
newList := l.SubList(l.IndexOf("测试"), 10)
len := newList.Size()
fmt.Println("新链表的长度:", len)
for i := uint64(0); i < len; i++ {
fmt.Println(newList.Get(0))
newList.Erase(0)
}
fmt.Println("从结尾向首部输出原链表:")
for i := l.Size(); i > 0; i-- {
fmt.Println(l.Get(i - 1))
l.Erase(i - 1)
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 使用迭代器访问全部元素;
> 0
> 1
> 2
> 3
> 4
> 5
> 6
> 7
> 9
> 8
> 输出刚设定的测试元素的位置: 5
> 从测试位生产新list长度上限为10并从头部输出
> 新链表的长度: 5
> 测试
> 6
> 7
> 9
> 8
> 从结尾向首部输出原链表:
> 8
> 9
> 7
> 6
> 测试
> 4
> 3
> 2
> 1
> 0

View File

@ -0,0 +1,327 @@
github仓库存储地址https://github.com/hlccd/goSTL
### 概述
队列queue是一个封装了动态大小数组的顺序容器。除了可以包含任意类型的元素外更主要是的它满足**FIFO**的先进先出模式,对于一些排队问题可以考虑使用队列来存储。
对于queue的实现,由于它也是一个线性容器底层依然可以考虑使用动态数组来实现但它和vector仍有一定的不同vector的冗余量主要是在尾部毕竟vector要实现随机读取的话中间和首部不能有空余量而对于queue来说它的添加只在尾部而首部仅仅只做删除所以除了在尾部留有一定的空间做添加之外也可以在首部删除后留有不多的余量以**避免多次分配空间**。
### 原理
对于一个queue来说它可以**在尾部添加元素****在首部删除元素**。而同vector一样如果每次增删都要重新分配空间将会极大的降低效率所以可以考虑对前后都留有一定的冗余以此来减少分配空间和复制的次数从而减少时间开销。
对于首部的冗余来说,它的存在主要是为了减少删除后就重新分配空间并复制,同时对于尾部添加时,如果尾部冗余不足可以将使用的元素整体前移到首部,即将首部冗余挪到尾部去,从而减少了空间分配的次数。
对于尾部的冗余来说它存在的目的和vector类似仅仅是为了后续元素时更快而已。
和vector一样都是通过设置一定量的冗余来换取操作次数的减少从而提高效率减少时间损耗。
#### 扩容策略
对于queue的动态数组扩容来说由于其添加是线性且连续的即每一次只会增加一个并且每一次都只会在它的尾部不像vector一样可能在数组的任意位置进行添加同时由于首部也有冗余所以它的扩容也需要考虑首部的情况。
queue的扩容主要由三种方案
1. 利用首部:**严格来说其实并没有进行扩容**,而是将承载的元素前移到首部,即将首部的冗余平移到最后进行利用。
2. 固定扩容:固定的去增加一定量的空间,该方案时候在数组较大时添加空间,可以避免直接翻倍导致的**冗余过量**问题到由于增加量是固定的如果需要一次扩容很多量的话就会比较缓慢。同vector
3. 翻倍扩容将原有容量直接翻倍该方案适合在数组不太大的适合添加空间可以提高扩容量增加扩容效率但数组太大时使用的话会导致一次性增加太多空间进而造成空间的浪费。同vector
#### 缩容策略
对于queue的数组来说它的缩容不同于扩容由于删除元素只会在首部进行所以缩容其实也只会在首部进行考虑到首部并不会进行添加所以也不需要冗余太多的量进行减缓即可以将设定上限减少一些如(2^10)
1. 固定缩容:释放一个固定的空间,该方案适合在当前数组较大的时候进行,可以减缓需要缩小的量,当首部冗余超过上限时进行缩容,**一次性全部释放即可**。
2. 折半缩容:当**首部冗余超过了实际承载的元素的数量**时,需要对首部进行缩容,同前者一样,不需要考虑首部添加的问题,即采用对首部冗余全部释放的方式。
### 实现
queue底层同样使用动态数组实现同时由于首部只做删除尾部只做添加而首尾两侧都有一定的冗余所以需要对两侧都进行记录也可以根据尾部下标减首部下标得出实际承载元素的量与此同时需要引入cap以记录实际分配的空间大小也可以根据cap和end计算出尾部冗余量。同时为了解决在高并发情况下的数据不一致问题引入了并发控制锁。
```go
type queue struct {
data []interface{} //泛型切片
begin uint64 //首节点下标
end uint64 //尾节点下标
cap uint64 //容量
mutex sync.Mutex //并发控制锁
}
```
#### 接口
```go
type queuer interface {
Iterator() (i *Iterator.Iterator) //返回包含队列中所有元素的迭代器
Size() (num uint64) //返回该队列中元素的使用空间大小
Clear() //清空该队列
Empty() (b bool) //判断该队列是否为空
Push(e interface{}) //将元素e添加到该队列末尾
Pop() (e interface{}) //将该队列首元素弹出并返回
Front() (e interface{}) //获取该队列首元素
Back() (e interface{}) //获取该队列尾元素
}
```
#### New
创建一个queue容器并初始化同时返回其指针。
```go
func New() (q *queue) {
return &queue{
data: make([]interface{}, 1, 1),
begin: 0,
end: 0,
cap: 1,
mutex: sync.Mutex{},
}
}
```
#### Iterator
将队列中承载元素传入迭代器中,不清除队列中的冗余量。返回迭代器指针,用于遍历队列。
```go
func (q *queue) Iterator() (i *Iterator.Iterator) {
if q == nil {
q=New()
}
q.mutex.Lock()
tmp:=make([]interface{},q.end-q.begin,q.end-q.begin)
copy(tmp, q.data[q.begin:q.end])
i = Iterator.New(&tmp)
q.mutex.Unlock()
return i
}
```
#### Size
返回queue中当前所包含的元素个数由于其数值必然是非负整数所以选用了**uint64**。
```go
func (q *queue) Size() (num uint64) {
if q == nil {
q = New()
}
return q.end - q.begin
}
```
#### Clear
清空了queue中所承载的所有元素。
```go
func (q *queue) Clear() {
if q == nil {
q = New()
}
q.mutex.Lock()
q.data = make([]interface{}, 1, 1)
q.begin = 0
q.end = 0
q.cap = 1
q.mutex.Unlock()
}
```
#### Empty
判断queue中是否为空通过Size()是否为0进行判断。
```go
func (q *queue) Empty() (b bool) {
if q == nil {
q = New()
}
return q.Size() <= 0
}
```
#### Push
以queue为接受器向queue尾部添加一个元素添加元素会出现两种情况第三种是还有冗余量此时**直接覆盖**以len为下标指向的位置即可另一种情况是没有冗余量了需要对动态数组进行**扩容**,此时就需要利用扩容策略。另一种是尾部没有冗余量,但首部仍有冗余量,此时可以将承载元素前移,把首部冗余量”借“过来使用。
扩容策略上文已做描述,可返回参考,该实现过程种将两者进行了结合使用,可参考下方注释。
固定扩容值设为2^16翻倍扩容上限也为2^16。
```go
func (q *queue) Push(e interface{}) {
if q == nil {
q = New()
}
q.mutex.Lock()
if q.end < q.cap {
//不需要扩容
q.data[q.end] = e
} else {
//需要扩容
if q.begin > 0 {
//首部有冗余,整体前移
for i := uint64(0); i < q.end-q.begin; i++ {
q.data[i] = q.data[i+q.begin]
}
q.end -= q.begin
q.begin = 0
} else {
//冗余不足,需要扩容
if q.cap <= 65536 {
//容量翻倍
if q.cap == 0 {
q.cap = 1
}
q.cap *= 2
} else {
//容量增加2^16
q.cap += 2 ^ 16
}
//复制扩容前的元素
tmp := make([]interface{}, q.cap, q.cap)
copy(tmp, q.data)
q.data = tmp
}
q.data[q.end] = e
}
q.end++
q.mutex.Unlock()
}
```
#### Pop
以queue队列容器做接收者弹出容器首部元素,同时begin++即可,若容器为空,则不进行弹出,当弹出元素后,可能进行缩容由于首部不会进行添加所以不需要太多的冗余即将首部冗余上限设为2^10固定缩容和折半缩容都参考此值执行具体缩容策略可参考上文介绍。
```go
func (q *queue) Pop() (e interface{}) {
if q == nil {
q = New()
return nil
}
if q.Empty() {
q.Clear()
return nil
}
q.mutex.Lock()
e = q.data[q.begin]
q.begin++
if q.begin >= 1024 || q.begin*2>q.end {
//首部冗余超过2^10或首部冗余超过实际使用
q.cap -= q.begin
q.end -= q.begin
tmp := make([]interface{}, q.cap, q.cap)
copy(tmp, q.data[q.begin:])
q.data = tmp
q.begin=0
}
q.mutex.Unlock()
return e
}
```
#### Front
以queue为接受器返回vector所承载的元素中位于**首部**的元素如果queue为nil或者元素数组为nil或为空则返回nil。考虑到仅仅只是读取元素故不对该过程进行加锁操作。
```go
func (q *queue) Front() (e interface{}) {
if q == nil {
q=New()
return nil
}
if q.Empty() {
q.Clear()
return nil
}
return q.data[q.begin]
}
```
#### Back
以queue为接受器返回queue所承载的元素中位于**尾部**的元素如果queue为nil或者元素数组为nil或为空则返回nil。考虑到仅仅只是读取元素故不对该过程进行加锁操作。
```go
func (q *queue) Back() (e interface{}) {
if q == nil {
q=New()
return nil
}
if q.Empty() {
q.Clear()
return nil
}
return q.data[q.end-1]
}
```
### 使用示例
```go
package main
import (
"fmt"
"github.com/hlccd/goSTL/data_structure/queue"
"sync"
)
func main() {
q := queue.New()
wg := sync.WaitGroup{}
//随机插入队列中
for i := 0; i < 8; i++ {
wg.Add(1)
go func(num int) {
fmt.Println(num)
q.Push(num)
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("输出首部:", q.Front())
fmt.Println("输出尾部:", q.Back())
fmt.Println("弹出并输出前4个:")
for i := uint64(0); i < q.Size()-1; i++ {
fmt.Println(q.Pop())
}
//在尾部再添加4个,从10开始以做区分
for i := 10; i < 14; i++ {
q.Push(i)
}
fmt.Println("从头输出全部:")
for ;!q.Empty();{
fmt.Println(q.Pop())
}
}
```
注:由于过程中的增删过程是并发执行的,所以其结果和下方示例并不完全相同
> 0
> 7
> 1
> 2
> 3
> 4
> 5
> 6
> 输出首部: 0
> 输出尾部: 6
> 弹出并输出前4个:
> 0
> 7
> 1
> 2
> 从头输出全部:
> 3
> 4
> 5
> 6
> 10
> 11
> 12
> 13