【纯代码向】 运用优先队列对拓补排序的实现
2017-10-24 20:07
253 查看
在这篇文章里,我想贴出我的拓补排序代码与分析,讲一讲它是如何从最慢的方法(O(n方))变到理论上最优的方法(O(n))的。
类似于广搜,我们用一个队列(stack[xx])储存出度为零的点。
不难看出,因为每次操作,我们都可以删去一条边,所以这个算法的时间复杂度是O(m)的,但仍不是我们理想的结果,因为每次都要进行一次判断,常数可能很大。
接下来,就是我们的最终解法。
- 出度最小的点队列(有序)——优先队列 因为我们需要优化,所以不能在每次循环后去寻找入度为零的点,而是应当排序。注意,这里的优先队列也得使用结构体,因为我们要记录原先这个点的位置。
- 每个节点的出度
- 每个点的父结点
- 点的访问情况 **运用优先队列,会导致一个点重复进出队列,为了不重复,我们需要用一个数组来判断是否访问过。
这里是结构体里的东西,注意重载运算符,这是优先队列的前提。
读入数据的过程,我用vector fa
来存储父结点。
这是主程序的部分。注意的点有两个:首先,要以优先队列非空作为判断的依据,这是为了避免运行错误的基准条件,任何数据结构一定要判断是否超了下界。
其次,是判断这个点的度是否为零:如果不为零,而因为这又是度最小的那一个,那么说明[b]该图有环,情况错误,退出。剩下的是一些无关紧要的东西了,都可以在我之前的文章里找到。
2017.10.24
分析
首先,我们需要知道我们面对什么样的问题:我们需要每次找出出度为零(倒序)或者入度为零(正序)的节点,删除这个点后,将这个节点与“父结点”相连的边删除,重复该步骤直到无点可删。 那么第一种方法就出来了。
方案一 逐次检索
每一次我们删除一个点,就去寻找一次图上是否有出度为零的点,这样时间为n方,会浪费大量时间在不必要的搜索上。但相对比较好写,故此不贴出代码,不提倡。方案二 检查,入队
请大家思考一些问题:有什么点,它原本的出度不为0,然而经过了一次删边之后,出度就变化为0了呢?答案是,那些跟删除的那些点相连,出度为1的点。因为每次删除只会影响到这些点,其他店的出度是不变的,所以我们只需在这中间找即可。类似于广搜,我们用一个队列(stack[xx])储存出度为零的点。
int stack[20005],be[20005];//be[xx]储存点的出度 vector <int> son[20005];//vector 作为邻接表 int p1=0,p2=0; for(int i=1;i<=n;i++) if(be[i]==0) stack[++p2]=i; while(++p1<=p2){ int h=stack[p1]; for(int i=son[h].size()-1;i>=0;i--) { int k=son[h][i]; if(--be[k]==0) stack[++p2]=k; } }
不难看出,因为每次操作,我们都可以删去一条边,所以这个算法的时间复杂度是O(m)的,但仍不是我们理想的结果,因为每次都要进行一次判断,常数可能很大。
接下来,就是我们的最终解法。
方案三 优先队列
优先队列真是太好用了!感觉这东西后面会用的很多,而的确是这样,所以请确保你能独立打出优先队列的模板(可以去看我的前一篇博客,堆)。数据结构
我们需要这几个东西:- 出度最小的点队列(有序)——优先队列 因为我们需要优化,所以不能在每次循环后去寻找入度为零的点,而是应当排序。注意,这里的优先队列也得使用结构体,因为我们要记录原先这个点的位置。
- 每个节点的出度
- 每个点的父结点
- 点的访问情况 **运用优先队列,会导致一个点重复进出队列,为了不重复,我们需要用一个数组来判断是否访问过。
using namespace std; struct node{ int du,pos; bool operator < (const node& x)const{ if(du>x.du) return true; return false; } }; priority_queue<node> Q;
这里是结构体里的东西,注意重载运算符,这是优先队列的前提。
void readit() { memset(vis,0,sizeof(vis)); int n,m;scanf("%d%d",&n,&m); for(int a,b,i=1;i<=m;i++){ scanf("%d%d",&a,&b); fa.push_back(a); chu[a]++; } for(int i=1;i<=n;i++) Q.push((node){chu[i],i}); }
读入数据的过程,我用vector fa
来存储父结点。
while(!Q.empty()){ node x=Q.top();Q.pop(); int k=x.pos; if(vis[k]) continue; if(x.du!=0) break; vis[k]=true; //doit(k); 放你想做的事吧 for(int i=fa[k].size()-1;i>=0;i--) { int p=fa[k][i]; chu[p]--; Q.push((node){chu[p],p}); } }
这是主程序的部分。注意的点有两个:首先,要以优先队列非空作为判断的依据,这是为了避免运行错误的基准条件,任何数据结构一定要判断是否超了下界。
其次,是判断这个点的度是否为零:如果不为零,而因为这又是度最小的那一个,那么说明[b]该图有环,情况错误,退出。剩下的是一些无关紧要的东西了,都可以在我之前的文章里找到。
尾声
这一点内容也结束了,虽然不多,但感觉写起来很累。效率是否过低?我开始理解为什么很多大大们分析完思路就直接贴代码了。下一次我会酌情改进,那么再见,你的支持是我的动力(之一),莫忘评论指出我的不足,谢谢!2017.10.24
相关文章推荐
- PTA 7-7(排序) Windows消息队列(25 分) 25分代码 优先队列
- 利用无序数组实现优先队列并排序数组——ADT实现
- 堆排序应用之优先队列的实现
- 优先队列的 java 代码实现
- 优先队列(priority_queue)的C语言实现代码
- java编程实现优先队列的二叉堆代码分享
- PTA 7-7(排序) Windows消息队列(25 分) 25分代码 优先队列
- 利用优先队列实现堆排序(自顶向下自底向上堆化完全二叉树的运用)
- 算法基础:排序(四)——二叉堆、优先队列、堆排序——Python实现
- 算法基础:排序(四)——二叉堆、优先队列、堆排序——Python实现
- 优先队列的STL运用与哈夫曼树的实现
- 利用优先队列实现堆排序(自顶向下自底向上堆化全然二叉树的运用)
- 数据结构之广度优先搜索(队列实现)问题
- 优先队列——二叉堆实现
- Java实现对中文字符串的排序功能实例代码
- 线性表、堆栈、队列的特点,及代码实现
- 关于CLR中堆排序若干问题的代码实现
- ASP.NET2.0中GRIDVIEW控件完全代码实现模版列排序!
- 数据结构之队列的基本操作入队出队初始化删除-c++代码实现及运行实例结果
- php比较多维数组中值的大小排序实现代码