0
点赞
收藏
分享

微信扫一扫

C++算法与数据结构_20

RockYoungTalk 2022-04-24 阅读 86

队列的定义

队列的概念

  • 队列是一种常用的线性结构,到达越早的结点,离开的时间越早。
  • 只能从对队尾入队,从队头出队。
  • 所以队列通常称之为先进先出(FIFO: First In First Out)队列。

LWevSx.png

队列的基本操作

  • 创建一个队列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个 ,下标的范围从0MaxSize-1
  • 顺序队列有三种组织方式:
    • 队头位置固定:头元素下标为0,并指定尾元素,当队列为空时尾元素被指定为-1。出队会引起大量数据移动。

      LWnp3q.png

    • 队头位置不固定: 队首指针front指示队首结点的前一位置,队尾指针rear指示队尾结点存放的下标地址,初始化将两指针均设为-1,队满时rear=Maxsize-1。浪费空间。

      LWn9g0.png

    • 循环队列 : 依旧一个front和一个rear,牺牲一个单元规定front指向的单元不能存储队列元素,队列满的条件是:(rear+1)%MaxSize == front。这是相对而言更合理的一种顺序队列的实现方式。

      LWnCvV.png

循环顺序队列类定义

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

链接队列

链接队列的概念和设计

  • 采用不带头结点的单链表
  • 单链表的表头作为队头,单链表的表尾作为队尾
  • 同时记录头尾结点的位置。

LWYoCT.png

链接队列类定义

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节车厢放在最前面。

LWrreU.png

实现思想

  • 每条缓冲轨道用一个队列模拟。
  • 处理过程如下:
依次取入轨的队头元素,直到队列为空
    进入一条最合适的缓冲轨道
    检查每条缓冲轨道的队头元素,将可以放入出轨的元素出队,进入出轨

解决方案实现

  • 模拟过程:
// 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} sa0a1a2an1”,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个字节
    • 用块状链表存储字符串。

块状链表的概念和操作

概念: 块状链表的每个结点存放字符串中一段连续的字符,而不是单个字符。

LfIXYq.png

  • 优点:提高了空间的利用率
  • 缺点:插入和删除时会引起数据的大量移动
  • 改进方法:允许节点有一定的空闲空间(空间换时间)

块状链表删除:

  • 分裂起始和终止结点

LfoCm4.png

  • 删除中间结点

LfIzlT.png

  • 归并起始和终止结点

LfIjf0.png

块状链表插入:

  • 分裂节点

LfoS6U.png

  • 插入子串

LfIxpV.png

  • 归并结点

LfopXF.png

练习

墓碑字符

考古学家发现了一座千年古墓,墓碑上有神秘的字符。经过仔细研究,发现原来这是开启古墓入口的方法。

墓碑上有两行字符串,其中第一个串的长度为偶数,现在要求把第二个串插入到第一个串的正中央,如此便能开启墓碑进入墓中。

输入描述:

第一行一个整数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;
}
举报

相关推荐

0 条评论