Skip to content

Commit fd48226

Browse files
authored
Add details to queue and stack (#139)
1 parent 1909e89 commit fd48226

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

queue/README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,17 @@ func dequeue() (int, error) {
7575

7676
## Complexity
7777

78-
Enqueue and dequeue operations both perform in O(1) times.
78+
Enqueue and dequeue operations both perform in O(1) times in the linked list implementation. In other traditional languages like C the linked list approach is considered to be faster than the slice approach because both enqueue and dequeue operations are O(n) due to the slice size change. In Go however slices are managed intelligently behind the scene and perform very well for just about all purposes.
79+
80+
We can use Go's built-in benchmarking tooling to see which implementation is faster. This is done in [slice_vs_linked_list_bench_test.go](./slice_vs_linked_list_bench_test.go). It can be executed by running `go test -bench=. -test.benchmem` in this directory. The output shows that the slice implementation is almost seven times faster.
81+
82+
```Shell
83+
pkg: github.com/spring1843/go-dsa/queue
84+
BenchmarkLinkedListQueue-8 17956176 83.73 ns/op 56 B/op 1 allocs/op
85+
BenchmarkSliceQueue-8 100000000 11.79 ns/op 45 B/op 0 allocs/op
86+
PASS
87+
ok github.com/spring1843/go-dsa/queue 3.775s
88+
```
7989

8090
## Application
8191

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package queue
2+
3+
import (
4+
"container/list"
5+
"errors"
6+
"testing"
7+
)
8+
9+
type (
10+
linkedListQueue struct {
11+
items *list.List
12+
}
13+
14+
sliceQueue []int
15+
)
16+
17+
func BenchmarkLinkedListQueue(b *testing.B) {
18+
q := newLinkedListQueue()
19+
for n := 0; n < b.N; n++ {
20+
q.enqueue(n)
21+
}
22+
for n := 0; n < b.N; n++ {
23+
q.dequeue()
24+
}
25+
}
26+
27+
func BenchmarkSliceQueue(b *testing.B) {
28+
q := sliceQueue{}
29+
for n := 0; n < b.N; n++ {
30+
q.enqueue(n)
31+
}
32+
for n := 0; n < b.N; n++ {
33+
q.dequeue()
34+
}
35+
}
36+
37+
func (q *sliceQueue) enqueue(val int) {
38+
*q = append(*q, val)
39+
}
40+
41+
func (q *sliceQueue) dequeue() (int, error) {
42+
if len(*q) == 0 {
43+
return 0, errors.New("queue is empty")
44+
}
45+
value := (*q)[0]
46+
*q = (*q)[1:]
47+
return value, nil
48+
}
49+
50+
func newLinkedListQueue() *linkedListQueue {
51+
return &linkedListQueue{items: list.New()}
52+
}
53+
54+
func (q *linkedListQueue) enqueue(val int) {
55+
q.items.PushBack(val)
56+
}
57+
58+
func (q *linkedListQueue) dequeue() (int, error) {
59+
if q.items.Len() == 0 {
60+
return 0, errors.New("queue is empty")
61+
}
62+
return q.items.Remove(q.items.Front()).(int), nil
63+
}

stack/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ func pop() (int, error) {
7575

7676
Push and pop operations in stacks are considered O(1) operations, making them highly efficient. Additionally, many machines have built-in stack instruction sets, further increasing their performance. Stacks' unique efficiency and usefulness have solidified their place as one of the most fundamental data structures, second only to [arrays](../array).
7777

78+
Resizing the slice and item shifting maybe necessary in the slice implementation, hence traditionally this implementation is seen as O(n). As shown in the complexity of [queue](../queue/README.md) because of the intelligent ways Go resizes the slices this is not a problem and the slice implementation of both stack and queue will perform better than the linked list implementation.
79+
7880
## Application
7981

8082
Stacks are helpful when LIFO operations are desired. Many [graph](../graph) problems are solved with stacks.

0 commit comments

Comments
 (0)