队列在数据结构家族里的地位是举足轻重的。传统的队列一般在队头进行出队操作,在队尾进行入队操作,而双端循环队列是功能最为齐全的,它有四个操作:A.在队头出队 B.在队头入队 C.在队尾出队 D.在队尾入队
这四种操作的组合大大增强了队列的实用性,更可观的是,它又是循环的,如此一来这样的队列就可以解决大部分与序列有关的问题了
下面是双端循环队列的示意图:
这样的循环队列用数组实现时必须增加一个额外的空间用来指示指针的关系,当队列为空时,队尾指针在队头指针的前面,当队列满时,两个指针重合指向附加的区域
下面是JavaScript双端循环队列的具体实现,我借助它对数组进行了简单的循环移位操作:
/双端循环队列
//数组中必须有一个 额外的附加空间(此空间不能存放元素) 用来判断头尾指针的关系
//如果头指针位置=为指针位置:队列已满
//如果头指针位置在尾指针位置的前面:队列为空
function DoubleCircleQueue(length)
{
var EMPTY_BLOCK = 0; //额外的附加空间
var queueLen = 0; //队列长度(包括 额外的附加空间)
var headPointer = 0;//队头指针
var rearPointer = 0;//队尾指针
var queue = new Array(queueLen);
var emtNum = 0; //队列里元素的个数
queueLen = length + 1;
headPointer = queueLen - 1;//队头指针
//从队头出队列
this.headPop = function()
{
//从队头出队列时,队头指针前进一位,保证头指针始终在队头元素的前一位置
if((headPointer+1) % queueLen == rearPointer)
{
alert("队列已经为空,不能再出队列!");
return null;
}
emtNum --;
var emt = queue[(headPointer+1)%queueLen];
queue[(headPointer+1)%queueLen] = EMPTY_BLOCK;
headPointer = (headPointer+1)%queueLen;
return emt;
}
//从队头入队列
this.headPush = function(emt)
{
//从队头入队列时,队头指针后退一位,保证头指针始终在队头元素的前一位置
if(headPointer == rearPointer)
{
alert("队列已经满了,不能再入队列!");
return null;
}
emtNum ++;
queue[headPointer] = emt;
headPointer = (headPointer == 0 ? queueLen - 1 : headPointer - 1); //用三元表达式比用取模运算的反函数更为简单
return emtNum;
}
//从队尾出队列
this.rearPop = function()
{
//从队尾出队列时,队尾指针后退一位,保证尾指针始终在队尾元素的后一位置
if((headPointer+1)%queueLen == rearPointer)
{
alert("队列已经为空,不能再出队列!");
return null;
}
emtNum --;
var position = rearPointer == 0 ? queueLen - 1:rearPointer - 1; //用三元表达式比用取模运算的反函数更为简单
var emt = queue[position];
queue[position] = EMPTY_BLOCK;
rearPointer = position;
return emt;
}
//从队尾入队列
this.rearPush = function(emt)
{
//从队尾入队列时,队尾指针前进一位,保证尾指针始终在队尾元素的后一位置
if(headPointer == rearPointer)
{
alert("队列已经满了,不能再入队列!");
return null;
}
emtNum ++;
queue[rearPointer] = emt;
rearPointer = (rearPointer + 1) % queueLen;
return emtNum;
}
//取队头元素(不改变元素)
this.getHead = function()
{
if((headPointer+1)%queueLen == rearPointer)
{
alert("队列已经为空,不能取元素!");
return null;
}
return queue[(headPointer+1)%queueLen];
}
//取队尾元素(不改变元素)
this.getRear = function()
{
if((headPointer+1)%queueLen == rearPointer)
{
alert("队列已经为空,不能取元素!");
return null;
}
return queue[rearPointer == 0 ? queueLen - 1:rearPointer - 1];
}
}
var testArray = new Array("A","B","C","D","E","F","G");
var testQueue = new DoubleCircleQueue(testArray.length);
alert(testArray.toString());
function moveRight()
{
for(var i = 0;i < testArray.length;i++)
{
testQueue.headPush(testArray[i]);
}
//先从队头取出一个元素,再从队尾取出剩余的元素就可以实现循环右移一位(一次队头取元素、一次队尾取元素)
testArray[0] = testQueue.headPop();
for(var i=1;i<testArray.length;i++)
{
testArray[i] = testQueue.rearPop();
}
alert(testArray.toString());
}
function moveLeft()
{
for(var i = 0;i < testArray.length;i++)
{
testQueue.headPush(testArray[i]);
}
//先从队尾取出一个元素放在数组的最后一位,再从队尾取出剩余的元素从数组的起始位置开始放置(两次都是队尾取元素 )
testArray[testArray.length-1] = testQueue.rearPop();
for(var i=0;i<testArray.length-1;i++)
{
testArray[i] = testQueue.rearPop();
}
alert(testArray.toString());
}
//向右循环移位5次
for(var j=0;j<5;j++)
{
moveRight();
}
//再向左循环移位5次
for(var j=0;j<5;j++)
{
moveLeft();
}
结果如下:
向右移5次
再向左移5次还原