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) } } }