队列的定义
队列的概念
- 队列是一种常用的线性结构,到达越早的结点,离开的时间越早。
- 只能从对队尾入队,从队头出队。
- 所以队列通常称之为先进先出(FIFO: First In First Out)队列。
队列的基本操作
- 创建一个队列
create()
:创建一个空的队列; - 入队
enQueue(x)
:将x插入队尾,使之成为队尾元素; - 出队
deQueue()
:删除队头元素并返回队头元素值; - 读队头元素
getHead()
:返回队头元素的值; - 判队列空
isEmpty()
:若队列为空,返回true
,否则返回false
。
队列的抽象类
template <class elemType>
class queue{
public:
virtual bool isEmpty() = 0; //判队空
virtual void enQueue(const elemType &x) = 0; //进队
virtual elemType deQueue() = 0; //出队
virtual elemType getHead() = 0; //读队头元素
virtual ~queue() {} //虚析构函数
};
队列的顺序实现
顺序队列的概念和设计
- 顺序队列也是通过一个数组来实现的。
- 使用数组存储队列中的元素
- 结点个数最多为
MaxSize
个 ,下标的范围从0
到MaxSize-1
。 - 顺序队列有三种组织方式:
-
队头位置固定:头元素下标为0,并指定尾元素,当队列为空时尾元素被指定为-1。出队会引起大量数据移动。
-
队头位置不固定: 队首指针
front
指示队首结点的前一位置,队尾指针rear
指示队尾结点存放的下标地址,初始化将两指针均设为-1
,队满时rear=Maxsize-1
。浪费空间。 -
循环队列 : 依旧一个
front
和一个rear
,牺牲一个单元规定front
指向的单元不能存储队列元素,队列满的条件是:(rear+1)%MaxSize == front
。这是相对而言更合理的一种顺序队列的实现方式。
-
循环顺序队列类定义
template <class elemType>
class seqQueue: public queue<elemType> {
private:
elemType *elem;
int maxSize;
// 队头和队尾
int front, rear;
void doubleSpace();
public:
seqQueue(int size = 10);
// 析构函数:收回动态数组
~seqQueue() { delete [] elem ; }
// 判队列是否为空:队头是否等于队尾
bool isEmpty() { return front == rear; }
void enQueue(const elemType &x);
elemType deQueue();
// 访问队头元素
elemType getHead() { return elem[(front + 1) % maxSize]; }
};
循环顺序队列成员函数实现
- 构造函数
template <class elemType>
seqQueue<elemType>::seqQueue(int size) {
elem = new elemType[size];
maxSize = size;
front = rear = 0;
}
- deQueue函数
template <class elemType>
elemType seqQueue<elemType>::deQueue() {
front = (front + 1) % maxSize;
return elem[front];
}
- enQueue函数
template <class elemType>
void seqQueue<elemType>::enQueue(const elemType &x) {
if ((rear + 1) % maxSize == front)
doubleSpace();
rear = (rear + 1) % maxSize;
elem[rear] = x;
}
- doubleSpace函数
template <class elemType>
void seqQueue<elemType>::doubleSpace() {
elemType *tmp =elem;
elem = new elemType[2 * maxSize];
for (int i = 1; i < maxSize; ++i)
elem[i] = tmp[(front + i) % maxSize];
front = 0;
rear = maxSize - 1;
maxSize *= 2;
delete [] tmp;
}
练习
解密QQ号
新学期开始了,小哈是小哼的新同,小哼向小哈询问QQ号,小哈当然不会直接告诉小哼。所以小哈给了小哼一串加密过的数字,同时小哈也告诉了小哼解密规则。规则是这样的:首先将第1个数删除,紧接着将第2个数放到这串数的末尾,再将第3个数删除并将第4个数再放到这串数的末尾,再将第5个数删除……直到剩下最后一个数,将最后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一起就是小哈的QQ啦。现在你来帮帮小哼吧。小哈给小哼加密过的一串数是6 3 1 7 5 8 9 2 4。解密后小哈的QQ号应该是6 1 5 9 4 7 2 8 3。
输入描述:
只有2行,第1行有一个整数n (1 ≤ n ≤ 10^5)
第2行有n个整数为加密过的QQ号,每个整数之间用空格隔开。每个整数在 1~9 之间。
对于100%的数据满足1 ≤ n ≤ 10^5。
输出描述:
只有一行,输出解密后的QQ号。
示例 1:
输入:
9
6 3 1 7 5 8 9 2 4
输出:
6 1 5 9 4 7 2 8 3
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1000005
using namespace std;
int n;
int q[N], l, r;
int main() {
int n;
scanf("%d", &n);
l = r = 0;
for (int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
q[r++] = x;
}
int flag = 0;
while (l < r) {
int x = q[l++];
if (!flag) printf("%d ", x);
else q[r++] = x;
flag ^= 1;
}
return 0;
}
链接队列
链接队列的概念和设计
- 采用不带头结点的单链表
- 单链表的表头作为队头,单链表的表尾作为队尾
- 同时记录头尾结点的位置。
链接队列类定义
template <class elemType>
class linkQueue: public queue<elemType> {
private:
struct node {
elemType data;
node *next;
node(const elemType &x, node *N = NULL){data = x; next = N;}
node():next(NULL) {}
~node() {}
};
node *front, *rear;
public:
linkQueue() { front = rear = NULL; }
// 析构函数,和链表的析构函数类似
~linkQueue();
bool isEmpty() { return front == NULL; }
void enQueue(const elemType &x);
elemType deQueue();
elemType getHead() { return front->data; }
};
链接队列成员函数实现
- enQueue函数
template <class elemType>
void linkQueue<elemType>::enQueue(const elemType &x) {
if (rear == NULL)
front = rear = new node(x);
else
rear = rear->next = new node(x);
}
- deQueue函数
template <class elemType>
elemType linkQueue<elemType>::deQueue() {
node *tmp = front;
elemType value = front->data;
front = front->next;
delete tmp;
// 判断是否为空
if (front == NULL) rear = NULL;
return value;
}
队列的应用
问题描述
一列货运列车共有n节车厢,每节车厢将被放在不同的车站。假定n个车站的编号分别为1 – n,货运列车按照第n站到第1站的次序经过这些车站。车厢的编号与它们的目的地相同。为了便于从列车上卸掉相应的车厢,必须重新排列这些车厢,将第n节车厢放在最后,第1节车厢放在最前面。
实现思想
- 每条缓冲轨道用一个队列模拟。
- 处理过程如下:
依次取入轨的队头元素,直到队列为空
进入一条最合适的缓冲轨道
检查每条缓冲轨道的队头元素,将可以放入出轨的元素出队,进入出轨
解决方案实现
- 模拟过程:
// in表示输入轨道中车厢的排列,总共n个车厢,通过k个缓冲轨道
void arrange(int in[], int n, int k) {
linkQueue<int> *buffer = new linkQueue<int>[k];
int last = 0;
// 依次把每节车厢放入缓冲轨道,然后检查能否将缓冲轨道中的车厢移动到输出轨道
for (int i = 0; i < n; ++i) {
// 如果车厢放不进去,表示重排失败,不必再进行下去
if (!putBuffer(buffer, k, in[i])) return;
checkBuffer(buffer, k, last);
}
}
putBuffer
函数:找合适的轨道- 基本要求:轨道上最后一节车厢的编号小于进入的车厢编号,没有满足基本要求的轨道,则启用一条新轨道
- 最优要求:多个轨道满足时,选择其中最后一节车厢编号最大的一个
- 例如处理车厢8,现有轨道情况
- 轨道1: 2、5
- 轨道2:3、7
- 8应该放入轨道2。如果放入轨道1,接入后面一节车厢是6号,则必须启用一根新的轨道
// 把车厢in,放入缓冲轨道,buffer是存储缓冲轨道队列的数组,size表示缓冲轨道的数量
// 如果车厢能放入合适的缓冲轨道则返回true,否则返回false
bool putBuffer(linkQueue<int> *buffer, int size, int in) {
// 希望把车厢放到编号为avail的缓冲轨道上,max表示所有合适轨道队尾编号最大的车厢
int avail = -1, max = 0;
for (int j = 0 ; j < size; ++j) {
if (buffer[j].isEmpty()) { if (avail == -1) avail = j; }
else if (buffer[j].getTail() < in && buffer[j].getTail() > max) {
avail = j;
max = buffer[j].getTail();
}
}
if (avail != -1) {
buffer[avail].enQueue(in);
cout << in << "移入缓冲区 " << avail << endl;
return true;
} else {
cout << "无可行的方案" << endl;
return false;
}
}
checkBuffer
函数
// 检查能否将缓冲轨道上的车厢移动到输出轨道,last代表输出轨道上最后一列车厢的标号
void checkBuffer( linkQueue<int> *buffer, int size, int &last) {
// 表示需要检查缓冲轨道
bool flag = true;
while (flag) {
flag = false;
for (int j = 0; j < size; ++j) {
if (! buffer[j].isEmpty() && buffer[j].getHead() == last + 1) {
cout << "将" << buffer[j].deQueue() << "从缓冲区" << j << "移到出轨" << endl;
++last;
// 有车厢出轨后再次将flag设置为true
flag = true;
break;
}
}
}
}
练习
小组队列
有m个小组,n个元素,每个元素属于且仅属于一个小组。
支持以下操作:
push x:使元素x进队,如果前边有x所属小组的元素,x会排到自己小组最后一个元素的下一个位置,否则x排到整个队列最后的位置。
pop:出队,弹出队头并输出出队元素,出队的方式和普通队列相同,即排在前边的元素先出队。
对于全部测试数据,保证1 ≤ n ≤ 10^5,1 ≤ m ≤ 300,T ≤ 10^5,输入保证操作合法。
输入描述:
第一行有两个正整数n m,分别表示元素个数和小组个数,元素和小组均从0开始编号。
接下来一行n个非负整数Ai,表示元素i所在的小组。
接下来一行一个正整数T,表示操作数。
接下来T行,每行为一个操作。
输出描述:
对于每个出队操作输出一行,为出队的元素。
示例 1:
输入:
4 2
0 0 1 1
6
push 2
push 0
push 3
pop
pop
pop
输出:
2
3
0
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100005
#define M 305
using namespace std;
int n, m, t;
int team[N];
int q[M][N], l[M], r[M];
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++i) scanf("%d", &team[i]);
scanf("%d", &t);
char op[10];
int x;
while (t--) {
scanf("%s", op);
if (op[1] == 'u') {
scanf("%d", &x);
int tx = team[x];
if (l[tx] == r[tx]) q[m][r[m]++] = tx;
q[tx][r[tx]++] = x;
} else {
int tx = q[m][l[m]];
printf("%d\n", q[tx][l[tx]++]);
if (l[tx] == r[tx]) ++l[m];
}
}
return 0;
}
字符串
字符串定义
- 字符串是由零个或多个字符组成的有限序列,一般记为 s = “ a 0 a 1 a 2 … a n − 1 s=“a_0 a_1 a_2…a_{n-1} s=“a0a1a2…an−1”,n称为字符串的长度
- 将每个 a i a_i ai看成一个元素,则字符串可看成一个线性表。
- 字符串的特点:需要对整个表而不是表中元素进行操作
字符串操作
- 求字符串中字符的个数
length(s)
- 字符串输出
disp(s)
- 判断两个字符串大小
- 字符串赋值
copy(s1, s2)
- 字符串连接
cat(s1, s2)
- 取子串
substr(s, start, len)
- 字符串插入
insert(s1, start, s2)
- 删除子串
remove(s, start, len)
字符串存储
- 顺序存储
- 用字符数组存储字符串。
- C语言的处理方式:固定大小的数组(可以调用cstring库,处理字符串)
- C++的处理方式:动态改变数组大小(使用string类)
- 缺点:插入子串、删除子串的时间性能差。
- 用字符数组存储字符串。
- 链接存储
- 用单链表存储字符串。
- 缺点:空间问题,1个字符1个字节,1个指针可能要占4个字节
- 用块状链表存储字符串。
块状链表的概念和操作
概念: 块状链表的每个结点存放字符串中一段连续的字符,而不是单个字符。
- 优点:提高了空间的利用率
- 缺点:插入和删除时会引起数据的大量移动
- 改进方法:允许节点有一定的空闲空间(空间换时间)
块状链表删除:
- 分裂起始和终止结点
- 删除中间结点
- 归并起始和终止结点
块状链表插入:
- 分裂节点
- 插入子串
- 归并结点
练习
墓碑字符
考古学家发现了一座千年古墓,墓碑上有神秘的字符。经过仔细研究,发现原来这是开启古墓入口的方法。
墓碑上有两行字符串,其中第一个串的长度为偶数,现在要求把第二个串插入到第一个串的正中央,如此便能开启墓碑进入墓中。
输入描述:
第一行一个整数n,表示测试数据的组数。
接下来n组数据,每组数据两行。每行各一个字符串,且长度满足1 ≤ length ≤ 50,并且第一个串的长度必为偶数。
输出描述:
每组数据输出一个能开启古墓的字符串,每组输出占一行。
示例 1:
输入:
2
CSJI
BI
AB
CMCLU
输出:
CSBIJI
ACMCLUB
代码:
#include <iostream>
#include <string>
using namespace std;
int main() {
int t, len;
cin >> t;
while (t--) {
string a, b, ans;
cin >> a >> b;
len = a.length();
ans = a.substr(0, len / 2) + b + a.substr(len / 2);
cout << ans << endl;
}
return 0;
}