数据结构:理解循环队列

循环队列:一个数组,和一个头指针、尾指针。(这里的指针不是真的指针,只是记录下标的整数)。现在还不明白没关系,我们看下面的代码:

 1let loopQuene = new Array(5)    // 创建一个长度为 5 的循环队列。
 2let front = 0, rear = 0         // 初始化两个指针,记录头位置和尾+1位置。
 3/*
 4现在的样子:
 5[null, null, null, null, null]
 6 ^f,r
 7*/
 8
 9loopQuene[rear] = 1 //排队入队
10rear++;
11/*
12现在的样子:
13[1, null, null, null, null]
14 ^f  ^r
15*/
16
17loopQuene[rear] = 4 //排队入队
18rear++;
19/*
20现在的样子:
21[1, 4, null, null, null]
22 ^f     ^r
23*/
24
25loopQuene[front] = null //1 出队
26front++;
27/*
28现在的样子:
29[null, 4, null, null, null]
30       ^f  ^r
31*/

如果元素太多呢?

 1/*
 2现在的样子:
 3[1, 1, 2, 3, null]
 4 ^f          ^r
 5*/
 6loopQuene[rear] = 5 //排队入队
 7rear++;//溢出了
 8// 所以为了避免溢出,我们这样:
 9rear = (rear + 1) % loopQuene.length
10/*
11现在的样子:
12[1   , 1, 2, 3, 5]
13 ^f^r
14*/

为啥是循环队列呢?继续出队

 1/*
 2现在的样子:
 3[1   , 1, 2, 3, 5]
 4 ^f^r
 5*/
 6// 出队
 7loopQuene[front] = null;
 8front++;
 9/*
10现在的样子:
11[null   , 1, 2, 3, 5]
12 ^r      ^f
13
14// 出队
15loopQuene[front] = null;
16front++;
17/*
18现在的样子:
19[null   , null, 2, 3, 5]
20 ^r             ^f
21*/
22
23// 入队
24loopQuene[rear] = 8;
25rear += (rear + 1) % loopQuene.length;
26/*
27现在的样子:
28[8   , null, 2, 3, 5]
29 ^r             ^f
30*/

看,就像首尾相连起来了。这样的好处(相对于不循环的直接数组模拟的队列)是不用在出队的时候批量移动元素。

另外发现,队满和队空时都会有 front == rear 成立。为了避免无法判断是空是满,解决方法如下:

标识法:

 1const delete = 0;
 2const append = 1;
 3let lastOperation = delete;
 4
 5if( front == rear && lastOperation == delete){
 6    alert("队空了!")
 7}
 8
 9if( front == rear && lastOperation == append){
10    alert("队满了!")
11}

计数法:添加一个变量 queneLength 记录队列中元素数量,每当入队时 queneLength++,出队时 queneLength--

空闲单元法:说白了就是队尾一直维持一个空元素,这样需要在初始化队列的时候提前增加一个空间。

 1/*
 2现在的样子:
 3[1, 1, 2, 3, <null>, null]
 4 ^f          ^r
 5*/
 6//排队入队
 7loopQuene[rear] = 5
 8rear = (rear + 1) % loopQuene.length
 9/*
10现在的样子:
11[1, 1, 2, 3, 5, <null>]
12 ^f              ^r
13此时队满,可见队满条件是 front == (rear + 1) % loopQuene.length
14*/
15/**
16出队:
17[ <null>, 1, 2, 3, 5, null]
18入队:
19[ <null>, 1, 2, 3, 5, 8]
20用起来没毛病!
21 */