一、链表:
和数组都可以用于存储数据,其中链表通过指针来连接元素,而数组则是把所有元素按照次序存储。
数组和链表有不同的优势:
链表:可以方便地删除插入数据,操作次数为O(1),但寻找读取据的效率低,在随机访问数据中的操作次数为O(n)。
数组:可以方便地寻找读取数据,在随机访问中操作次数为O(1),但删除插入的操作次数为O()
1.单向链表:
包含数据域和指针域。
struct node{
int value;
node *next;
}
(1)插入元素:
在p与p->next之间插入一个数据x:
x->next=p->next;
p->next=x;
(2)删除元素:
删除节点p的数据:
将p->next的数据覆盖到p上
p->value=p->next->value;
p->next=p->next->next;
2.双向链表:
同样具有数据域和指针域,不同之处在于指针域有左右之分。
struct node{
int val;
node *left;
node *right;
}
(1)插入元素:
在p和p->right之间插入元素x:
x->right=p->right;
x->left=p;
p->right->left=x;
p->right=x;
(2)删除元素:
删除节点p:
p->left->right=p->right;
p->right->left=p->left;
【例题】办公楼
有n个职员,有m对职员之间有联系。若两个职员之间有联系,他们就可以被安置在两座办公楼内。问最多能将这n个职员安排在几座办公楼内。
N<=100000,m<=2000000
两个点若没有联系即连边,建立原图的补图。
二、哈希表
又称散列表,一种以"key-value"形式存储数据的数据结构。所谓以"key-value"形式存储数据,是指任意的key都唯一对应到内存中的某个位置,只需要输入查找的值key,就可以快速地找到其对应的value。可以把哈希表理解成一种高级的数组,这种数组的下表可以是很大的整数,浮点数,字符串甚至结构体。
1.哈希函数:
要让key对应到内存中的位置,就要为key计算索引,也就是计算这个数据应该放在哪里,根据key计算索引的函数叫做哈希函数,也称散列函数。哈希函数应当易于计算,且尽量使计算出来的索引均与分布。
2.常见处理方式:
在OI中,最常见的情况应该是key为整数的情况。当key范围较大时,需要用到哈希表,通常取f(x)=x mod M作为哈希函数,M为一个大质数。
另一种比较常见的情况是 key 为字符串的情况,在 OI 中,一般不直接把字符串作为 key,而是先算出字符串的哈希值,再把其哈希值作为 key 插入到哈希表里。
3.冲突:
如果对于任意的key,哈希函数计算出来的索引都不相同,那只用根据索引把 (key,value) 放到对应的位置就行了。但实际上,常常会出现两个不同的 key,他们用哈希函数计算出来的索引是相同的。这时候就需要一些方法来处理冲突。在 OI 中,最常用的方法是拉链法。
4.拉链法:
拉链法也称开散列法。拉链法是在每个存放数据的地方开一个链表,如果有多个 key 索引到同一个地方,只用把他们都放到那个位置的链表里就行了。查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其 key 与查询的 key 是否一致。如果索引的范围是 1~M,哈希表的大小为 N,那么一次插入/查询需要进行期望O(N/M)次比较。
const int M=999997;
struct hashtable{
struct node{
node *next;
int key,value;
};
node *head[M];
}
int f(int key){
return key%M;
}//哈希函数
int get(int key){
for(node *p=head[f(key)];p;p=p->next){
if