【学习笔记+习题集】字符相关(输入输出流,字典树)(3116字)(不定期更新)

目录

板块零:输入输出流

情况一:读取字符串和读取行混用的时候

情况二:关于识别空行

第一题:hdoj2072

情况三:用char数组接受getline函数的输入流

情况四:关于汉字

补充练习:

第一题:单词反转(对行,pe地狱)

情况五:关于getchar()读取到文件尾

第一题:排序 

板块一:字典树

注意:

操作1:建树

操作2:查找


板块零:输入输出流

阴间的输入和输出。

情况一:读取字符串和读取行混用的时候

#include 
using namespace std;
int main() {
	string a, b;
	cin >> a;
	cin.get();//清除回车,否则下面的getline会因为cin残留的回车符直接结束,输出回车
	getline(cin, b);
	cout << a << '\n';
	cout << b << '\n';
	return 0;
}

cin会残留回车符,就像scanf一样。

情况二:关于识别空行

#include 
using namespace std;
int main() {
	string a;
	getline(cin, a);
	if (a == "") {
		cout << "YES\n";
	} else {
		cout << "NO\n";
	}
	return 0;
}

 getline对空行畅行无阻,并不会停下来,不会异常返回。

专治阴间输入输出流练习题:

第一题:hdoj2072

注意坑点可能有多个空格,主要这里是每行一组,以#结束。

#include 
using namespace std;
int main() {
	char ch;
	set s;
	string a;
	int flag = 0;//如果前面有空格的话
	while ((ch = getchar()) != '#') {//如果不是#
		if (ch <= 'z' && ch >= 'a') {
			if (flag) {//如果后面有字母,而且,前面是有空格
				s.insert(a);
				flag = 0;
				a = "";
			}
			a += ch;
		} else if (ch == ' ') {//如果最开始就是空格
			if (a != "") {//而且,a不是空串,flag等于1
				flag = 1;
			}
		} else if (ch == '\n') {//如果读到回车
			if (a != "") {//而且还有一个串
				s.insert(a);
				a = "";
				flag = 0;//注意置零以防对下一组数据产生影响
			}
			cout << s.size() << '\n';
			s.clear();//清空
		}
	}
	return 0;
}

情况三:用char数组接受getline函数的输入流

getline()函数是只能接受string类型的。 不信可以试试:p

#include 
#include 
using namespace std;
int main() {
	string a;
	char b[1000];
	getline(cin, a);
	return 0;
}

情况四:关于汉字

汉字占两个char,而且,两个char变量的值均为负数。

汉字统计

#include 
using namespace std;
const int MAX = 1e6 + 5;
char s[MAX];
int main() {
	int t;
	cin >> t;
	cin.get();
	while (t--) {
		gets(s + 1);
		long long ans = 0;
		for (int i = 1; i < strlen(s + 1); i++) {
			if (s[i] >= 0 && s[i] <= 127) continue;
			ans++;
			i++;
		}
		cout << ans << '\n';
	}
	return 0;
}

补充练习:

第一题:单词反转(对行,pe地狱)

#include 
using namespace std;
int main() {
	int t;
	cin >> t;
	getchar();
	while (t--) {
		string s = "";
		char ch;
		while ((ch = getchar()) != '\n') {
			if (ch == ' ') {
				for (int i =s.size() - 1; i >= 0; i--) {
					if (s[i] == ' ') continue;//小心空格
					cout << s[i];
				}
				cout << ' ';
				s = "";
			}
			s += ch;
		}
		for (int i = s.size() - 1; i >= 0; i--) {
			if (s[i] == ' ') continue;//小心空格
					cout << s[i];
		}
	    cout << '\n';
	}
	return 0;
} 

情况五:关于getchar()读取到文件尾

第一题:排序 

注意while ((ch = getchar()) != EOF) ch和getchar之间需要括号,否则优先级会出问题。

#include 
using namespace std;
int main() {
//	ios::sync_with_stdio(false);
//	cin.tie(0);
	priority_queue, greater > q;
	char ch;
	int flag = 0, sum = 0; 
	while ((ch = getchar()) != EOF) {
		if (ch != '5' && ch != '\n') {
			flag = 1;
		}
		if (flag && ch != '5' && ch != '\n') {
			sum = 10 * sum + ch - '0';
		}
		if (flag && ch == '5') {
			flag = 0;
			q.push(sum);
			sum = 0;
		}
		if (ch == '\n') {
			if (flag) {
				q.push(sum);
				sum = 0;
				flag = 0;
			}
			int c = q.top();
			q.pop();
			cout << c;
			while (!q.empty()) {
				c = q.top();
				q.pop();
				cout << ' ' << c;
			}
			cout << '\n';
		}
	}
	return 0;
}

板块一:字典树

字典树是一种前缀树。有邻接矩阵版本,指针链表版本和结构体版本(大概)。

注意:

关于数组开多大的问题,注意,这种方法的空间消耗很大。最大的空间不是最长子串而是所有的字母数,即单词数 * 最长单词长度,这样才稳妥不会RE。

操作1:建树

板子:

void insert(string s) {
	int p = 0;
	for (int i = 0; i < s.size(); i++) {
		int x = s[i] - 'a';
		if (trie[p][x] == 0) trie[p][x] = ++id;
		p = trie[p][x];
	}
	cnt[p]++;
}

trie是一个二维数组,不是字符数组,有点类似邻接矩阵。其中一维是26个字母的标号,第二维是节点的编号用变量p表示,id是一个编号,用来表示单词的先后关系,用来说明此处是否有单词。cnt数组用来统计此处的单词数目。

这个id的使用是关键,当trie走到零的时候,说明这是一个之前没有走到过的一个新的节点,id++,输入第一个单词的时候,比如“abc”id是变化是1 2 3 然后我们再次输入 cba的话,id 变化是 4 5 6但是如果输入的是a b d的话,则是输入a的时候会索引到“ab”的b的p是2,然后到d的时候p到了3,此时是d然后出现了一个新的编号7

下面是p右边是x 0(a) 1(b) 2(c) 3(d)
0 id = 1 1 4
1(p = id) 2
2 3   7
3
4 5
5 6
6
7

三种颜色表示了三次输入,填表的过程。id是独立且唯一的,我们给前缀不同的单词进行标号,每当出现一种新的前缀就给一个新的编号。这种写法感觉空间的浪费还是有点大的。

操作2:查找

int find(string s) {
	int p = 0;
	for (int i = 0; i < s.size(); i++) {
		int x = s[i] - 'a';
		if (trie[p][x] == 0) return 0;
		p = trie[p][x];
	}
	return cnt[p];
}

和建树基本相同,

你可能感兴趣的:(学习)