GO语言数据结构之队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

顺序队列

在顺序队列中,为了降低运算的复杂度,元素入队时,只修改队尾指针;元素出对时,只修改队头指针。由于顺序队列的存储空间是提前设定的,因此队尾指针会有一个上限值,当队尾指针达到其上限时,就不能只通过修改队尾指针来实现新元素的入队操作了。

顺序队列代码实现

//定义队列结构
type queue struct {
    MaxSize int //定义队列容量
    array   [10]int
    read    int // 定义指向队尾
    front   int // 定义指向队头
}

//添加数据
func (queue *queue) add(data int) (err error) {
    //判断队列是否已满
    if queue.read == queue.MaxSize-1 {
        return errors.New("队列满")
    }
    //添加数据,队尾下标+1
    queue.read++
    queue.array[queue.read] = data
    return
}

//取数据
func (queue *queue) get() (data int, err error) {
    if queue.read == queue.front {
        return 0, errors.New("队列空")
    }
    //取数据队头下标+1
    queue.front++
    data = queue.array[queue.front]
    return data, err
}

//展示数据
func (queue *queue) show() {
    //获取数据从对头遍历到队尾
    for i := queue.front + 1; i <= queue.read; i++ {
        fmt.Println(i, queue.array[i])
    }
}

func main() {
    //初始化队列
    q := &queue{
        MaxSize: 10,
        array:   [10]int{},
        read:    -1,
        front:   -1,
    }
    var key string
    var val int
    for true {
        fmt.Println("1 添加数据")
        fmt.Println("2 获取数据")
        fmt.Println("3 显示队列")
        fmt.Println("4 退出")
        _, err := fmt.Scanln(&key)
        if err != nil {
            return
        }
        switch key {
        case "1":
            _, err := fmt.Scanln(&val)
            if err != nil {
                return
            }
            err = q.add(val)
            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println("成功")
            }
        case "2":
            get, err := q.get()
            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println(get)
            }
        case "3":
            q.show()
        case "4":
            os.Exit(0)
        }
    }
}

 

假溢出

顺序队列入队和出队操作均是直接在其后面进行结点的链接和删除,这就造成其使用空间不断向出队的那一边偏移,产生假溢出。

假设是长度为5的数组,初始状态,空队列如所示,front与 rear指针均指向下标为-1的位置。

然后入队a1、a2、a3、a4, front指针依然指向下标为-1位置,而rear指针指向下标为3的位置。

 

 出队a1、a2,则front指针指向下标为2的位置,rear不变

再入队a5,a6,此时front指针不变,rear指针移动到数组之外。

 

假设这个队列的总个数不超过5个,但目前如果接着入队的话,因数组末尾元素已经占用,再向后加,就会产生数组越界的错误,可实际上,我们的队列在下标为0和1的地方还是空闲的。我们把这种现象叫做假溢出

如何解决这个问题,能够利用到前面空闲的区域有两种方案

1、在元素出队列时所有元素向前平移,但是效率低下不建议采用

2、将新入队元素插入空闲区域,形成一个循环的结构,这就叫环形队列

环形队列

 虽然使用循环队列,解决了假溢出问题,但是又有新问题发生——判空的问题,因为仅凭 front = rear 不能判定循环队列是空还是满。

空循环队列

满循环队列

这种情况下通常采用留一位元素空间的方式来进行判断,此时front = rear + 1

环形队列代码实现

//定义环形队列结构
type LoopQueue struct {
    data [3]int //定义环形数组
    head int    //头部指针
    tail int    //尾部指针
    cap  int    //环形数组大小
}

//入队列
func (LoopQueue *LoopQueue) add(val int) (err error) {
    //判断队列是否已满
    if (LoopQueue.tail+1)%LoopQueue.cap == LoopQueue.head {
        return errors.New("队列满")
    }
    //放入数据
    LoopQueue.data[LoopQueue.tail] = val
    LoopQueue.tail = (LoopQueue.tail + 1) % LoopQueue.cap
    return
}

//出队列
func (LoopQueue *LoopQueue) get() (val int, err error) {
    //判断队列是否为空
    if LoopQueue.head == LoopQueue.tail {
        return 0, errors.New("队列空")
    }
    //赋值
    val = LoopQueue.data[LoopQueue.head]
    LoopQueue.head = (LoopQueue.head + 1) % LoopQueue.cap
    return
}

//数据展示
func (LoopQueue *LoopQueue) show() {
    //fmt.Println(LoopQueue.data)
    length := LoopQueue.length()
    if length == 0 {
        fmt.Println("队列为空")
    }
    //设计辅助变量指向头部指针
    tmpFront := LoopQueue.head
    for i := 0; i < length; i++ {
        fmt.Printf("data[%d]=%v\t", tmpFront, LoopQueue.data[tmpFront])
        tmpFront = (tmpFront + 1) % LoopQueue.cap
    }
    fmt.Println()
}

//获取元素个数
func (LoopQueue *LoopQueue) length() int {
    return (LoopQueue.tail + LoopQueue.cap - LoopQueue.head) % LoopQueue.cap
}

func main() {
    q := LoopQueue{
        data: [3]int{},
        head: 0,
        tail: 0,
        cap:  3,
    }

    var key string
    var val int
    for true {
        fmt.Println("1 添加数据")
        fmt.Println("2 获取数据")
        fmt.Println("3 显示队列")
        fmt.Println("4 退出")
        _, err := fmt.Scanln(&key)
        if err != nil {
            return
        }
        switch key {
        case "1":
            _, err := fmt.Scanln(&val)
            if err != nil {
                return
            }
            err = q.add(val)
            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println("成功")
            }
        case "2":
            get, err := q.get()
            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println(get)
            }
        case "3":
            q.show()
        case "4":
            os.Exit(0)
        }
    }
}

 

posted @ 2021-08-16 14:02  悠悠听风  阅读(224)  评论(0编辑  收藏  举报