3.图论经典算法之最短路~DJI+堆优化的DJI,

作为前导知识的深度优先搜索和广度优先搜索就不赘述了,自行谷歌百度解决!(因为不是重点,大雾)

以下建图均使用链式前向星,默认有n个节点,m条边,关于链式前向星转之前的文章

下面贴一下标准建图以及输入!复习一下!

从洛谷上copy的

下面是链式前向星建图伪代码,仅作建图参考!(符合上述题意)

struct xing

{

int wei,quan,qian;

}bian[1010100];

int head[1100100],n,m,s,o1,o2,o3,cnt=0;

void add(int u,int v,int o3)

{

bian[++cnt].wei=v;

bian[cnt].quan=o3;

bian[cnt].qian=head[u];

head[u]=cnt;

}//链式前向星

cin>>n>>m>>s;//输入点数,边数,起点

while(m--){

cin>>o1>>o2>>o3;//输入边的信息

add(o1,o2,o3);//add函数加边

}



前置知识:贪心

在详细讲解DJI之前,首先介绍一下贪心思想,贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。其实按照字面意思就行了,就是贪吗QAQ

题源于洛谷

链接:https://www.luogu.com.cn/problem/P1181

显然,我们对于每一个正整数,不是把他自己单独开新的一段,就是把他并到他前面的段了,我们按照贪心的想法,能合并为什么不合并?当然要合并,这就是贪心,实在不能合并的在单列,这也就是所谓的贪婪,不去理会后面的情况,只做出现在最有利的选择,在这里也就是能合并就合并!



迪杰斯特拉(DJI)

好了,我们终于进入正题了,DJI的基本原理就是贪心,DJI的特点是首先建立一个集合,这个集合里面只有起点一个点,然后找除了集合里的点之外所有点中离起点最近的一个点,将他加入集合,并用新加入的点作为中转站,更新其他集合外的点到起点的距离,更新完之后再找一个除了集合里的点之外中离起点最近的点,再次中转更新,直到所有点都进入集合,这时候就获得了起点到其他点的最短距离!

光这么说可能还是难以理解,我们上个图,假设1为起点,现在集合里面只有1

建立一个len数组表示从起点到某点的已知最短距离,现在初始值分别为0 2 6 4

还是这个图

我们找了一下其他点,发现离1最近的点是2,很容易知道1和2的最短距离就是2,因为其他中转都一定比这个直接到达要长,所以2进入集合,现在集合里面有v1 v2两个点,然后以v2为中转,发现len3=6>len2+v2-v3=5;更新,这个过程专业上叫松弛,知道就行了,重要的是思想,len4<len2+v2-4;不更新,所以现在len数组的值为0 2 5 4;

下一步,现在集合外离v1最近的显然是v4,好,v4进入集合,以v4为中转,发现从v4中转到v3的距离是4+12>5,不更新len,在之后。。。v3就进集合了,好了,就此结束,得到len数组0 2 5 4,和Floyd结果一致!

显然在有n个节点的情况下,DJI的时间复杂度显然是n的二次方,还是比Floyd优秀很多,尽管他只能求单源最短路径

如果还不明白,不要紧,下面给出题目和伪代码,好好体会一下dji的思想


题目链接:https://www.luogu.com.cn/problem/P3371

memset(len,0x3f,sizof(len))//前面讲了,初始化len数组

len[s]=0;//自己到自己为0

bool used[101000];//用于判断某点是否已经进入集合

used[u]=1//起点自己开始就在里面

ans=0x3f3f3f3f//定义的极大值

for(int k=1;k<n;k++)//一共还有n-1个点要进集合
{

int  u,ans=0x3f3f3f3f;//u和ans每次都得更新,因为都要找进入集合的新点

      for(int i=1;i<n+1;i++)

      if(!used[i]&&len[i]<ans){ans=len[i],u=i;}//如果不在集合中又比ans小,就把i点记录下来,遍历一遍后即可得所需进入集合的点

used[u]=1;//此时的u点进入集合

for(int i=head[u];i;i=bian[i].qian)//遍历与u点相连的点,当i=0的时候说明都遍历完了可以停了

{

int v=bian[i].wei;//与u点相连的v点

if(len[v]>len[u]+bian[i].quan)//如果可以松弛,其实这里多遍历了已经进入集合的点,集合的点是不会被松弛的,这里如果加一个判断跳过进入集合的点,会更快一些,但是不影响算法的正确性

len[v]=len[u]+bian[i].quan;//松弛更新

}

}

最后的len数组就是你要找的东西了!是不是大概知道DJI的本质了,就是贪心贪心贪心!

接下来讲的堆优化的dji也是因为贪心才能进行的,如果你不知道什么是堆,也不要紧,C++里面已经给您包装好了,我们管他叫优先队列,是按照一定优先级进行排序的一个队列,比如现在有1 2 3 4 8,现在要朝这个队列里面插入元素6,插入完就是1 2 3 4 6 8,始终保持一定的排序(当然是自定义的,我这个例子只是方便理解)

学过队列的人一定发现这好像和我学的队列不一样啊,废话,优先队列是用堆写的又不是用队列,当然不一样啊。你不懂就把他当成一个工具记住就行了(本菜鸡就是这么做的,有理想的人不建议这么做)!

有了优先队列,我们来找找DJI的不足之处,每次都要遍历一遍查询他是不是在集合里面,要全部对比一遍才能知道哪个点最短,这复杂度是o(n)了,属实弟弟啊,不就是从len数组里面每次找一个非集合内的最小值吗,如果我们能边更新边保持len的有序性,哦豁?成了,这不就是优先队列吗!

如果理解了DJI,我想你也能轻易理解接下来的代码,我会贴注释的嘻嘻

struct node{ 

int id,quan;//一个元素存贮点v和len[v]两个信息,也就是我写的id和quan

bool operator <(const node &a)const {

return quan>a.quan;//这是我为优先队列定义的优先级,len小的必然放到前面2333,当然你在有需求的时候也可以自己修改,由于优先队列不是重点,其他用法自行百度谷歌

}

};

for(int i=1;i<n+1;i++){

len[i]=INF;//初始化len,INF是个很大的值

}

len[s]=0;//自己到自己是0

priority_queue<node> q;//定义优先队列

q.push((node){s,0});//起点先进去

while(!q.empty())//只要队列不空

{

node u=q.top();//u就是优先队列队首,就是最小的那个

q.pop();//把u扔出去

        if(used[u.id])continue;//如果你发现u这个点已经进队了,那么直接去队首再来一遍,因为进队说明u点已经在集合中了,不需要他当中转站了

used[u.id]=1;//标记进入集合

for(int i=head[u.id];i;i=bian[i].qian)//遍历边

{

int v=bian[i].wei;

if(len[v]>len[u.id]+bian[i].quan&&!used[v])//如果可以松弛并且没有在集合里面

{

len[v]=len[u.id]+bian[i].quan;//松弛

q.push((node){v,len[v]});//进队

}

}

}

这里我多说几句吧,为什么会出现重复进队的情况,假如有v1 v2 v3 v4四个点,v1是起点,v1和v2 v3都直接相连,假设v2先进队,那么v4铁定进队了,但是现在v1到v4还是比v1到v3长,所以v3当中转站,又因为v3到v4极短,所以v4又进队了,这时候队里就会有两个v4,但因为优先队列的特性,我们只取了一个最优。剩下的在队里的无用元素我们通过continue把他扔出去!

堆优化的DJI因为堆或者说优先队列的复杂度是log的,所以复杂度已经拿到了很优秀的nlogn。对于普通的Dji已经优化很多了

接着多说两句,虽然优化后的DJi很厉害,但是他也有不足之处,那就是负权值,因为其贪心的原理使其不能处理负权值,那是因为贪心这玩意是无法回溯的,他在后面发现了更短的路,但是他已经在集合了,不能更新了,所以无论是否优化的DJi只能处理正权图,举例子

v1 v2 v3,v1到v2有1权边,v1到v3有2权边,v3到v2有-2权编,很容易看出来len数组应该是0 0 2,但DJi处理出来却是0 1 2,看明白了吗,DJi先把v2给扔进集合了,后续也不更新人家,当然就炸裂了,有什么算法能处理负权边吗?当然有,下一篇就讲他,好了,到此为止吧,贴两个题

洛谷oj:https://www.luogu.com.cn/problem/P3371   普通DJi可过,Floyd不可过

洛谷oj:https://www.luogu.com.cn/problem/P4779   普通DJi,spfa不可过,堆优化的DJi可过

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • 栈,队列,双端队列无序链表,有序链表二叉树,堆,二叉搜索树,AVL树图以及一些算法 coding: utf-8 u...
    hugoren阅读 617评论 0 0
  • 本篇仅简单叙述自己对图的理解,大概是随笔性质,本人水平有限,难免有不足之处,请见谅! 1.图,顾名思义,就是由一个...
    散排自闭咕阅读 519评论 0 1
  • 1736年,瑞士数学家Euler(欧拉)在他的一篇论文中讨论了格尼斯七桥问题,由此诞生了一个全新的数学分支——图论...
    不困于情阅读 4,400评论 0 9
  • 你只是个孩子,你根本不知道你自己在说些什么。你从一开始就向一个彻头彻尾的伪君子方向发展,试问你真的能脱离心灵重压下...
    墨小凝阅读 353评论 0 0