Day 3:新知——并查集
学习笔记
一、概念
- 概念:用来表示不相交集合的数据结构,处理不相交集合的合并和查询问题。每个集合通过代表来区分。
- 操作:
(1)FindSet (x)
用来查找元素 x 属于哪个集合,返回集合的代表。
(2)UnionSet (x, y)
如果 x 、 y 属于不同集合,则将 x 、 y 所在集合进行合并,否则不进行任何操作/ - 实现方法:有根树表示集合。
二、基本操作
- 初始化
MakeSet
用father[i]
表示i
的父结点。
void MakeSet () {
for (int i = 1; i <= n; i ++) {
father[i] = i;//若父节点与自己相同,则为根结点。
}
}
- 查询
FindSet
int FindSet (int x) {
if (father[x] == x) {
//同上注释
return x;
} else {
return FindSet (father[x]);
}
}
- 合并
UnionSet
void UnionSet () {
if (FindSet (x) == FindSet (y)) {
//如果x、y在同一集合,不用合并。
return;
}
father[FindSet (x)] = FindSet[y];//接在y的根结点上。
}
三、并查集优化1——路径压缩
int FindSet (int x) {
if (father[x] == x) {
//同上注释
return x;
} else {
return father[x] = FindSet (father[x]);//直接连到根结点。
}
}
四、并查集优化2——按秩合并(启发式合并)
用rank[i]
维护以i
结点为根的子树的深度。
void UnionSet () {
int a = FindSet (x), b = FindSet (y);
if (a == b) {
//如果x、y在同一集合,不用合并。
return;
}
if (rank[a] <= rank[b]) {
//如果b树比a树深,把a接在b后
father[a] = b;
} else {
//否则把b接在a后
father[b] = a;
}
if (rank[a] == rank[b]) {
//如果a和b一样深,根据刚才的语句是接在b后的,则b的深度要加1
rank[b] ++;
}
}
五、带权并查集(边带权并查集)
六、种类并查集(扩展域并查集)
Day 5:新知——树状数组
学习笔记
一、概念
树状数组(Binary Indexed Tree
,简称BIT
),是一个区间查询和单点修改时间复杂度降为 Θ ( log n ) \Theta (\log n) Θ(logn)的数据结构,主要用于查询任意两点之间所有元素之和。
二、问题的提出
一个一维数组,长度为 n n n,接下来要对这个数组进行两种操作:
- 修改,对 i i i~ j j j之间的元素增加 x x x;
- 求和,求第 i i i个元素到第 j j j个元素的和。
三、解决办法
办法一:前缀和
优点:求和: Θ ( 1 ) \Theta (1) Θ(1)。
缺点:修改: Θ ( n ) \Theta (n) Θ(n)。
办法二:树状数组
重点:lowbit (x)
函数
求把 x x x转化成二进制后,取末尾的 1 1 1和后面的 0 0 0,再转化成十进制的值。
写法:
int lowbit (int x) {
return x & -x;
}
bit[i]
数组就是上图中的C[i]
数组,按照上图规律:
bit[1] = a[1]
bit[2] = bit[1] + a[2]
bit[3] = a[3]
bit[4] = bit[2] + bit[3] + a[4]
...
重点:update (k, x)
函数:将第k个元素的值加x。
void update (int k, int x) {
for (int i = k; i <= n; i += lowbit (i)) {
//由上图易得,第i个元素+lowbit (i)即为它的上级元素
bit[i] += x;
}
}
重点:sum (k)
函数:求第k个元素的值。
int sum (int k) {
int ans = 0;
for (int i = k; i > 0; i -= lowbit (i)) {
//累加差分(bit)数组即为原数
ans += bit[i];
}
return ans;
}
四、离散化
Q:为什么要离散化?
A:在某些时刻,数据较大时且只需知道元素的位置而元素的值无关紧要时,可以使用离散化来简化数据的强度。
离散化方法一:用数组进行离散化
struct node {
int val, id;
bool operator < (const node x) const {
return val < x.val;
}
}
......
for (int i = 1; i <= n; i ++) {
scanf ("%d", &a[i].val);
a[i].id = i;
}
sort (a + 1, a + n + 1);
b[a[i].id] = i;
离散化方法二:用STL
+二分离散化
#include <algorithm>
using namespace std;
int a[MAXN], lsh[MAXN], cnt, n;
......
for (int i = 1; i <= n; i ++) {
scanf ("%d", &a[i]);
lsh[i] = a[i];
}
sort (lsh + 1, lsh + n + 1);//排序
cnt = unique (lsh + 1, lsh + n + 1) - lsh - 1;//去重
for (int i = 1; i <= n; i ++) {
a[i] = lower_bound (lsh + 1, lsh + cnt + 1, a[i]) - lsh;//返回坐标
}
Day 8:新知——哈希Hash
表
学习笔记
一、Hash函数
指可以根据关键字直接计算出元素所在位置的函数。
二、哈希表
根据设定的哈希函数 Hash(key)
和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的 “象” 作为记录在表中的存储位置,这种表便称为哈希表,这一映象过程称为哈希造表或散列,所得存储位置称为哈希地址或散列地址。
三、冲突
- 定义:不同的元素占用同一个地址的情况叫做冲突。
- 发生冲突的因素
(1) 装填因子 α \alpha α
装填因子是指哈希表中己存入的元素个数 n n n 与哈希表的大小 m m m 的比值,即 α = n m α=\frac{n}{m} α=mn。 α α α越小,发生冲突的可能性越小,反之,发生冲突的可能性就越大。但是, α α α太小又会造成大量存贮空间的浪费,因此必须兼顾存储空间和冲突两个方面。
(2)所构造的哈希函数
构造好的哈希函数,使冲突尽可能的少。
(3)解决冲突的方法
设计有效解决冲突的方法 。.
四、Hash函数的构造方法
-
直接定址法
取关键字或关键字的某个线性函数值为散列地址,即Hash(K)=K
或Hash(K)=a * K + b
(其中 a a a、 b b b为常数)。
优点:以关键码key
的某个线性函数值为哈希地址,不会产生冲突。
缺点:要占用连续地址空间,空间效率低。 -
除后余数法 (常用)
取关键字被不大于散列表表长 m m m 的数 p p p 除后所得的余数为哈希函数。即
H a s h ( K ) = K m o d p ( p ≤ m ) Hash(K) = K \mod p (p≤m) Hash(K)=Kmodp(p≤m)ps:经验得知,一般可选 p p p为质数 或 不包含小于 20 20 20的质因子的合数。例如:
131, 1331, 13331, ...
-
平方取中法
取关键字平方后的中间几位为哈希函数。因为中间几位与数据的每一位都相关。
例: 2589 2589 2589的平方值为 6702921 6702921 6702921,可以取中间的 029 029 029为地址。 -
数字分析法
选用关键字的某几位组合成哈希地址。
选用原则应当是:各种符号在该位上出现的频率大致相同。 -
折叠法
是将关键字按要求的长度分成位数相等的几段,最后一段如不够长可以短些,然后把各段重叠在一起相加并去掉进位,以所得的和作为地址。
适用于:每一位上各符号出现概率大致相同的情况。
具体方法:
移位法:将各部分的最后一位对齐相加(右对齐)。
间接叠加法:从一端向另一端沿分割界来回折叠后,最后一位对齐相加。
例:元素 42751896 42751896 42751896,
移位法: 427 + 518 + 96 = 1041 427+518+96=1041 427+518+96=1041
间接叠加法: 42751896 − > 724 + 518 + 69 = 1311 427 518 96 -> 724+518+69 =1311 42751896−>724+518+69=1311 -
随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即Hash (key) = random (key)
其中random
为随机函数(random是C语言函数)。
通常,当关键字长度不等时采用此法构造哈希函数较恰当。
rand ()
: 取随机数,以默认种子1来生成,只要种子一样,无论何时何地生成的随机数都一样。
srand (x)
: 将随机数的种子改为 x x x。
time (0)
: 获取当前时间,因为时间一直在变化,所以随机数的值也在变化。
参考代码:
#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;
int main () {
srand (time (0));
printf ("%d\n", rand ());
return 0;
}
- 建立
Hash ()
函数通常考虑的因素
(1)计算哈希函数所需时间(包括硬件指令的因素);
(2)关键字的长度;
(3)哈希表的大小;
(4)关键字的分布情况;
(5)记录的查找频率。
五、处理冲突的办法
-
开放地址法
开放地址就是表中尚未被占用的地址,当新插入的记录所选地址已被占用时,即转而寻找其它尚开放的地址。
(1) 线性探测法
设散列函数Hash (K) = K mod m
( m m m为表长),若发生冲突,则沿着一个探查序列逐个探查(也就是加上一个增量),那么,第i次计算冲突的散列地址为:
H i = ( H ( K ) + d i ) m o d m ( d i = 1 , 2 , … , m − 1 ) H_i = (H(K)+d_i) \mod m (d_i=1,2,…,m-1) Hi=(H(K)+di)modm(di=1,2,…,m−1)
优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素;
缺点:可能使第 i i i个哈希地址的同义词存入第 i + 1 i+1 i+1 个哈希地址,这样本应存入第 i + 1 i+1 i+1个哈希地
址的元素变成了第 i + 2 i+2 i+2个哈希地址的同义词,……,因此,可能出现很多元素在相邻的哈希
地址上“堆积”起来,大大降低了查找效率。
(2) 二次探测法
二次探测法对应的探查地址序列的计算公式为:
H i = ( H ( k ) + d i ) m o d m H_i = ( H(k) + d_i ) \mod m Hi=(H(k)+di)modm
其中 d i = 1 2 , − 1 2 , 2 2 , − 2 2 , … , j 2 , − j 2 ( j ≤ m / 2 ) d_i =1^2,-1^2,2^2,-2^2,…,j^2,-j^2 (j≤m/2) di=12,−12,22,−22,…,j2,−j2(j≤m/2)。 -
链地址法
基本思想:
将具有相同哈希地址的记录链成一个单链表,m个哈希地址就设 m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。
优点:插入、删除方便。
缺点:占用存储空间多。 -
再哈希法
基本思想:
H i = R H i ( k e y ) ( i = 1 , 2 , 3 , … … , k ) 。 H_i= RH_i(key) (i=1,2,3,……,k)。 Hi=RHi(key)(i=1,2,3,……,k)。
其中, R H i ( ) RH_i() RHi() 均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。
优点:不易产生“聚集”。
缺点:增加了计算时间。
- 建立一个公共溢出区
基本思想:
假设哈希函数的值域为 [ 0 , m − 1 ] [0,m-1] [0,m−1],则设向量 H a s h T a b l e [ 0 , m − 1 ] HashTable[0,m-1] HashTable[0,m−1]为基本表。在此基础上,再建立一个溢出表,在之后的哈希操作中,无论关键字的同义词生成怎样的哈希地址,一旦发生冲突,就将其放入溢出表中。
Day 10:新知——图的概念、结构和遍历
学习笔记
一、定义
图(graph),用来存储某些具体事物和这些事物中的联系。
图由顶点(vertex)——具体事物和边(edge)——联系组成
顶点集合为 V V V,边的集合为 E E E,图表示为 G = ( V , E ) G=(V,E) G=(V,E)
二、种类
- 无向图:边没有指定方向的图。
- 有向图:边具有指定方向的图。
注:有向图所连的边也叫做弧,一条边起点为弧头,终点为弧尾。 - 带权图:边上带有权值的图。
三、无向图的术语
- 两个顶点之间有边连接,则称两个顶点相邻。
- 路径:相邻顶点的序列。
- 圈:起点与终点重合的路径。
- 度:顶点连接边的条数。
- 树:没有圈的连通图。
- 森林:没有圈的非连通图。
四、有向图的术语
- 在有向图中,边是单向的,它们的邻接性是单向的。
- 有向路径:相邻顶点的序列。
- 有向环:一条至少含有一条边且起点和终点相同的路径。特别地,自环(见下图)
- 有向无环图(DAG):没有环的有向图。
- 度:一个顶点的出度和入度之和即为该顶点的度。
(1) 入度:以顶点为弧尾的边的数量。
(2) 出度:以顶点为弧头的边的数量。
五、图的表示
-
邻接矩阵
对于一个有 V V V个顶点的图而言,使用 V × V V \times V V×V的二维矩阵表示
G i , j = 1 G_{i,j}=1