文本编辑器-题解
内存限制: 256 Mb时间限制: 1000 ms
题目背景
文本编辑器是一种常见的软件,开发高效的文本编辑器是一件相当困难的事情。解决本题需求的数据结构被称为 Gap buffer,是一种非常短小精悍的数据结构。
题目描述
文本编辑器用于编辑文本。开始时,编辑器里记录的文本是空的,为了定位操作的位置,编辑器还提供了一个光标。光标是指向文本的字符间隙的一个标记。
给定 mm 条修改操作,请模拟编辑器在这些操作下的行为,最后输出编辑器所记录的文本内容:
前进操作:编辑器将光标向右移动一格,如果光标已经在最右端,则忽略这步操作;
后退操作:编辑器将光标向左移动一格,如果光标已经在最左端,则忽略这步操作;
插入操作:该操作还需要提供一个字符参数 ch,编辑器在光标的左侧插入该字符;
删除操作:文本编辑器将删去光标右侧的字符,若光标右侧没有字符,则忽略这步操作。
输入格式 第一行:单个整数 m;第二行到第 m+1 行:每行表示一个操作;
前进操作仅有一个字母 f。后退操作仅有一个字母 b;删除操作仅有一个字母 d;插入操作以字母 i 开头,后接一个字母,保证该字母是一个大写的英文字母, i 和该字母中间用一个空格分隔。输出格式 单个字符串:表示编辑器最后所记录的文本内容。
数据范围
对于 30% 的数据,l≤m≤500;
对于 60% 的数据,1≤m≤50000;
对于 %100% 的数据,1≤m≤500000。
样例数据 输入:
5
i H
i E
i L
i L
i O
输出:
HELLO
输入:
6
f
b
i A
b
i B
d
输出:
B
说明: 第一步的f和第二步的b都没有作用,接下来,每步结束后文本和光标的效果为:A| |A B|A B|
分析
在不考虑时间的情况下: 字符串操作, 用字符数组来做, 删除和插入会设计大量的数据的移动, 容易出错, 可以使用string,它支持插入 删除 拼接更方便些. 不过, 还是练习下数组吧.
需要pos表示当前光标位置,还有end表示字符数组的结尾.
光标和字符的下标都是pos点, 那么删除和插入的左右两边怎么区分?
一个判断方法, 光标在pos点,pos点的字符是否可以删除,可以删除的话, 则光标表示pos点字符的左边.
考虑到初始状态, 一个都没输入时, pos 和 end都是 0, 这时pos指向的是待插入字符的位置. 这时pos == end, 表示pos和end之间为空, 删除时,也没有字符可以删除. 插入时, 直接在pos点插入字符.
所以pos最小值和初始值都为0, 表示待插入字符的位置.如果已经有有效字符, 则字符需要后移一位. 删除时, 把pos点的字符删掉, 后面的字符前移一位.
pos最大值为end
end表示字符结束表示为'\0', 其值等于长度.
这里因为没有说明换行符是\r\n, 还是\n, 所以没敢用getchar()
#include
using namespace std;
const int MAXN = 500005;
char a[MAXN], op[2], ch[2];
int n, pos, end;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%s", op);
switch(op[0]) {
case 'f':
if(pos < end)
pos++;
break;
case 'b':
if(pos > 0)
pos--;
break;
case 'd':
for(int j = pos; j < end - 1; j++)
a[j] = a[j+1];
if(end > pos)
end--;
break;
case 'i':
scanf("%s", ch);
for(int j = end - 1; j >= pos; j--)
a[j+1] = a[j];
a[pos] = ch[0];
pos++;
end++;
break;
}
}
for(int i = 0; i < end; i++)
printf("%c", a[i]);
printf("\n");
return 0;
}
string
C++ 标准库提供了 string 类类型 String类:按照类的方式进行动态管理字符串
底层:是一种顺序表的结构,元素是char类型的字符
访问
str1[i];
长度
str1.length();
str1.size();
insert函数方法:
str.insert(2,3,'A')——在str下标为2的位置添加 3个 字符'A'
str.insert(2,"ABC")——在str下标为2的位置添加 字符串 "ABC"
str.insert(2,"ABC",1)——在str下标为2的位置添加 字符串 "ABC" 中 1个 字符
erase(下标)
str.erase(2)——删除 下标2 的位置开始,之后的全删除
str.erase(2,1)——删除 下标2 的位置开始,之后的 1个 删除
其他函数请参看以前讲义.
cin >> char 会自动过滤空白字符.
#include
using namespace std;
int n, pos;
string a;
char op, ch[2];
int main() {
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> op;
switch(op) {
case 'f':
if (pos < a.size())
pos++;
break;
case 'b':
if (pos > 0)
pos--;
break;
case 'i':
cin >> ch;
a.insert(pos, ch, 1);
pos++;
break;
case 'd':
a.erase(pos, 1);
break;
}
}
cout << a;
return 0;
}
考虑到时间
对于 %100% 的数据,1≤m≤500000。比较坏的一种情况是 200000个插入, 200000个后退,光标回到0点, 然后100000个插入操作. 那么每次都需要200000+的字符向后移动一位.需要操作200000*100000个后移操作. 这样会超时. 所以可以用链表操作可以避免这个问题.
STL list
list 由双向链表(doubly linked list)实现而成,元素也存放在堆中,每个元素都是放在一块内存中,他的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随机存取变得非常没有效率,因此它没有提供 [] 操作符的重载。但是由于链表的特点,它可以很有效率的支持任意地方的插入和删除操作。
头文件
#include
定义
list<int> a; // 定义一个int类型的列表a
基本操作
大小
容器大小:
lst.size();
容器最大容量:
lst.max_size();
容器判空:
lst.empty();
添加函数
头部添加元素:
lst.push_front(const T& x);
末尾添加元素:
lst.push_back(const T& x);
任意位置插入一个元素:
lst.insert(iterator it, const T& x);
list<int> lst;
// 头部增加元素
lst.push_front(4);
// 末尾添加元素
lst.push_back(5);
// 任意位置插入一个元素
list<int>::iterator it = lst.begin();
lst.insert(it, 2);
删除函数
头部删除元素:
lst.pop_front();
末尾删除元素:
lst.pop_back();
任意位置删除一个元素:
lst.erase(iterator it);
清空所有元素:
lst.clear();
迭代器
开始迭代器指针:
lst.begin();
末尾迭代器指针:
lst.end();
反向迭代器指针,指向最后一个元素:
lst.rbegin();
反向迭代器指针,指向第一个元素的前一个元素:
lst.rend();
遍历元素
list::iterator it;for (it = lst.begin(); it != lst.end(); it++)
cout << *it << endl;
#include
using namespace std;
int n;
char op, ch;
list<char> a;
list<char>::iterator it;
int main() {
ios::sync_with_stdio(false);
cin >> n;
it = a.begin();
for(int i = 1; i <= n; i++) {
cin >> op;
switch(op) {
case 'f':
if (it != a.end())
it++;
break;
case 'b':
if (it != a.begin())
it--;
break;
case 'i':
cin >> ch;
a.insert(it, ch);
break;
case 'd':
if(it != a.end())
it = a.erase(it);
break;
}
}
for (it = a.begin(); it != a.end(); it++) {
cout << *it;
}
return 0;
}
数组实现list
使用int next[N]数组指向下一个字符. 将所有插入的数据一次放入 char a[N]中.
插入时:
cin >> a[i];
next[i] = next[pos];
next[pos] = i;
删除时:
next[pos] = next[next[pos];
代码略.
参考题目:
UVA11988 破损的键盘 Broken Keyboard (a.k.a. Beiju Text)
难度提升:
这种题目可以变的比较难 比如:
P4008 [NOI2003]文本编辑器
云帆优培公众号:
云帆优培老师联系方式:
云帆老师
微信:
云帆优培介绍