队列及其应用

队列的各种实现

队列和栈一样,是一种特殊的线性表。队列的插入和删除操作分别在线性表的两端进行。因此,队列是一个先进先出(FIFO)的线性表。
队列(queue)是一个线性表,其插入和删除操作分别在表的不同端进行。插入元素的那一端称为队尾(back或rear),删除元素的那一端称为队首(front)

#ifndef QUEUE_H_INCLUDED
#define QUEUE_H_INCLUDED
using namespace std;
template<class T>
class queue{
public:
    virtual ~queue(){}
    virtual bool empty() const = 0;
                    //返回true当且仅当队列为空
    virtual int size() const = 0;
                    //返回队列中元素个数
    virtual T& front() = 0;
                    //返回头元素的引用
    virtual T& back() = 0;
                    //返回队尾元素的引用
    virtual void pop() = 0;
                    //删除首元素
    virtual void push(const T& theElement) = 0;
                    //把元素theElement加入队尾
};
#endif // QUEUE_H_INCLUDED

数组描述

如果使用非循环数组描述的话,即location(i)=i,会占用多余空间;
使用循环数组,location(i) = (location(队列首元素)+i)%arrayLength;
以下代码theFront指向第一个元素前一个空的位置

//循环队列的数组描述

#ifndef arrayQueue_
#define arrayQueue_

#include "queue.h"
#include "myExceptions.h"
#include <sstream>

using namespace std;

template<class T>
class arrayQueue : public queue<T>
{
   public:
      arrayQueue(int initialCapacity = 10);
      ~arrayQueue() {delete [] queue;}
      bool empty() const {return theFront == theBack;}
      int size() const
          {return (theBack - theFront + arrayLength) % arrayLength;}
      T& front()
         {// 返回队首元素
            if (theFront == theBack)
               throw queueEmpty();
            return queue[(theFront + 1) % arrayLength];
         }
      T& back()
         {// 返回队尾元素
            if (theFront == theBack)
               throw queueEmpty();
            return queue[theBack];
         }
      void pop()
           {// 移除队首元素
              if (theFront == theBack)
                 throw queueEmpty();
              theFront = (theFront + 1) % arrayLength;//theFront指向下一个位置
              queue[theFront].~T();  // 调用T的析构函数
           }
      void push(const T& theElement);
   private:
      int theFront;       // 从队首元素逆时针方向1
      int theBack;        // 队尾元素的位置
      int arrayLength;    // 队列长度
      T *queue;           // 元素的数组
};

template<class T>
arrayQueue<T>::arrayQueue(int initialCapacity)
{// 构造器
   if (initialCapacity < 1)
   {ostringstream s;
    s << "Initial capacity = " << initialCapacity << " Must be > 0";
    throw illegalParameterValue(s.str());
   }
   arrayLength = initialCapacity;
   queue = new T[arrayLength];
   theFront = 0;
   theBack = 0;
}

template<class T>
void arrayQueue<T>::push(const T& theElement)
{// 把元素加到队列中

   // 如果必要的话增加队列容量
   if ((theBack + 1) % arrayLength == theFront)
   {// 长度2倍
      // 分配一个新的数组
      T* newQueue = new T[2 * arrayLength];

      // 把元素复制到新数组中
      int start = (theFront + 1) % arrayLength;
      if (start < 2)
         // 没有形成环
         copy(queue + start, queue + start + arrayLength - 1, newQueue);
      else
      {  // 队列形成环
         copy(queue + start, queue + arrayLength, newQueue);
         copy(queue, queue + theBack + 1, newQueue + arrayLength - start);
      }

      // 设置新队列的首和尾的元素位置
      theFront = 2 * arrayLength - 1;
      theBack = arrayLength - 2;   // 队列长度arrayLength - 1
      arrayLength *= 2;
      queue = newQueue;
   }

   // 把元素放到队尾
   theBack = (theBack + 1) % arrayLength;
   queue[theBack] = theElement;
}

#endif


链表描述


// linked implementation of a queue
// derives from the ADT queue

#ifndef linkedQueue_
#define linkedQueue_

#include "queue.h"
#include "chainNode.h"
#include "myExceptions.h"
#include <sstream>

using namespace std;

template<class T>
class linkedQueue : public queue<T>
{
   public:
      linkedQueue(int initialCapacity = 10)
            {queueFront = NULL; queueSize = 0;}
      ~linkedQueue();
      bool empty() const
           {return queueSize == 0;}
      int size() const
          {return queueSize;}
      T& front()
         {
            if (queueSize == 0)
               throw queueEmpty();
            return queueFront->element;
         }
      T& back()
         {
            if (queueSize == 0)
               throw queueEmpty();
            return queueBack->element;
         }
      void pop();
      void push(const T&);
   private:
      chainNode<T>* queueFront;  // 指向队首的指针
      chainNode<T>* queueBack;   // 指向队尾的指针
      int queueSize;             // 队列中的元素个数
};

template<class T>
linkedQueue<T>::~linkedQueue()
{// 构造器
   while (queueFront != NULL)
   {// 删除队首节点
      chainNode<T>* nextNode = queueFront->next;
      delete queueFront;
      queueFront = nextNode;
   }
}

template<class T>
void linkedQueue<T>::pop()
{// 删除队首节点
   if (queueFront == NULL)
      throw queueEmpty();

   chainNode<T>* nextNode = queueFront->next;
   delete queueFront;
   queueFront = nextNode;
   queueSize--;
}


template<class T>
void linkedQueue<T>::push(const T& theElement)
{// 把节点加入队尾
   // 为新元素创建节点
   chainNode<T>* newNode = new chainNode<T>(theElement, NULL);
      
   // 把新节点加入队尾
   if (queueSize == 0)
      queueFront = newNode;       // 队列为空
   else
      queueBack->next = newNode;  // 队列不空
   queueBack = newNode;
   queueSize++;
}
#endif

队列的应用

列车车厢重排

将栈应用中的缓冲轨道换为如图的队列式轨道,使得列车车厢经过缓冲轨道后成为按顺序排列的车厢。
在这里插入图片描述
缓冲轨道的使用方案为,假定有9道车厢需要重排,其初始排序为5,8,1,7,4,2,9,6,3。假设k=3,3号车厢不能直接进入出轨道,进入H1,之后6,9都进入H1,之后2进入H2,4,7也都进入H2,之后1号出轨,2,3,4,依次出轨,然后5号从入轨再出轨,之后一次是6,7,8,9;
当一节车厢进入缓冲轨道时,依据如下的原则来选择缓冲轨道:缓冲轨道上已有的车厢编号均小于c;如果有多个缓冲轨道都满足这一条件,则选择左端车厢编号最大的缓冲轨道;否则选择一个空的缓冲轨道

//输出车厢
void ouputFromHoldingTrack()
{//将编号最小的车厢从缓冲轨道转移到出轨道
//从栈itsTrack中删除编号最小的车厢
	track[itsTrack].pop();
	cout << "Move car " << smallestCar << " from holding track "<< itsTrack << " to output track" << endl;
	//检查所有队列尾,寻找编号最小的车厢和他所属的队列
	smallestCar = numberOfCars + 2;
	for(int i = 1;i <= numberOfCars;i++)
		if(!track.empty() && track[i].front() < smallestCar)
		{
			smallestCar = track[i].front();
			itsTrack = i;
		}
}
//把车厢调入一个缓冲轨道
bool putInHoldingTrack(int c)
{//使车厢c移到一个缓冲轨道。返回false,当且仅当没有可用的缓冲轨道

//为车厢c寻找最合适的缓冲轨道
//初始化
int bestTrack = 0,
	bestLast = 0;

//扫描缓冲轨道
for(int i = 1;i <= numberOfTracks;i++)
{
	if(!track[i].empty())
	{//缓冲轨道i不空
		int lastCar = track[i].back();
		if(c > lastCar && lastCar > besLast)
		{
			//即小于他的最大的车厢
			//缓冲轨道i的尾部具有编号更大的车厢
			bestLast = lastCar;
			bestTrack = i;
		}
	}
	else//缓冲对到i为空
		if(bestTrack == 0)
			bestTrack = i;
}
if (bestTrack == 0)
	return false; //没有可用的缓冲轨道

//把车厢c移到轨道bestTrack
track[bestTrack].push(c);
cout << "Move car " << c <<" from input track "	
	 << "to holding track"<< bestTrack << endl;
	//如果有需要,更新smallestCar和itsTrack
if(c < smallestCar)
{
	smallestCar = c;
	itsTrack = bestTrack;
}
return false;
}
bool railroad(int* inputOrder,
			  int theNumberOfCars,int theNumberOfTracks)
{
	numberOfCars = theNumberOfCars;
	numberOfTracks = theNumberOfTracks - 1;

	track = new arrayQueue<int> [numberOfTracks + 1]

	int nextCarToOutput = 1;
	smallestCar = numberOfCars + 1;
    for (int i = 1; i <= numberOfCars; i++)
      if (inputOrder[i] == nextCarToOutput)
      {// send car inputOrder[i] straight out
          cout << "Move car " << inputOrder[i] << " from input "
               << "track to output track" << endl;
          nextCarToOutput++;
 
          // output from holding tracks
          while (smallestCar == nextCarToOutput)
          {
             outputFromHoldingTrack();
    	     nextCarToOutput++;
         }
      }
      else
      // put car inputOrder[i] in a holding track
         if (!putInHoldingTrack(inputOrder[i]))
            return false;

   return true;
}

第二种方法没有用到队列,解决方案如下:如果只是为了简单地输出车厢重排过程中所有必要的车厢移动排序,那么我们只需要知道每条缓冲轨道队列中最后一节车厢的号码,一节每节车厢当前所在的轨道即可。如果缓冲轨道i为空,则令lastCar[i]=0,否则令lastCar[i]为缓冲轨道i中最后一节车厢的编号。如果车厢i位于入轨道,令whichTrack[i]=0;否则令whichTrack[i]为车厢i所在的缓冲轨道。起始时,lastCar[i]=0≤i<k,whichTrack[i]=0,1≤i≤n。使用这些变量而不使用队列。

// railroad car rearrangement using no explicit queues
#include <iostream>

using namespace std;

// global variables
int* whichTrack;  // track that has the car
int* lastCar;     // last car in track
int numberOfCars;
int numberOfTracks;

void outputFromHoldingTrack(int c)
{// Move car c from its holding track to the output track.
   cout << "Move car " << c << " from holding track "
        << whichTrack[c] << " to output track" << endl;

   // if c was the last car in its track, the track is now empty
   if (c == lastCar[whichTrack[c]])
      lastCar[whichTrack[c]] = 0;
}


bool putInHoldingTrack(int c)
{// Put car c into a holding track.
 // Return false iff there is no feasible holding track for this car.

   // find best holding track for car c
   // initialize
   int bestTrack = 0,  // best track so far
       bestLast =  0;  // last car in bestTrack

   // scan tracks
   for (int i = 1; i <= numberOfTracks; i++)
      if (lastCar[i] != 0)
      {// track i not empty
          if (c > lastCar[i] && lastCar[i] > bestLast)
          {
             // track i has bigger car at its rear
             bestLast = lastCar[i];
             bestTrack = i;
          }
      }
      else // track i empty
         if (bestTrack == 0)
            bestTrack = i;
      
   if (bestTrack == 0)
      return false; // no feasible track

   // add c to bestTrack
   whichTrack[c] = bestTrack;
   lastCar[bestTrack] = c;
   cout << "Move car " << c << " from input track "
        << "to holding track " << bestTrack << endl;

   return true;
}

bool railroad(int* inputOrder,
              int theNumberOfCars, int theNumberOfTracks)
{// Rearrange railroad cars beginning with the initial order.
 // inputOrder[1:theNumberOfCars]
 // Return true if successful, false if impossible.

   numberOfCars = theNumberOfCars;
   // keep last track open for output
   numberOfTracks = theNumberOfTracks - 1;

   // create the arrays lastCar and whichTrack
   lastCar = new int [numberOfTracks + 1];
   fill(lastCar + 1, lastCar + numberOfTracks + 1, 0);
   whichTrack = new int [numberOfCars + 1];
   fill(whichTrack + 1, whichTrack + numberOfCars + 1, 0);

   int nextCarToOutput = 1;

   // rearrange cars
   for (int i = 1; i <= numberOfCars; i++)
      if (inputOrder[i] == nextCarToOutput)
      {// send car inputOrder[i] straight out
          cout << "Move car " << inputOrder[i] << " from input "
               << "track to output track" << endl;
          nextCarToOutput++;
 
          // output from holding tracks
          while (nextCarToOutput <= numberOfCars &&
                 whichTrack[nextCarToOutput] != 0)
          {
             outputFromHoldingTrack(nextCarToOutput);
    	     nextCarToOutput++;
         }
      }
      else
      // put car inputOrder[i] in a holding track
         if (!putInHoldingTrack(inputOrder[i]))
            return false;

   return true;
}

int main()
{
   //int p[] = {0, 5, 8, 1, 7, 4, 2, 9, 6, 3};
   //cout << "Input permutation is 581742963" << endl;
   int p[] = {0, 3, 6, 9, 2, 4, 7, 1, 8, 5};
   cout << "Input permutation is 369247185" << endl;
   railroad(p, 9, 3);

   return 0;
}

电路布线

在迷宫老鼠问题中,可以寻找从迷宫入口到迷宫出口的一条最短路径。这种在网格中寻找最短路径的算法有许多应用。例如,在电路布线问题的求解中,一个常用的方法就是在布线区域设置网格,该网格把布线区域划分成n×m个方格,就像迷宫一样。一条线路从一个方格a的中心点连接到另一个方格b的中心点,转弯处可以采用直角,已有线路经过的方格被封锁,成为下一条线路的障碍,我们希望用a和b直线的最短路径来布线,以减少信号的描述
在这里插入图片描述

bool findPath()
{
    //寻找从始点到终点的最短路径
    //找到时,返回true,否则返回false
    if((start.row == finish.row) && (start.col == finish.col))
    {
        //始点 == 终点
        pathLength = 0;
        return true;
    }
    //初始化偏移量
    position offset[4];
    offset[0].row = 0;offset[0].col = 1;//右
    offset[1].row = 1;offset[1].col = 0;//下
    offset[2].row = 0;offset[1].col = -1;//左
    offset[3].row = -1;offset[3].col = 0;//上

    //初始化网格四周的障碍物
    for(int i = 0; i <= size; i++)
    {
        grid[0][i] = grid[size + 1][i] = 1;//顶部和底部
        grid[i][0] = grid[i][size + 1] = 1;//左边和右边
    }
    position here = start;
    grid[start.row][start.col] = 2;//标记
    int numOfNbrs = 4;//一个方格的相邻位置数

    //对可到达的位置做标记
    arrayQueue<position> q;
    position nbr;
    do //给相邻位置做标记
    {
        for(int i = 0; i < numOfNbrs; i++)
        {
            //检查相邻位置
            nbr.row = here.row + offset[i].row;
            nbr.col = here.col + offset[i].col;
            if(grid[nbr.row][nbr.col] == 0)
            {
                //对不可标记的nbr做标记
                grid[nbr.row][nbr.col]
                    = grid[here.row][here.col] + 1;
                if ((nbr.row == finish.row) &&
                        (nbr.col == finish.col))break;
                //把后者插入队列
                q.push(nbr);
            }
        }
        // 是否到达终点,到达终点后停止
        if((nbr.row == finish.row) && (nbr.col == finish.col))
            break;
        // 终点不可到达
        if(q.empty())
            return false;
        here = q.front();
        q.pop();
    }while(true);

    //构造路径
    pathLength = grid[finish.row][finish.col] - 2;
    path = new position [pathLength];

    //从终点回溯
    here = finish;
    for(int j = pathLength - 1; j >= 0; j--)
    {
        path[j] = here;
        //寻找祖先位置
        for(int i = 0;i < numOfNbrs; i++)
        {
            nbr.row = here.row + offset[i].row;
            nbr.col = here.col + offset[i].col;
            if(grid[nbr.row][nbr.col] == j + 2)break;
        }
        here = nbr;
    }
    return true;
}

任意一个方格至多插入队列1次,所以距离标记过程需耗耗时 O ( m 2 ) O(m^2) O(m2)。而路径标记过程需耗时 O ( 最 短 路 径 长 度 ) O(最短路径长度) O()

图元识别

数字化图像是一个m×m的像素矩阵。在单色图像中,每一个像素要么为0.要么为1。值为0表示图像的背景,值为1的像素表示图元上的一个点,称其为图元像素。两个像素是相邻的,是指它们左右相邻或上下相邻。两个相邻的图元像素是同一个图元的像素。图元识别的目的就是给图元像素做标记,使得两个像素标记相同,当且仅当它们是同一个图元的像素
在这里插入图片描述
求解策略:通过扫描像素来识别图元。扫描的方式是逐行扫描,每一行逐列扫描。当扫描到一个未标记的图元像素时,给它一个图元标记。然后把这个图元像素作为一个新余元的种子,通过识别和标记所有与该种子相邻的图元像素,来寻找新图元剩余的像素。与种子相邻的图元像素称为1-间距像素。然后,识别和标记与1-间距像素相邻的所有未标记的图元像素,这些像素被称为2-间距像素。接下来识别和标记与2-图元像素相邻的未标记的图元像素,这个过程一直持续到没有新的、未标记的、相邻的图元像素为止。

void labelComponents()
{//给图元编号
    //初始化数组offset
    position offset[4];
    offset[0].row = 0;offset[0].col = 1;//
    offset[1].row = 1;offset[1].col = 0;//
    offset[2].row = 0;offset[2].col = -1;//
    offset[3].row = -1;offset[3].col = 0;//

    //初始化0值像素围墙
    for(int i = 0;i <= size + 1;i++)
    {
        pixel[0][i] = pixel[size+1][i] = 0;
        pixel[i][0] = pixel[i][size + 1] = 0;
    }

    int numOfNbrs = 4;
    int id = 1;
    //扫描所有元素,标记图元
    arrayQueue<position> q;
    position here,nbr;
    for(int r = 1; r <= size; r++)
        for(int c = 1;c <= size; c++)
            if(pixel[r][c] == 1)
            {//新图元
                pixel[r][c] = ++id;
                here.row = r;
                here.col = c;

                while(true)
                {//寻找其余的图元
                    for(int i = 0;i < numOfNbrs;i++)
                    {//检查所有相邻位置
                        nbr.row = here.row + offset[i].row;
                        nbr.col = here.col + offset[i].col;
                        if(pixel[nbr.row][nbr.col] == 1)
                        {//像素是当前图元的一部分
                            pixel[nbr.row][nbr.col] = id;
                            q.push(nbr);
                        }
                    }
                    //图元中任意未考察的像素
                    if(q.empty())break;
                    here = q.front();   //一个图元像素
                    q.pop();
                }
            }//结束if
}


工厂仿真

一个工厂有m台机器。工厂的每项任务都需要若干道工序才能完成。每台机器都执行一道工序,不同的机器执行不同的工序。一台机器一旦开始执行一道工序就不会中断,直到该工序完成为为止每道工序都需要工序时间和执行该工序的机器,一项任务中的若干道工序必须按照一定顺序来完成。每台机器都可以有如下三种状态:活动状态、空闲状态和转换状态一项任务的最后完成时间成为该任务的完成时间
在这里插入图片描述

//工厂仿真的主要程序
void main()
{
	inputDataa();	//获取机器和任务的数据
	startShop();	//装入初始任务
	simulate();		//执行所有任务
	outputStatistics();//输出在每台机器上的等待时间
}

结构task,每项工序都有两部分构成,machine(执行该工序的机器)和time(完成该工序所需要的时间)

struct task
{
	int machine;
	int time;
	task(int theMachine = 0, int theTime = 0)
	{
		machine = theMachine;
		time = theTime;
	}
};

结构job,每项任务都有一个工序表,每道工序按表中的顺序执行。可以把工序表描述成一个队列taskQ。为了计算每一项任务的总等待时间,需要知道该任务的长度和完成时间。完成时间通过计时确定,任务长度为各工序时间之和。为了计算任务长度,我们定义一个数据成员length。
arrivalTime用于记录一项任务进入当前队列的时间,然后确定该任务在这个队列中的等待时间。任务标志符存储在id中,仅在输出该任务的总的等待时间才会使用这个标志符。
addTask把一道工序加入任务的工序队列中,该工序在机器theMachine上执行,需要时间为theTime。该方法仅在数据输入时使用。当一个任务从一个机器队列中删除然后进入到活动状态时,使用方法removeNextTask。这时,该任务的第一道工序从工序队列(工序队列用于保存尚未被执行的工序)中删除,并把该工序时间加到任务长度中。

struct job
{
    arrayQueue<task> taskQ;         //任务的工序
    int length;                     //被调度的工序时间之和
    int arrivalTimen;               //到达当前队列的时间
    int id;                         //任务标志符
    
    job(int theId = 0)
    {
        id = theId;
        length = 0;
        arrivalTime = 0;
    }
    
    void addTask(int theMachine, int theTime)
    {
        task theTask(theMachine,theTime);
        taskQ.push(theTask);
    }
    
    int removeNextTask()
    {//删除任务的下一道工序,返回它的时间
    //更新长度
    
        int theTime = taskQ.front().time;
        taskQ.pop;
        length += theTime;
        return theTime;
    }
};

结构machine,每台机器都有转换时间、当前任务和等待任务的队列。由于每项任务在任何时刻只会在一台机器队列中,因此所有队列的空间总量以任务的数目为限。不过,任务在各个机器队列中的分布随着仿真过程的进展会不断变化。

struct machine
{
   arrayQueue<job*> jobQ;
                     // 本机器的等待处理的任务队列
   int changeTime;   // 本机器的转换时间
   int totalWait;    // 本机器的总体延时
   int numTasks;     // 本机器处理的工序数量
   job* activeJob;   // 本机器当前处理的任务

   machine()
   {
      totalWait = 0;
      numTasks = 0;
      activeJob = NULL;
   }
};

类eventList
所有机器的完成时间都存储在一个事件表中。为了从一个事件转向下一个事件,我们需要在机器的完成时间中确定最小者。


#ifndef eventList_
#define eventList_

#include "myExceptions.h"

using namespace std;

class eventList
{
   public:
      eventList(int theNumMachines, int theLargeTime)
      {//为m台机器,初始化其完成时间
         if (theNumMachines < 1)
            throw illegalParameterValue
                  ("number of machines must be >= 1");
         numMachines = theNumMachines;
         finishTime = new int [numMachines + 1];
   
         // 所有机器都空闲,用最大的完成时间初始化
         for (int i = 1; i <= numMachines; i++)
            finishTime[i] = theLargeTime;
      }
      
      int nextEventMachine()
      {// 返回值是处理下一项工序的机器
      
         // 寻找完成时间最早的机器
         int p = 1;
         int t = finishTime[1];
         for (int i = 2; i <= numMachines; i++)
            if (finishTime[i] < t)
            {// 机器i完成时间更早
               p = i;
               t = finishTime[i];
            }
         return p;
      }
   
      int nextEventTime(int theMachine)
      {return finishTime[theMachine];}
   
      void setFinishTime(int theMachine, int theTime)
      {finishTime[theMachine] = theTime;}
   private:
      int* finishTime;   // 完成时间数组
      int numMachines;   // 机器数量
};
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值