前言
在做遍历的题目的时候,发现掌握一些特殊的数据结构和技巧有时对解决题目有着决定性的作用,不可不学。因此特地拿出来两天学习一下并查集、堆、优先队列。以后有更多思考和感悟再加补充吧。内容来自算法笔记,作者胡凡、曾磊。
正文
一.并查集
并查集是一种维护集合的数据结构。
1.1并查集的定义
并 :Union 合并两个集合。
查: Find 判断两个元素是否在一个集合中。
集:Set
用一个数组实现并查集
int father[N];
father[i]表示元素i的父节点,父亲结点本身也是这个集合内的元素,
例如father[1]=2,表示元素1的父亲节点是2,以这种父系关系表示元素所属的集合,
如果father[i] = i ,说明元素i是该集合的根节点,对同一个集合来说,只存在一个根节点,并且将其视为所属集合的表示。
father[1] = 1; //1是根节点
father[2] = 1; // 2的父结点是1
father[3] = 2; // 3的父节点是2
father[4] = 2; //4的父节点是2
father[5] = 5; //5是根节点
father[6] = 5; //6的父结点是5
//图1的并查集实现
1.2并查集的操作
1.初始化
for(int i=1;i<=N;i++)
father[i] = i ;
2.查找
查找根节点
int findFather(int x)
{
while(x != father[x])
x = father[x];
return x;
}
以图1为例子,查找4的根节点是谁。
第一步, x = 4; father[4] = 2, 4!=father[4],继续while循环
第二步,x = 2; father[2] = 1, 1!=father[2],继续while循环
第三步,x=1=father[1],返回1。
递归实现查找
int findFather(int x)
{
if(x == father[x])
return x;
else
return findFather(father[x]);
}
3.合并
合并是把两个集合合并成一个集合,合并的过程是把一个集合的根节点的父亲指向另一个集合的根节点。(有时注意两个元素在一个集合)
主要分以下两步
第一步:对于给定的两个元素 a , b .判断是否在一个集合里。用上面的findFather。
第二步: 找到的两个faA , faB,根节点,让一个根节点的父亲指向另一个根节点。
father[faA] = faB;b,a反之亦可.
注意:合并一定要动根,直接合并结点有可能出错。
并查集的特色,优势就是改祖宗。
void Union (int a, int b)
{
int faA = findFather(a);
int faB = findFather(b);
if(faA != faB)
father[faA] = faB ;
return ;
}
并查集一个重要的性质:
在合并的过程中,只对两个不同的集合进行合并,如果两个元素在相同的集合
那么就不会有操作。这就保证了同一个集合一定不产生环,并查集的每一个集合都是一棵树。
1.3路径压缩
如果并查集的结构如图2,有100000个结点形成链,那么查找起来效率很低,因此要进行路径压缩这种来优化查询操作。
接着看下面这个例子。
father[1] = 1 ;
father[2] = 1 ;
father[3] = 2 ;
father[4] = 3 ;
如果只是查找根节点,完全可以把操作等价于如下:
father[1] = 1 ;
father[2] = 1 ;
father[3] = 1 ;
father[4] = 1 ;
//修改x前面的所有父节点,直到根节点
int findFather(int x)
{
int a = x;
while(x != father[x])
{
x = father[x]
} //寻找根节点
while(a != father[a])
{
int z = a; // 先保存a的值
a = father[a]; // 回溯到a的根节点
father[z] = x; // 修改a的根节点的值,下次循环处理a原先根节点的根节点,直到x
}
return x ;
}
二. 堆
2.1堆的定义
堆是一棵完全二叉树,树中每个结点值大于等于(或小于等于)左右子节点的值,
称为大顶堆,堆一般用于优先队列的实现。
建堆的过程:从右下开始,总是将结点V和它的左右孩子比较,如果有比V大的孩子,
交换V和值最大孩子的位置,一直到V没有比他大的左右孩子。
向下调整的代码,时间复杂度O(logn)
对数组head[low,high]范围向下调整
low为欲调整结点的数组下标,high为堆的最后一个元素的数组下标
void downAdjust(int low ,int high)
{
int i = low ,j = i * 2 ; // i 为调整结点,j是左孩子
while(j <= high )
{
if(j+1<= high && heap[j+1] > heap[j]
j = j + 1;
if(heap[j]>heap[i])
{
swap(heap[i],heap[j]);
i = j ;
j = i * 2; // 下一次查询
}
else
break;
}
}
建堆,时间复杂度O(n)
void createHeap(int n)
{
for(int i = n/2; i > 0;i--)
downAdjust(i,n);
}
删除堆顶元素,时间复杂O(logn)
void deleteTop()
{
heap[1] = heap[n--];
downAdjust(1,n);
}
向堆添加元素,O(logn),先考虑向上调整的操作,从子节点开始的调整
void upAdjust(int low ,int high)
{
int i = high, j = i/2;
while(j >= low )
{
if(heap[j]<heap[i])
{
swap(heap[i],heap[j]);
i = j;
j = 1/2;
}
else
break;
}
}
}
void insert(int x)
{
heap[++n] = x;
upAdjust(1,n);
}
2.2堆排序
void heapSort()
{
createHeap();
for(int i=n;i>1;i--)
{
swap(heap[i],heap[1]);
downAdjust(1,i-1);
}
}
三.优先队列(priority_queue)
priority_queue,队首元素是优先级最高的一个。
用法
3.1,优先队列定义
priority_queue< type > name ;
2,访问容器内元素
priority_queue <int> q;
q.push(3);
q.push(4);
q.push(1);
cout<<q.top;
输出结果:4
push() ,pop(),top(),size(),empty() 全家桶不必多说
优先级的设置:默认是数字越大优先级越大,
3.2 队列优先级的设置
priority_queue<int> q;
等价于
priority_queue<int,vector<int>,less<int>> q;
第一个参数是容器数据类型。
第二个参数底层数据结构heap实现类型,vector内和第一个参数保持一致即可
第三个参数,①less表示数字越大,优先级越大.
②greater表示数字越小,优先级越大。
结构体的优先级设置
四.相关题目
1.HDU1232畅通工程
题目链接:HDU畅通工程
题意:求一个图中连通块个数。
输入:
5 3
1 2
3 2
4 5
0
输出:
1
弱数据通过,没有用scanf估计对强数据WA.
2.堆的练习
POJ3253Fence Repair
堆的题目:围栏修复
题意:
见链接,切一次木板之前,先收取整块木板长度的钱,然后才可以切成两块,再不断地切剩下的的小块,切之前还是收小块全长的钱。直到切成符合输入题意。(其实就是哈夫曼编码问题)
输入:
3
8
5
8
输出:
34
想法:优先队列或者手工用堆实现。
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 20010;
int n,m,x;
int heap[maxn];
void downAdjust(int low,int high)
{
int i = low,j = i*2;
while(j<=high)
{
if(j+1<=high&&heap[j+1]<heap[j])
j++;
if(heap[j]<heap[i])
{
swap(heap[i],heap[j]);
i = j;
j = i*2;
}
else
break;
}
}
void createHeap(int n)
{
for(int i=n/2;i>0;i--)
downAdjust(i,n);
}
int main()
{
int Min ;
long long ans = 0 ;
cin>>n;
for(int i=1;i<=n;i++)
cin>>heap[i];
createHeap(n);
while(n>1)
{
Min = heap[1];
heap[1] = heap[n--];
downAdjust(1,n);
Min = Min + heap[1];
heap[1] = Min;
downAdjust(1,n);
ans = ans + Min; // 体会一下在该过程中操作的思想,什么时候进,什么时候出
}
cout<<ans;
return 0;
}