github.com/chenjiandongx/go-queue@v0.0.0-20191023082232-e2a36f382f84/README.md (about) 1 # 📂 collctions 2 3 > Golang 实现的 collections 模块,灵感来自 [Python queue](https://docs.python.org/3/library/queue.html) 和 [Python collections](https://docs.python.org/3/library/collections.html) 4 5 [](https://travis-ci.org/chenjiandongx/collections) [](https://ci.appveyor.com/project/chenjiandongx/collections) [](https://goreportcard.com/report/github.com/chenjiandongx/collections) [](https://opensource.org/licenses/MIT) [](https://godoc.org/github.com/chenjiandongx/collections) 6 7 ## 📚 目录 8 9 * [Queue - 先进先出队列](#Queue) 10 * [LifoQueue - 后进先出队列](#LifoQueue) 11 * [PriorityQueue - 优先队列](#PriorityQueue) 12 * [Deque - 双端队列](#Deque) 13 * [OrderedMap - 有序 Map](#OrderedMap) 14 * [Counter - 计数器](#Counter) 15 * [AVLTree - AVL 树](#AVLTree) 16 * [Sort - 排序](#Sort) 17 18 ### 🔰 安装&引用 19 20 ```bash 21 $ go get github.com/chenjiandongx/collections 22 23 import "github.com/chenjiandongx/collections" 24 ``` 25 26 ### 📦 Collections 27 28 ### Queue 29 > 先进先出队列(线程安全) 30 31 📝 方法集 32 ```shell 33 Get()(interface{}, bool) // 出队 34 Put(v interface{}) // 入队 35 Qsize() int // 返回队列长度 36 IsEmpty() bool // 判断队列是否为空 37 ``` 38 39 ✏️ 示例 40 ```go 41 var nums = 1000 42 43 q := collections.NewQueue() 44 var item interface{} 45 var ok bool 46 for i := 0; i < nums; i++ { 47 q.Put(i) 48 } 49 for i := 0; i < nums; i++ { 50 if item, ok = q.Get(); ok { 51 fmt.Println(item.(int)) 52 } 53 } 54 55 fmt.Println(q.IsEmpty()) 56 fmt.Println(q.Qsize()) 57 ``` 58 59 ### LifoQueue 60 > 后进先出队列(线程安全) 61 62 📝 方法集 63 ```shell 64 Get()(interface{}, bool) // 出队 65 Put(v interface{}) // 入队 66 Qsize() int // 返回队列长度 67 IsEmpty() bool // 判断队列是否为空 68 ``` 69 70 ✏️ 示例 71 ```go 72 var nums = 1000 73 74 q := collections.NewLifoQueue() 75 var item interface{} 76 var ok bool 77 for i := 0; i < nums; i++ { 78 q.Put(i) 79 } 80 for i := nums-1; i >=0; i-- { 81 if item, ok = q.Get(); ok { 82 fmt.Println(item.(int)) 83 } 84 } 85 86 fmt.Println(q.IsEmpty()) 87 fmt.Println(q.Qsize()) 88 ``` 89 90 ### PriorityQueue 91 > 优先队列(线程安全) 92 93 📝 方法集 94 ```shell 95 Get()(interface{}, bool) // 出队 96 Put(v *PqNode) // 入队 97 Qsize() int // 返回队列长度 98 IsEmpty() bool // 判断队列是否为空 99 100 // 优先队列节点 101 type PqNode struct { 102 Value string 103 Priority, index int 104 } 105 ``` 106 107 ✏️ 示例 108 ```go 109 var nums = 1000 110 111 q := collections.NewPriorityQueue() 112 113 for i := 0; i < nums; i++ { 114 r := rand.Int() 115 q.Put(&collections.PqNode{Value: string(r), Priority: rand.Int()}) 116 } 117 118 for i := 0; i < nums/2; i++ { 119 item1, _ := q.Get() 120 item2, _ := q.Get() 121 fmt.Println(item1.(*collections.PqNode).Priority > item2.(*collections.PqNode).Priority) 122 } 123 124 fmt.Println(q.IsEmpty()) 125 fmt.Println(q.Qsize()) 126 ``` 127 128 ### Deque 129 > 双端队列(线程安全) 130 131 📝 方法集 132 ```shell 133 GetLeft()(interface{}, bool) // 左边出队 134 GetRight()(interface{}, bool) // 右边出队 135 PutLeft(v interface{}) // 左边入队 136 PutRight(v interface{}) // 右边入队 137 Qsize() int // 返回队列长度 138 IsEmpty() bool // 判断队列是否为空 139 ``` 140 141 ✏️ 示例 142 ```go 143 var nums = 1000 144 q := collections.NewDeque() 145 146 var item interface{} 147 var ok bool 148 149 for i := 0; i < nums; i++ { 150 q.PutLeft(i) 151 } 152 fmt.Println(q.Qsize()) 153 154 for i := nums - 1; i >= 0; i-- { 155 q.PutRight(i) 156 } 157 fmt.Println(q.Qsize()) 158 159 for i := 0; i < nums; i++ { 160 item, ok = q.GetRight() 161 fmt.Println(item, ok) 162 } 163 for i := nums - 1; i >= 0; i-- { 164 item, ok = q.GetLeft() 165 fmt.Println(item, ok) 166 } 167 168 item, ok = q.GetLeft() 169 fmt.Println(item, ok) 170 171 item, ok = q.GetRight() 172 fmt.Println(item, ok) 173 ``` 174 175 ### OrderedMap 176 > 有序 Map,接口设计参考 [cevaris/ordered_map](https://github.com/cevaris/ordered_map) 177 178 📝 方法集 179 ```shell 180 Set(key, value interface{}) // 新增键值对 181 Get(key interface{}) (interface{}, bool) // 取值 182 Delete(key interface{}) bool // 删除键 183 Iter() (interface{}, interface{}, bool) // 遍历 184 Len() int // 键值对数量 185 // 指针回退到 Head,遍历时 current 指针会向后移动 BackToHead 使其移动到头指针,以便下一次从头遍历 186 BackToHead() 187 ``` 188 189 ✏️ 示例 190 ```go 191 maxNum := 100 192 om := collections.NewOrderedMap() 193 for i := 0; i < maxNum; i++ { 194 om.Set(i, i+1) 195 } 196 197 fmt.Println(om.Len()) 198 om.Delete(0) 199 fmt.Println(om.Len()) 200 201 for k, v, ok := om.Iter(); ok; k, v, ok = om.Iter() { 202 fmt.Println(k, v) 203 } 204 205 om.BackToHead() 206 for k, v, ok := om.Iter(); ok; k, v, ok = om.Iter() { 207 fmt.Println(k, v) 208 } 209 ``` 210 211 📣 讨论 212 213 有序 Map 在 Golang 中应该是十分常见的需求,Map 最大的优势就是其查找性能,**理论上** Map 查找的时间复杂度为常数级。但实际情况我们可以通过 benchmark 来验证。在 [Go Maps Don’t Appear to be O(1)](https://medium.com/@ConnorPeet/go-maps-are-not-o-1-91c1e61110bf) 这篇文章中,作者测试了 Golang Map 查找的实际性能,不过作者是基于 Go1.4 的,版本有点旧了。下面是我修改了作者的测试案例后在 Go1.10 下跑出来的结果。 214 215  216 217 上图是使用 [go-echarts](https://github.com/go-echarts/go-echarts) 绘制的。测试通过与二分查找对比,二分查找的时间复杂度为 **O(log2n)**。很明显,在 10e5 数量级下两者的性能差别还不是特别大,主要差距是在 10e6 后体现的。结论:Map 的性能优于 **O(log2n)**,但不是常数级。 218 219 **collections.OrderdMap 🆚 cevaris/ordered_map** 220 221 本来我一直使用的是 [cevaris/ordered_map](https://github.com/cevaris/ordered_map),后来自己重新实现了一个。实现完就与其进行了性能测试对比,它是基于两个 Map 实现的,而我是使用的 Map+LinkedList,LinkedList 在删除和插入操作上的时间复杂度都是 **O(1)**,用其来存储 Map key 的顺序是一个很好的选择。 222 223 同样的测试代码,BenchMark 结果如下 224 ```shell 225 goos: windows 226 goarch: amd64 227 pkg: github.com/chenjiandongx/collections 228 BenchmarkCollectionsSet-8 2000000 689 ns/op 187 B/op 3 allocs/op 229 BenchmarkCevarisSet-8 1000000 1212 ns/op 334 B/op 3 allocs/op 230 BenchmarkCollectionsGet-8 2000000 823 ns/op 187 B/op 3 allocs/op 231 BenchmarkCevarisGet-8 1000000 1281 ns/op 334 B/op 3 allocs/op 232 BenchmarkCollectionsIter-8 2000000 670 ns/op 187 B/op 3 allocs/op 233 BenchmarkCevarisIter-8 1000000 1341 ns/op 366 B/op 4 allocs/op 234 ``` 235 **collections.OrderedMap Win 🖖 性能+内存占用全部占优 🚀** 236 237 ### Counter 238 > 计数器 239 240 📝 方法集 241 ```shell 242 // key-value item 243 type Item struct { 244 k interface{} 245 v int 246 } 247 248 Add(keys ...interface{}) // 新增 item 249 Get(key interface{}) int // 获取 key 计数 250 GetAll() []Item // 获取全部 key 计数 251 Top(n int) []Item // 获取前 key 计数 252 Delete(key interface{}) bool // 删除 key,成功返回 true,key 不存在返回 false 253 Len() int // key 数量 254 ``` 255 256 ✏️ 示例 257 ```go 258 c := collections.NewCounter() 259 c.Add("a", "b", "c", "d", "a", "c") 260 fmt.Println(c.Get("A")) 261 fmt.Println(c.Get("a")) 262 fmt.Println(c.Get("b")) 263 fmt.Println(c.Top(2)) 264 fmt.Println(c.Len()) 265 fmt.Println(c.All()) 266 c.Delete("a") 267 ``` 268 269 ### AVLTree 270 > AVL 二叉自平衡查找树 271 272 📝 方法集 273 ```shell 274 NewAVLTree() *AVLTree // 生成 AVL 树 275 Insert(v int) // 插入节点 276 Search(v int) bool // 搜索节点 277 Delete(v int) bool // 删除节点 278 GetMaxValue() int // 获取所有节点中的最大值 279 GetMinValue() int // 获取所有节点中的最小值 280 AllValues() []int // 返回排序后所有值 281 ``` 282 283 ✏️ 示例 284 ```go 285 var maxNum = 100 286 287 tree := NewAVLTree() 288 for i := 0; i < maxNum; i++ { 289 tree.Insert(i) 290 tree.Insert(maxNum + i) 291 } 292 fmt.Println(len(tree.AllValues())) 293 fmt.Println(tree.GetMaxValue()) 294 fmt.Println(tree.GetMinValue()) 295 fmt.Println(tree.Search(50)) 296 fmt.Println(tree.Search(100)) 297 fmt.Println(tree.Search(-10)) 298 fmt.Println(tree.Delete(-10)) 299 fmt.Println(tree.Delete(10)) 300 ``` 301 302 📣 讨论 303 304 AVL 树是自平衡树的一种,其通过左旋和右旋来调整自身的平衡性,使其左右子树的高度差最大不超过 1。AVL 在插入、查找、删除的平时时间复杂度都是 O(logn),在基本的 BST(二叉查找树)中,理想情况的效率也是为 O(logn),但由于操作的性能其实是依赖于树的高度,BST 最坏的情况会导致树退化成链表,此时时间复杂度就变为 O(n),为了解决这个问题,自平衡二叉树应运而生。 305 306 AVL 的主要精髓在于`旋转`,旋转分为 4 种情况,左旋,左旋+右旋,右旋,右旋+左旋。调整树结构后需要重新计算树高。 307 308 **左子树左节点失衡** 309 > 左左情况 直接右旋 310 ```shell 311 x 312 x => 右旋 x 313 x x x 314 ``` 315 316 **左子树右节点失衡** 317 > 左右情况 先左旋后右旋 318 ```shell 319 x x 320 x => 左旋 x => 右旋 x 321 x x x x 322 ``` 323 324 **右子树右节点失衡** 325 > 右右情况 直接左旋 326 ```shell 327 x 328 x => 左旋 x 329 x x x 330 ``` 331 332 **右子树左节点失衡** 333 > 右左情况 先右旋后左旋 334 ```shell 335 x x 336 x => 右旋 x => 左旋 x 337 x x x x 338 ``` 339 340 AVL 主要的性能消耗主要在插入,因为其需要通过旋转来维护树的平衡,但如果使用场景是经常需要排序和查找数据的话,AVL 还是可以展现其良好的性能的。 341 342 **benchmark** 343 ``` 344 BenchmarkAVLInsert10e1-6 2000000000 0.00 ns/op 345 BenchmarkAVLInsert10e2-6 2000000000 0.00 ns/op 346 BenchmarkAVLInsert10e3-6 2000000000 0.00 ns/op 347 BenchmarkAVLInsert10e4-6 2000000000 0.02 ns/op 348 BenchmarkAVLInsert10e5-6 1000000000 0.82 ns/op 349 BenchmarkAVLSearch-6 2000000000 0.00 ns/op 350 BenchmarkAVLDelete-6 2000000000 0.00 ns/op 351 ``` 352 353 ### Sort 354 355 📝 方法集 356 ```shell 357 BubbleSort() // 冒泡排序 358 InsertionSort() // 插入排序 359 QuickSort() // 快速排序 360 ShellSort() // 希尔排序 361 HeapSort() // 堆排序 362 MergeSort() // 归并排序 363 ``` 364 365 ✏️ 示例 366 ```go 367 var maxCnt = 10e4 368 369 func yieldRandomArray() []int { 370 res := make([]int, maxCnt) 371 for i := 0; i < maxCnt; i++ { 372 res[i] = rand.Int() 373 } 374 return res 375 } 376 377 BubbleSort(yieldRandomArray()) 378 InsertionSort(yieldRandomArray()) 379 QuickSort(yieldRandomArray()) 380 ShellSort(yieldRandomArray()) 381 HeapSort(yieldRandomArray()) 382 MergeSort(yieldRandomArray()) 383 ``` 384 385 📣 讨论 386 387 **排序算法时间复杂度比较** 388 389 | 排序算法 | 是否稳定 | 平均 | 最好 | 最差 | 动画演示 | 390 | -------- | --------- |----------| --------| -------- | ----------- | 391 | BubbleSort | 是 | O(n^2) | O(n) | O(n^2) |  | 392 | InsertionSort | 是 | O(n^2) | O(n) | O(n^2) |  | 393 | QuickSort | 否 | O(nlogn) | O(nlogn) | O(n^2) |  | 394 | ShellSort | 否 |O(nlogn) | O(n) | O(n^2) |  | 395 | HeapSort | 否 | O(nlogn) | O(nlogn) | O(nlogn) |  | 396 | MergeSort | 是 | O(nlogn) | O(nlogn) | O(nlogn) |  | 397 398 通过 benchmark 来测试平均排序性能 399 400 **数据随机分布** 401 ```go 402 var maxCnt int = 10e4 403 404 func yieldRandomArray(cnt int) []int { 405 res := make([]int, cnt) 406 for i := 0; i < cnt; i++ { 407 res[i] = rand.Int() 408 } 409 return res 410 } 411 ``` 412 413 运行结果 414 ```shell 415 BenchmarkBubbleSort-8 1 17361549400 ns/op 416 BenchmarkInsertionSort-8 1 1934826900 ns/op 417 BenchmarkQuickSort-8 100 10651807 ns/op 418 BenchmarkShellSort-8 100 16476199 ns/op 419 BenchmarkHeapSort-8 100 14231607 ns/op 420 BenchmarkMergeSort-8 100 14840583 ns/op 421 ``` 422 423 冒泡和直接插入排序在随机数据集的排序性能最差,为 O(n^2),剩余 4 种排序快排效率最佳,其他 3 者性能很接近。 424 425 **换两种极端的数据分布方式** 426 427 **数据升序分布** 428 ```go 429 func yieldArrayAsce(cnt int) []int { 430 res := make([]int, cnt) 431 for i := 0; i < cnt; i++ { 432 res[i] = i 433 } 434 return res 435 } 436 ``` 437 438 运行结果 439 ```shell 440 BenchmarkBubbleSort-8 5000 266690 ns/op 441 BenchmarkInsertionSort-8 10000 213429 ns/op 442 BenchmarkQuickSort-8 1 3291222900 ns/op 443 BenchmarkShellSort-8 1000 1716406 ns/op 444 BenchmarkHeapSort-8 200 6806788 ns/op 445 BenchmarkMergeSort-8 300 4677485 ns/op 446 ``` 447 448 在数据基本升序的情况下,冒泡和直接插入排序能够取得良好的性能。而快排就给跪了,就是最差的 O(n^2) 了。 449 450 **数据降序分布** 451 ```go 452 func yieldArrayDesc(cnt int) []int { 453 res := make([]int, cnt) 454 for i := 0; i < cnt; i++ { 455 res[i] = cnt-i 456 } 457 return res 458 } 459 ``` 460 461 运行结果 462 ```shell 463 BenchmarkBubbleSort-8 1 6710048800 ns/op 464 BenchmarkInsertionSort-8 1 3881599100 ns/op 465 BenchmarkQuickSort-8 1 3373971200 ns/op 466 BenchmarkShellSort-8 500 2876371 ns/op 467 BenchmarkHeapSort-8 200 7081150 ns/op 468 BenchmarkMergeSort-8 300 4448222 ns/op 469 ``` 470 471 在数据基本降序的情况下,冒泡和直接插入排序一如既往的差,快排又给跪了,又是 O(n^2)... 472 473 那自己实现的排序和 Golang 官方提供的 sort.Sort 排序方法对比,效率如何呢 474 475 476 定义一个 struct,实现 sort.Interface 477 ```go 478 import "sort" 479 480 type StdItems struct { 481 data []int 482 } 483 484 func (o StdItems) Less(i, j int) bool { 485 return o.data[i] < o.data[j] 486 } 487 488 func (o StdItems) Swap(i, j int) { 489 o.data[i], o.data[j] = o.data[j], o.data[i] 490 } 491 492 func (o StdItems) Len() int { 493 return len(o.data) 494 } 495 ``` 496 497 只取 n(logn) 复杂度的排序算法与标准 sort 进行对比 498 499 **数据随机分布** 500 ```shell 501 BenchmarkStdSort-8 50 22978524 ns/op 502 BenchmarkQuickSort-8 100 11648689 ns/op 503 BenchmarkShellSort-8 100 17353544 ns/op 504 BenchmarkHeapSort-8 100 14501199 ns/op 505 BenchmarkMergeSort-8 100 13793086 ns/op 506 ``` 507 508 是不是眼前一亮 😂,自己写的快排居然这么厉害,比标准的 sort 快了不止两倍??? 这里出现这种情况的主要原因是 sort 实现了 sort.Interface,该接口需要有三个方法 Less()/Len()/Swap(),而接口的类型转换是有成本的。**通用意味着兼容,兼容意味着妥协,这是专和精权衡后的结果**。当然,标准的 sort 大部分情况的性能都是可接受的。但当你需要追求极致性能的话,自己针对特定需求实现排序算法肯定会是更好的选择。 509 510 **数据升序分布** 511 ```shell 512 BenchmarkStdSort-8 200 7285511 ns/op 513 BenchmarkQuickSort-8 1 3351046900 ns/op 514 BenchmarkShellSort-8 1000 1679506 ns/op 515 BenchmarkHeapSort-8 200 6632256 ns/op 516 BenchmarkMergeSort-8 300 4308582 ns/op 517 ``` 518 519 是不是又是眼前一亮 🤣,我去 为什么这次标准的排序比快排快了这么多,官方的排序不也是快排吗?(这个测试结果看起来好像也没人会比快排慢是吧 😅) 520 521 **数据降序分布** 522 ```shell 523 BenchmarkStdSort-8 200 7405331 ns/op 524 BenchmarkQuickSort-8 1 3390954400 ns/op 525 BenchmarkShellSort-8 500 2900240 ns/op 526 BenchmarkHeapSort-8 200 7091124 ns/op 527 BenchmarkMergeSort-8 300 4295169 ns/op 528 ``` 529 530 emmmmmmm,同上 😓 531 532 关于官方排序的具体实现,可以参考 [src/sort/sort.go](https://golang.org/src/sort/sort.go),实际上是直接插入排序,快速排序,堆排序和归并排序的组合排序。[这篇文章](https://github.com/polaris1119/The-Golang-Standard-Library-by-Example/blob/master/chapter03/03.1.md) 对这部分有介绍 533 534 最后,按官方的排序针对自己想要的数据类型排序,但不使用接口那套,对比上面排序中最快的算法以及接口实现的 sort 535 536 **数据随机分布** 537 ```shell 538 BenchmarkStdSort-8 100 22649399 ns/op 539 BenchmarkQuickSort-8 100 10870924 ns/op 540 BenchmarkStdSortWithoutInterface-8 100 10511605 ns/op 541 ``` 542 543 **数据升序分布** 544 ```shell 545 BenchmarkStdSort-8 200 7006117 ns/op 546 BenchmarkShellSort-8 1000 1667537 ns/op 547 BenchmarkStdSortWithoutInterface-8 1000 1619643 ns/op 548 ``` 549 550 **数据降序分布** 551 ```shell 552 BenchmarkStdSort-8 200 7614625 ns/op 553 BenchmarkShellSort-8 500 3051834 ns/op 554 BenchmarkStdSortWithoutInterface-8 1000 1689479 ns/op 555 ``` 556 557 🖖 [StdSortWithoutInterface](https://github.com/chenjiandongx/collections/blob/master/std_sort.go) 完胜!!! 558 559 我们还可以进一步思考如何获得更高的排序性能,使用 goroutine 将一个数据切分成两半,分别使用 `StdSortWithoutInterface` 排序,将排序后的结果进行一次归并排序,就可以得到最终的有序数组,这次我们测试的数组长度为 **10e5** 560 561 为了验证真正的`并行计算` 我们将分别测试 cpu 数量为 1, 2, 8 的情况 562 ```shell 563 BenchmarkStdSort 5 260696480 ns/op 564 BenchmarkStdSort-2 5 246746560 ns/op 565 BenchmarkStdSort-8 5 248532560 ns/op 566 BenchmarkStdSortWithoutInterface 10 124666470 ns/op 567 BenchmarkStdSortWithoutInterface-2 10 120676740 ns/op 568 BenchmarkStdSortWithoutInterface-8 10 126062650 ns/op 569 BenchmarkStdSortWithGoroutine 20 125163280 ns/op 570 BenchmarkStdSortWithGoroutine-2 20 80835825 ns/op 571 BenchmarkStdSortWithGoroutine-8 20 81232625 ns/op 572 ``` 573 574 😎 WOW!!! cpu 数量为 1 时大家相差无几,cpu > 1 以后,goroutine 做到了真正的并行,利用多核进行计算,速度提升了 **1.5** 倍,比默认的 Sort 方法提升了 **4** 倍。喏,这就是算法的魅力。 575 576 ### 📃 License 577 MIT [©chenjiandongx](http://github.com/chenjiandongx)