Use go to implement common algorithms and data structures - queue (queue)

Introduction to queue

Queue is a very common data structure that can be seen frequently in daily life. A typical queue is as follows (picture from segmentfault ):
queue
It can be seen that the queue is basically the same as the queue in our daily life. All follow the principle of FIFO (First In First Out).

accomplish

Queues can be implemented using linked lists or arrays. The advantage of using a linked list is that it is easy to expand, but the disadvantage is that elements cannot be located by index. On the contrary, when using an array, expansion is not easy, but elements can be located by index. The article is implemented using a doubly linked list. The code is on github:

https://github.com/AceDarkknight/AlgorithmAndDataStructure/tree/master/queue

A linked list generally has the following basic operations. First define an interface to facilitate development and testing:

type Queue interface {
    // 获取当前链表长度。
    Length() int
    // 获取当前链表容量。
    Capacity() int
    // 获取当前链表头结点。
    Front() *Node
    // 获取当前链表尾结点。
    Rear() *Node
    // 入列。
    Enqueue(value interface{}) bool
    // 出列。
    Dequeue() interface{}
}

In the author's implementation, the front and rear nodes do not save specific values, but are only used to indicate the position of the real head and tail nodes.

Queue implemented by linked list

The implementation of enlisting is as follows:

// normalQueue.go
func (q *NormalQueue) Enqueue(value interface{}) bool {
    if q.length == q.capacity || value == nil {
        return false
    }

    node := &Node{
        value: value,
    }

    if q.length == 0 {
        q.front.next = node
    }

    node.previous = q.rear.previous
    node.next = q.rear
    q.rear.previous.next = node
    q.rear.previous = node
    q.length++

    return true
}

Implementation of dequeue:

// normalQueue.go
func (q *NormalQueue) Dequeue() interface{} {
    if q.length == 0 {
        return nil
    }

    result := q.front.next
    q.front.next = result.next
    result.next = nil
    result.previous = nil
    q.length--

    return result.value
}

It can be seen that the specific implementation is basically the same as the linked list. The advantage of this method is that there is no need to consider the problem of array overflow. But sometimes, we may insert the same element into the queue. Our current implementation cannot determine whether the data already exists. At this time, we need to implement a queue without repeated elements.

A queue with no repeating elements.

We only need to add a Map to store our specific values ​​on the original basis. Go directly to the code:

// uniqueQueue.go
func (q *UniqueQueue) Enqueue(value interface{}) bool {
    if q.length == q.capacity || value == nil {
        return false
    }

    node := &Node{
        value: value,
    }

    // Ignore uncomparable type.
    if kind := reflect.TypeOf(value).Kind(); kind == reflect.Map || kind == reflect.Slice || kind == reflect.Func {
        return false
    }

    if v, ok := q.nodeMap[value]; ok || v {
        return false
    }

    if q.length == 0 {
        q.front.next = node
    }

    node.previous = q.rear.previous
    node.next = q.rear
    q.rear.previous.next = node
    q.rear.previous = node

    q.nodeMap[value] = true

    q.length++

    return true
}

Because in golang, the keys of map must be comparable, so we need to exclude incomparable types such as Map, slice, and function. The rest of the implementation is similar to the above. Then see the column operation:

// uniqueQueue.go
func (q *UniqueQueue) Dequeue() interface{} {
    if q.length == 0 {
        return nil
    }

    result := q.front.next

    delete(q.nodeMap, result.value)

    q.front.next = result.next
    result.next = nil
    result.previous = nil

    q.length--

    return result.value
}

The above two queues are implemented based on linked lists. Let's introduce the circular queue implemented based on arrays.

circular queue

A circular queue achieves a "loop" effect by reusing array elements. Simply put, if there is a position in front of the array, put the element in it. The specific principle can be seen here . The entry code is as follows:

// cyclicQueue.go
func (q *CyclicQueue) Enqueue(value interface{}) bool {
    if q.length == q.capacity || value == nil {
        return false
    }

    node := &Node{
        value: value,
    }

    index := (q.rear + 1) % cap(q.nodes)
    q.nodes[index] = node
    q.rear = index
    q.length++

    if q.length == 1 {
        q.front = index
    }

    return true
}

The dequeue operation is similar:

// cyclicQueue.go
func (q *CyclicQueue) Dequeue() interface{} {
    if q.length == 0 {
        return nil
    }

    result := q.nodes[q.front].value
    q.nodes[q.front] = nil
    index := (q.front + 1) % cap(q.nodes)
    q.front = index
    q.length--

    return result
}

Reference

https://www.geeksforgeeks.org/queue-set-1introduction-and-array-implementation/

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325069669&siteId=291194637