栈 Stack 栈是一种非常基础的线性结构,一种FILO类型的数据结构,FILO 即 Fisrt In Last Out
也就是先进后出,也可以说是后进先出
栈只支持三个操作, pop, top, push。
pop取出栈中最上层元素(8),栈的最上层元素变为早先进入的元素(9), 出栈
top查看栈的最上层元素(8)。
push将一个新的元素(5)放在栈的最上层, 入栈
通过数组实现的叫顺序栈, 通过链表实现的叫链式栈
golang实现栈 通过golang实现简单的栈
slice实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package main import ( "fmt" "sync" ) type Item interface{} type ItemStack struct { items []Item lock sync.RWMutex } func NewStack() *ItemStack { s := &ItemStack{} s.items = []Item{} return s } func (s *ItemStack) Print ( ) { fmt.Println(s.items) } func (s *ItemStack) Push (t Item ) { s.lock.Lock() defer s.lock.Unlock() s.items = append(s.items, t) } func (s *ItemStack) Pop() Item { s.lock.Lock() defer s.lock.Unlock() if len(s.items) == 0 { return nil } item := s.items[len(s.items)-1 ] s.items = s.items[0 : len(s.items)-1 ] return item }
1.slice相对简单
2.interface类型,允许添加任意类型元素
3.Push和Pop有加锁处理,线程安全
问题:slice的pop并不是真正意义上的pop,slice[:]只是一个新的引用,底层的内存并没有减少
container/list内置包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package main import ( "container/list" "sync" ) type Stack struct { list *list.List lock *sync.RWMutex } func NewStack() *Stack { list := list.New() l := &sync.RWMutex{} return &Stack{list, l} } func (stack *Stack) Push (value interface{} ) { stack.lock.Lock() defer stack.lock.Unlock() stack.list.PushBack(value) } func (stack *Stack) Pop() interface{} { stack.lock.Lock() defer stack.lock.Unlock() e := stack.list.Back() if e != nil { stack.list.Remove(e) return e.Value } return nil } func (stack *Stack) Peak() interface{} { e := stack.list.Back() if e != nil { return e.Value } return nil } func (stack *Stack) Len() int { return stack.list.Len() } func (stack *Stack) Empty() bool { return stack.list.Len() == 0 }
container/list 是一个双向链表
用链表模拟栈,要么都向链表的最后做push和pop,要么都向链表的起点做push和pop
单链表自定义实现栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package main import ( "sync" ) type ( Stack struct { top *node length int lock *sync.RWMutex } node struct { value interface{} prev *node } ) func NewStack() *Stack { return &Stack{nil, 0 , &sync.RWMutex{}} } func (this *Stack) Len() int { return this .length } func (this *Stack) Peek() interface{} { if this .length == 0 { return nil } return this .top.value } func (this *Stack) Pop() interface{} { this .lock.Lock() defer this .lock.Unlock() if this .length == 0 { return nil } n := this .top this .top = n.prev this .length-- return n.value } func (this *Stack) Push (value interface{} ) { this .lock.Lock() defer this .lock.Unlock() n := &node{value, this .top} this .top = n this .length++ }
1.允许添加任意类型的元素
2.Push和Pop是线程安全的
性能对比
队列 Queue 队列是一种先进先出,后进后出的线性表
先进入队列的先出去,后进入队列的后出去。必须从队尾插入新元素,队列中的元素只能从队首出
队列也可以通过数组,链表实现
顺序队列 1.初始化一个数组,及两个指针,一个队头指针,一个队尾指针,都指向0的位置
2.队列总是从队头取元素,队尾插入元素
3.入队,判断队列是否已满,队尾指针是否等于长度,不等可以插入,新元素放在队尾位置,指针加一后移
4.出队,头指针和尾指针指向同一个位置,则队列为空,不为空可以出队,出队则头指针的元素出队,头指针加一后移
出队和入队的时间复杂度都是O(1)
golang 实现顺序队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 package main import "fmt" type ArrayQueue struct { q []interface{} capacity int head int tail int } func NewArrayQueue(n int) *ArrayQueue { return &ArrayQueue{ q: make([]interface{}, n), capacity: n, head: 0 , tail: 0 , } } func (this *ArrayQueue) EnQueue(v interface{}) bool { if this .tail == this .capacity { return false } this .q[this .tail] = v this .tail++ return true } func (this *ArrayQueue) DeQueue() interface{} { if this .head == this .tail { return false } v := this .q[this .head] this .head++ return v } func (this *ArrayQueue) String () string { if this .head == this .tail { return "empty queue!" } result := "head" for i := this .head; i <= this .tail - 1 ; i++ { result += fmt.Sprintf("<-%+v" , this .q[i]) } result += "<-tail" return result } func (this *ArrayQueue) Empty() bool { if this .head == this .tail { return true }else { return false } } func (this *ArrayQueue) Len() int { return len(this .q) } func (this *ArrayQueue) GetHead() interface{} { if this .Empty() { return nil } return this .q[this .head] } func (this *ArrayQueue) Full() bool { if this .tail == this .head { return true } return false } func (this *ArrayQueue) Clear ( ) { this .head = 0 this .tail = 0 }
链式队列 1.初始化也需要两个指针,队头指针,队尾指针
2.入队,让新节点的Next指向队尾节点的Next,也就是nil,再让队尾节点的Next指向新节点,队尾指针加一后移,空队列单独处理
3.出队,头指针指向的元素出队,头指针加一后移,队列长度为1的情况,单独处理
golang 实现链式队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 package main import ( "fmt" ) type queueNode struct { data interface{} next *queueNode } type queueList struct { length int front *queueNode rear *queueNode } func initQueue() *queueList { L := &queueList{ length: 0 , front: nil, rear: nil, } return L } func (queue *queueList) push (val interface{} ) { node := &queueNode{ data: val, next: nil, } if queue.isNull() { queue.front = node queue.rear = node queue.length ++ return } queue.rear.next = node queue.rear = queue.rear.next queue.length ++ } func (queue *queueList) pop ( ) { if queue.isNull() { fmt.Println("队列为空" ) return } if queue.length == 1 { queue.front = nil queue.rear = nil queue.length -- return } queue.front = queue.front.next queue.length -- } func (queue queueList) isNull() bool { return queue.length == 0 } func (queue *queueList) Traverse() (arr []interface{}) { pre := queue.front for i := 0 ; i < queue.length; i++ { arr = append(arr, pre.data, "-->" ) pre = pre.next } return }
循环队列 循环队列是指队列是前后连成一个圆圈,它以循环的方式去存储元素,但还是会按照队列的先进先出的原则去操作
循环队列是基于数组实现的,相比于顺序队列,更好的利用数组空间
普通的数组队列在经过了一段时间的入队和出队以后,尾指针就指向了数组的最后位置了,没法再往队列里插入数据了,但是数组的前面部分(头指针的前面)由于旧的数据曾经出队了,所以会空出来一些空间,这些空间就没法利用起来
即使可以单独处理顺序队列,整体移动,也需要消耗额外的操作
循环队列也是一种线性结构
1.最后一个位置并不是结束位。对于循环队列,头指针始终指向队列的前面,尾指针始终指向队列的末尾
2.初始化队列,头指针和尾指针指向相同位置,此时队列是空
3.入队,新元素插入队尾指针位置,队尾指针加一后移
4.出队,头指针元素出队,头指针加一后移,该元素并不删除,只是不属于队列的一部分
5.继续入队,尾指针到达队列尾部时候,尾指针的下标重新变成0,而之前出队的元素则空出来
6.继续出队,则队列又有空间了,就可以继续入队,头指针的下标已经大于尾指针的下标了,这也是正式循环队列的特性导致的
7.判断队列为空的条件是:head==tail
8.判断队列满的情况:(tail+1)%n=head,取余数等于head,则表示满了,因为是循环
9.会浪费一个空间(length+1)== capacity 表示队列为满
PS:循环队列为什么用空一个元素的位置呢
循环队列中,由于入队时尾指针向前追赶头指针;出队时头指针向前追赶尾指针,造成队空和队满时头尾指针均相等。 因此,无法通过条件front==rear来判别队列是”空”还是”满”。 解决这个问题的方法至少有三种: 1.另设一布尔变量以区别队列的空和满
2.少用一个元素的空间。约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空)
3.使用一个计数器记录队列中元素的总数(即队列长度)。
golang 实现 预留了一个空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 type loopQueue struct { queues []interface{} front int tail int length int capacity int } func NewLoopQueue() *loopQueue { loop := &loopQueue{ queues: make([]interface{}, 0 , 2 ), front: 0 , tail: 0 , length: 0 , capacity: 2 , } for i := 0 ; i < 2 ; i++ { loop.queues = append(loop.queues, "" ) } return loop } func (q *loopQueue) Len() int { return q.length } func (q *loopQueue) Cap() int { return q.capacity } func (q *loopQueue) IsEmpty() bool { return q.length == 0 } func (q *loopQueue) IsFull() bool { return (q.length + 1 ) == q.capacity } func (q *loopQueue) GetFront() (interface{}, error) { if q.Len() == 0 { return nil, errors.New( "failed to getFront,queues is empty." ) } return q.queues[q.front], nil } func (q *loopQueue) Enqueue (elem interface{} ) { if q.IsFull() { buffer := new (loopQueue) for i := 0 ; i < 2 *q.capacity; i++ { buffer.queues = append(buffer.queues, "" ) } for i := 0 ; i < q.length; i++ { buffer.queues[i] = q.queues[q.front] q.front = (q.front + 1 ) % q.capacity } q.queues = buffer.queues q.front = 0 q.tail = q.length q.capacity = 2 * q.capacity } q.queues[q.tail] = elem q.tail = (q.tail + 1 ) % q.capacity q.length++ } func (q *loopQueue) Dequeue() (interface{}, error) { if q.IsEmpty() { return nil, errors.New( "failed to dequeue,queues is empty." ) } if q.length <= q.capacity/4 { buffer := new (loopQueue) for i := 0 ; i < q.capacity/2 ; i++ { buffer.queues = append(buffer.queues, "" ) } for i := 0 ; i < q.length; i++ { buffer.queues[i] = q.queues[q.front] q.front = (q.front + 1 ) % q.capacity } q.queues = buffer.queues q.fron1t = 0 q.tail = q.length q.capacity = q.capacity / 2 } queue := q.queues[q.front] q.front = (q.front + 1 ) % q.capacity q.length-- return queue, nil }
循环双端队列 双端队列,就是可在头部入队出队,也可在尾部入队出队
leetcode 641 设计循环双端队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 package lt641 type MyCircularDeque struct { data []int front int last int } func Constructor(k int) MyCircularDeque { return MyCircularDeque{ data: make([]int, k+1 , k+1 ), front:0 , last:0 , } } func (this *MyCircularDeque) InsertFront(value int) bool { if this .IsFull() { return false } this .front = (len(this .data) + this .front - 1 ) % len(this .data) this .data[this .front] = value return true } func (this *MyCircularDeque) InsertLast(value int) bool { if this .IsFull() { return false } this .data[this .last] = value this .last = (this .last + 1 ) % len(this .data) return true } func (this *MyCircularDeque) DeleteFront() bool { if this .IsEmpty() { return false } this .front = (this .front + 1 ) % len(this .data) return true } func (this *MyCircularDeque) DeleteLast() bool { if this .IsEmpty() { return false } this .last = (len(this .data) + this .last - 1 ) % len(this .data) return true } func (this *MyCircularDeque) GetFront() int { if this .IsEmpty() { return -1 } return this .data[this .front] } func (this *MyCircularDeque) GetRear() int { if this .IsEmpty() { return -1 } return this .data[(len(this .data) + this .last - 1 ) % len(this .data)] } func (this *MyCircularDeque) IsEmpty() bool { return this .last == this .front } func (this *MyCircularDeque) IsFull() bool { return (this .last + 1 ) % len(this .data) == this .front }
优先队列 优先队列分为两种,一种是最大优先队列,一种是最小优先队列
最大优先队列,出队时最大值先出
有序数组的入队时间复杂度为 O(n),出队时间复杂度为 O(1)
无序数组的入队时间复杂度为 O(1),出队时间复杂度为 O(n)
总的来说出入队都是O(n)
利用堆来实现优先队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 package main import ( "container/heap" "fmt" ) type Item struct { value string priority int index int } type PriorityQueue []*Item func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].priority > pq[j].priority } func (pq PriorityQueue) Swap (i, j int ) { pq[i], pq[j] = pq[j], pq[i] pq[i].index = i pq[j].index = j } func (pq *PriorityQueue) Push (x interface{} ) { n := len(*pq) item := x.(*Item) item.index = n *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1 ] item.index = -1 *pq = old[0 : n-1 ] return item } func (pq *PriorityQueue) update (item *Item, value string, priority int ) { item.value = value item.priority = priority heap.Fix(pq, item.index) } func main ( ) { items := map[string]int{ "banana" : 3 , "apple" : 2 , "pear" : 4 , } pq := make(PriorityQueue, len(items)) i := 0 for value, priority := range items { pq[i] = &Item{ value: value, priority: priority, index: i, } i++ } heap.Init(&pq) item := &Item{ value: "orange" , priority: 1 , } heap.Push(&pq, item) pq.update(item, item.value, 5 ) for pq.Len() > 0 { item := heap.Pop(&pq).(*Item) fmt.Printf("%.2d:%s " , item.priority, item.value) } }
堆 heap 分为大顶堆,小顶堆
查找最大或最小:O(1)
删除最大或最小:O(logn)
插入: O(logn)或O(1)
golang 标准库,实现的是小顶堆, “container/heap”包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 type IHeap []interface{} func (h IHeap) Len() int { return len(h) } func (h IHeap) Less(i, j int) bool { return h[i] < h[j] } func (h IHeap) Swap (i, j int ) { h[i], h[j] = h[j], h[i] } func (h *IHeap) Push (x interface{} ) { *h = append(*h, x) } func (h *IHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1 ] *h = old[0 : n-1 ] return x }
二叉堆 1.是一颗完全二叉树,根节点都是满的,除了最后一层
2.树中任意节点的值大于等于或小于等于(二叉大顶,小顶)其子节点的值
3.二叉堆一般都是通过数组实现
4.父节点,子节点位置关系,索引为i
左子节点:2*i+1
右子节点:2*i+2
父节点:floor((i-1)/2)
5.插入元素:插入堆尾部,重新维护堆,向上调整
6。删除元素:向下调整
golang实现二叉堆 通过数组实现一个二叉大顶堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 type maxHeap struct { size int nums []int } func parent(i int) int { if i == 0 { return 0 } return (i - 1 ) / 2 } func leftChild(i int) int { return 2 *i + 1 } func rightChild(i int) int { return 2 *i + 2 } func NewMaxHeap() *maxHeap { return &maxHeap{} } func (heap *maxHeap) GetSize() int { return heap.size } func (heap *maxHeap) IsEmpty() bool { return heap.size == 0 } func (heap *maxHeap) SiftUp (i int ) { if heap.size < len (heap.nums ) { heap.nums[heap.size] = i } else { heap.nums = append(heap.nums, i) } parI := parent(heap.size) childI := heap.size for heap.nums[parI] < heap.nums[childI] { heap.nums[parI], heap.nums[childI] = heap.nums[childI], heap.nums[parI] childI = parI parI = parent(parI) } heap.size++ } func siftDown (heap *maxHeap, parI int ) { var maxI int for { leftI := leftChild(parI) switch { case leftI+1 > heap.size: return case leftI+2 > heap.size: if heap.nums[parI] < heap.nums[leftI] { heap.nums[parI], heap.nums[leftI] = heap.nums[leftI], heap.nums[parI] } return case heap.nums[leftI] >= heap.nums[leftI+1 ]: maxI = leftI case heap.nums[leftI] < heap.nums[leftI+1 ]: maxI = leftI + 1 } if heap.nums[parI] >= heap.nums[maxI] { return } heap.nums[parI], heap.nums[maxI] = heap.nums[maxI], heap.nums[parI] parI = maxI } } func (heap *maxHeap) SiftDown() (int, error) { if heap.IsEmpty() { return 0 , errors.New("maxHeap is empty." ) } vTop := heap.nums[0 ] heap.size-- heap.nums[0 ], heap.nums[heap.size] = heap.nums[heap.size], 0 siftDown(heap, 0 ) return vTop, nil } func (heap *maxHeap) GetMax() (int, error) { if heap.IsEmpty() { return 0 , errors.New("maxHeap is empty." ) } return heap.nums[0 ], nil }