目录:
一.链式前向星
二.字典树(Trie)
三.01字典树
四:cin关闭流同步的与cout的endl使用
一. 链式前向星:
图的存储方法很多,最常见的除了邻接矩阵、邻接表和边集数组外,还有链式前向星
链式前向星是一种静态链表存储,用边集数组和邻接表相结合,
可以快速访问一个顶点的所有邻接点,在算法竞赛中广泛应用。
1.边集数组:
边集数组是由两个一维数组构成。一个是存储顶点的信息;
另一个是存储边的信息。
这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成。
性质:
边集数组关注的是边的集合,
在边集数组中要查找一个顶点的度需要扫描整个边数组,效率并不高。
因此它更适合对边依次进行处理的操作,而不适合对顶点相关的操作。
2.前向星:
前向星是一种特殊的边集数组,
我们把边集数组中的每一条边按照起点从小到大排序,
如果起点相同就按照终点从小到大排序,
并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,
那么前向星就构造好了.
3. 链式前向星存储包括两种结构:
边集数组:edge[],edge[i]表示第i条边;
头结点数组:head[],head[i]存以i为起点的第一条边的下标(在edge[]中的下标)
struct node{
int to, next, w;
}edge[maxe];//边集数组,边数一般要设置比maxn*maxn大的数,如果题目有要求除外
int head[maxn];//头结点数组
4.添加一条边u v w的代码如下:
void add(int u,int v,int w){//添加一条边
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
5.使用链式前向星访问一个结点u的所有邻接点:
for(int i=head[u];~i;i=edge[i].next){
int v = edge[i].to; //u的邻接点
int w = edge[i].w; //u—v的权值
…
}
6.完整代码:
#include<iostream>//创建无向网的链式前向星
#include<cstring>
using namespace std;
const int maxn = 100000 + 5;
int maxx[maxn], head[maxn];
int n, m, x, y, w, cnt;
struct Edge {
int to, w, next;
}e[maxn];
void add(int u, int v, int w) {//添加一条边u--v
e[cnt].to = v; //头插法
e[cnt].w = w; //每一个新节点的next始终指向头结点的next;
e[cnt].next = head[u]; //头结点的next始终指向新结点
head[u] = cnt++;
}
void printg() {//输出链式前向星
cout << "----------链式前向星如下:----------" << endl;
for (int v = 1; v <= n; v++) {
cout << v << ": ";
for (int i = head[v]; ~i; i = e[i].next) {
int v1 = e[i].to, w1 = e[i].w;
cout << "[" << v1 << " " << w1 << "]\t";
}
cout << endl;
}
}
int main() {
cin >> n >> m;
memset(head, -1, sizeof(head));
cnt = 0;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> w;
add(x, y, w);//添加边
add(y, x, w);//添加反向边
}
printg();
return 0;
}
二:字典树(Trie):
字典树(Trie)是一个比较简单的数据结构,也叫前缀树,用来存储和查询字符串。
其中每个字符占据一个节点,拥有相同前缀的字符串可以共用部分节点。
起始点是特殊点(我们设为1号点),不存储字符。
建树的代码如下:
const int MAXN = 500005;
int Next[MAXN][26], cnt; // 用类似链式前向星的方式存图,
//Next[i][c]表示i号点所连、存储字符为c+'a'的点的编号
void init() // 初始化
{
memset(Next, 0, sizeof(Next)); // 全部重置为0,表示当前点没有存储字符
cnt = 1;
}
void insert(const string& s) // 插入字符串
{
int cur = 1;
for (auto c : s){
// 尽可能重用之前的路径,如果做不到则新建节点
if (!next[cur][c - 'a'])
next[cur][c - 'a'] = ++cnt;
cur = next[cur][c - 'a']; // 继续向下
}
}
字典树可以方便地查询某个前缀是否存在:
bool find_prefix(const string& s) // 查找某个前缀是否出现过
{
int cur = 1;
for (auto c : s)
{
// 沿着前缀所决定的路径往下走,如果中途发现某个节点不存在,
//说明前缀不存在
if (!next[cur][c - 'a'])
return false;
cur = next[cur][c - 'a'];
}
return true;
}
洛谷P2580 于是他错误的点名开始了
对于每个教练报的名字,输出一行。
如果该名字正确且是第一次出现,
输出 OK,如果该名字错误,输出 WRONG,
如果该名字正确但不是第一次出现,输出 REPEAT。
#include <iostream>
using namespace std;
const int MAXN = 500005;
namespace trie
{//解决不同模块命名冲突的问题
int next[MAXN][26], cnt;// 用类似链式前向星的方式存图,
bool vis[MAXN], exist[MAXN];//next[i][c]表示i号点所连、存储字符为c+'a'的点的编号
void init() //next前一维相当于头结点,充当索引功能
{
memset(next, 0, sizeof(next));// 全部重置为0,表示当前点没有存储字符
cnt = 1;
}
void insert(const string& s)
{
int cur = 1;
for (auto c : s)
{// 尽可能重用之前的路径,如果做不到则新建节点
if (!next[cur][c - 'a'])
next[cur][c - 'a'] = ++cnt;
cur = next[cur][c - 'a'];
}
exist[cur] = true;// 继续向下
}
int find(const string& s)
{
int cur = 1, ans;
for (auto c : s)
{
if (!next[cur][c - 'a'])
return 0;
cur = next[cur][c - 'a'];
}
if (!exist[cur])
ans = 0;
if (!vis[cur])
ans = 1;
else
ans = 2;
vis[cur] = true;
return ans;
}
} // namespace trie
int main()
{
int n;
cin >> n;
trie::init();
while (n--)
{
string s;
cin >> s;
trie::insert(s);
}
int q;
cin >> q;
while (q--)
{
string s;
cin >> s;
switch (trie::find(s))
{
case 0:
cout << "WRONG" << endl;
break;
case 1:
cout << "OK" << endl;
break;
case 2:
cout << "REPEAT" << endl;
}
}
return 0;
}
三:01字典树
01字典树(01 - trie)是一种特殊的字典树,它的字符集只有{ 0,1 } ,
主要用来解决一些异或问题
(HDU4825 Xor Sum)
Zeus 和 Prometheus 做了一个游戏,
Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,
随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,
之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。
Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。
你能证明人类的智慧么?
思路:
数字化为二进制后,当作01串,从高位到低位像普通字典树那样存储
现在对于上面问题,我们贪心地解决即可。
如果我们要找与给定数异或最大的数,就尽可能走与该数当前位不同的路径。
反之则尽可能走与当前位相同的路径。
const int MAXN = 3200000, MAXBIT = 31;
int next[MAXN][2], cnt;
int num[MAXN];
void init()
{
memset(next, 0, sizeof(next));
memset(num, 0, sizeof(num));
cnt = 1;
}
void insert(int n)
{
int cur = 1;
for (int i = MAXBIT; i >= 0; --i)
{
int bit = n >> i & 1; // 求出当前位并插入
if (!next[cur][bit])
next[cur][bit] = ++cnt;
cur = next[cur][bit];
}
num[cur] = n;
}
int find_max(int x) // 找到与x异或最大的那个数
{
int cur = 1;
for (int i = MAXBIT; i >= 0; --i)
{
int bit = x >> i & 1;
if (next[cur][bit ^ 1]) // 优先走与当前位不同的路径
cur = next[cur][bit ^ 1];
else
cur = next[cur][bit];
}
return x ^ num[cur];
}
以上是int范围内的模板,并不能通过杭电这个题,因为它的数据范围大于unsigned int上界了,需要开long long。
四:cin关闭流同步的与cout的endl使用
在算法题中涉及到大量数据读入的时候,
通常建议大家避免使用cin读入数据而改用scanf,原因是scanf相对速度更快。
解决:
1. cin效率低的原因一是在于默认cin与stdin总是保持同步,
cin会把要输出的东西先存入缓冲区,进而消耗时间。通过关闭同步,
可以有效提高cin效率;
2.. 默认情况下cin绑定的是cout,每次执行<<的时候都要调用flush,
进而增加IO负担,因此可以通过tie(0)解绑。
实现:
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
注意:
如果在同一个流上使用两组 I/O 函数(头文件<stdio.h>/<cstdio>和<iostream>)
(例如stdin流同时关联cin与scanf),那么最好让它们保持同步;
如果任何一个流只使用一个 I/O 系列,则可以关闭同步
(比如在各自涉及单独的流的情况下,可以同时使用scanf与cin)。
3.每次使用endl,都要flush缓冲区,造成大量时间耗费。
推荐cout << << "\n"的写法。