YOU CAN DRINK ALL YOU LIKE, BUT IN THE MORNING YOU GET HEADACHE WITH THE SAME PROBLEMS.
0x13链表与邻接表
链表
为了避免双向链表在左右两端或者空链表中访问越界,我们通常建立额外的两个节点head
与tail
代表链表头尾,把实际数据节点存储在head
与tail
之间,来减少链表边界处的判断,降低编程复杂度
ACWING136.邻值查找
题目描述在文章末尾的原文链接中
解法一:链表
读入数据后串成链表,再对数组排序,此时找第N
个数的前驱和后继,比较N
与前驱、后继的差的绝对值,得到目标值后,再删除N
,继续找N-1
的前驱、后继,时间复杂度为O(NlogN)
#include
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n;
int l[N], r[N];
int p[N];
PII a[N], ans[N];
int32_t main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].first;
a[i].second = i;
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
l[i] = i - 1, r[i] = i + 1;
p[a[i].second] = i;
}
a[0].first = 0xffffffff;
a[n + 1].first = 0xffffffff;
for (int i = n; i > 1; i--) {
int j = p[i], left = l[j], right = r[j];
int lv = abs(a[left].first - a[j].first);
int rv = abs(a[right].first - a[j].first);
if (lv <= rv) ans[i] = {lv, a[left].second};
else ans[i] = {rv, a[right].second};
l[right] = left, r[left] = right;
}
for (int i = 2; i <= n; i++) cout <" " <endl;
}
解法二:平衡树
把A1 A2 A3 ... An
依次插入一个集合,则在插入Ai
之前,集合中保存的就是满足1<=J的所有
Aj
。根据题意,我们只需在集合中查找与Ai
最接近的值
若能维护一个有序集合,则集合中与Ai
最接近的值一定在Ai
的前驱与后继当中,比较前驱与后继与Ai
的差即可
而平衡二叉树就是一个支持动态插入、查询前驱以及查询后继的数据结构。在C++
中,STLset
也为我们提供了这些功能
邻接表
邻接表是树与图的一般化存储方式,还能用于实现开散列Hash
表。实际上,邻接表可以看成“带有索引数组的多个数据链表”构成的结构集合。在这样的结构中存储的数据会被分成若干类,每一类的数据构成一个链表,每一类还有一个代表元素,称为该类对应链表的表头。所有表头构成一个表头数组,作为一个可以随机访问的索引,从而可以通过表头数组定位到每一类数据对应的链表
在一个具有N
个点M
条边的有向图结构中,我们可以把每条边所属的“类别”定义为该边的起点标号。这样所有边被分为N
类,其中第X
类就由“从X
出发的所有边”组成。通过表头head[X]
,我们很容易定位到第X
类对应的链表,从而访问从点X
出发的所有边
对于无向图,我们把每条无向边看作两条有向边插入即可。有一个小技巧是,结合成对变换的位运算性质,我们可以在程序最开始时,把第一条边存储在2 3
位置上,这样每条边都会存储在2 3 4 5
这样的位置上。通过对下标进行XOR 1
的运算,就可以直接定位到与当前边反向的边。换句话说,如果ver[i]
时第i
条边的终点,那么ver[i xor 1]
时第i
条边的起点
![9e85b48233f4bf8f18d5c33b31c5add7.png](https://img-blog.csdnimg.cn/img_convert/9e85b48233f4bf8f18d5c33b31c5add7.png)
![9545eef1fcda033d4017711c195ef2d8.png](https://img-blog.csdnimg.cn/img_convert/9545eef1fcda033d4017711c195ef2d8.png)