上海交大ACM班C++算法与数据结构——数据结构之队列

上海交大ACM班C++算法与数据结构——数据结构之队列

大纲

1.队列的定义

  • 先进先出的线性表
  • 基本操作
    - 创建
    - 入队
    - 出队
    - 读取
    - 判断是否为空
  • 虚构类实现
		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() {}                          //虚析构函数
		};

2.顺序实现

  • 循环队列:头指针、尾指针都可以移动,既不会浪费空间(数据固定,出队时头指针移动),也不需要移动大量元素(头指针固定,出队时数据移动)
    循环队列
  • 类定义
		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调整到数组最前端	 
			front = 0; 
			rear = maxSize - 1;
			maxSize *= 2;
			delete  [] tmp;
		} 
  • 例题

    新学期开始了,小哈是小哼的新同学,小哼向小哈询问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; 
		}

3.链接队列

  • 不带头结点的单链表(同时记录头尾结点的位置)
  • 队头:表头,表头插入和删除都方便,表尾不方便删除
    队尾:表尾
    链接队列
  • 类定义
		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;
		}
  • 性能分析:所有操作的时间复杂度都是O(1)

4.列车车厢重排问题

一列货运列车共有n节车厢,每节车厢将被放在不同的车站。假定n个车站的编号分别为1 – n,货运列车按照第n站到第1站的次序经过这些车站。车厢的编号与它们的目的地相同。为了便于从列车上卸掉相应的车厢,必须重新排列这些车厢,将第n节车厢放在最后,第1节车厢放在最前面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tfbijr21-1651345240940)(http://jiang-yvhang.gitee.io/boke/C++AlgorithmsAndDataStructures/photo/53列车车厢重拍.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;   
            }
        }
    }
}
  • 生成[a,b]间均匀分布的随机数rand*(b-a+1)/(RAND_MAX+1)+a(分成均匀的单位段)

5.例题

小组队列
有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; 
		}

6.块状链表

  • 概念:链表结点内顺序存储(每个结点存放字符串中一段连续的字符,而不是单个字符)
    块状链表

  • 优点:提高了空间的利用率(加大了数据的比例,减小了指针的比例,每个指针要占用4个字节的空间)

  • 缺点:插入和删除时会引起数据的大量移动

  • 改进方法:允许节点有一定的空闲空间(空间换时间)

  • 应用:字符串处理(C中用固定大小的数组处理字符串,常常会因为超出数组大小出现不可预知的错误,C++ 中用大小可变的空间处理字符串)

  • 例题

    定义字符串的基本操作为三种:删除一个字符、插入一个字符和将一个字符修改成另外一个字符。将字符串S1变成字符串S2的最少操作步数,称为字符串S1到字符串S2的编辑距离。字符串ABCDEFG到字符串BADECG的编辑距离为:

    3(首先,删除A得到BCDEFG;接着,替换C为A得到BADEFG;最后,替换F为C得到BADECG。)

    题目
    答案

    题目
    题目

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

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

    输入描述:

    第一行一个整数n,表示测试数据的组数。

    接下来n组数据,每组数据两行。每行各一个字符串,且长度满足1 ≤ length ≤ 50,并且第一个串的长度必为偶数。

    输出描述:

    每组数据输出一个能开启古墓的字符串,每组输出占一行。

    示例 1:
    输入:
    2
    CSJI
    BI
    AB
    CMCLU
    输出:
    CSBIJI
    ACMCLUB

    提示:substr函数:复制子字符串,要求从指定位置开始,并具有指定的长度。没指定长度或指定长度无效,则默认到源字符串的结尾

		#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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿航626

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值