算法-1A

算法-1A

博主有幸曾参加过清华邓俊辉老师的算法训练营第二期,花费了3000大洋,听取了邓老师的十节课,邓老师在中国数据结构与算法届还是相当有能力,培养出很多拿世界级赛事大奖的学生。但当时正值暑假,博主意志力薄弱,未能跟着五周的高强度训练敲下去,课程也是事后慢慢观看录播补回来的,用邓老师的话来说就是:入宝山,却未能取得奇珍异宝归。现在想想很是懊悔,没有跟着敲习,只迷糊间听完了课程,但不得不感叹课程功效强大,哪怕听其一点也受益无穷,在随后的学校数据结构中博主考取了99,算是不错的成绩,但学校内容过于简单,要想真正进入算法的大门,还需广看书籍,刻苦训练。

如今博主为参加比赛,同时提升自己,把邓老师的算法课,又重新温故,咀嚼一遍,同时更新感悟和课程内容到博客中,希望有志同道合的朋友,一起学习,共同进步。(博主才疏学浅,只是把自己对算法的所想所得和一些感悟分享到博客中,如有错误,望各位朋友多多包涵并指正)

下面就开始正式第一节课的内容:

1-入门简单介绍

在这里插入图片描述
第一个知识点,邓老师讲解了算法中最为本质重要的BigO算法复杂度,主要是两类,时间复杂度和空间复杂度,时间和空间资源在计算中也显得极为重要,时间过长才能得到结果,和要开出极大内存资源才能得到结果的程序从某个方面来说是没有意义的。所以一个程序的BigO是评判一个程序好坏的重要指标,而其中时间复杂度又是我们程序员更应关心的重点。程序中当n>>2(远远大于2时),所耗的时间T(n)都小于f(n)的常数c倍的时候,那么这时f(n)可以看成该程序所用的时间,一般这里的f(n)是程序在跑的时候遇见的最坏情况,需要的最大时间,有时候我们很难找到这个f(n)。但我们希望有这样一个f(n)函数,这条线段可以大致描述我们程序的Running time。在我们找这个f(n)时,像常数和低次项可以直接忽略,因为我们的前提是n>>2。(这里可以细品一下)也可以见图片中的两个式子。
在这里插入图片描述
邓老师笔记图,在我们找寻f(n)时,含n的式子和一个巨大的数相比,含n式更为重要,而那个巨大的数只要是可以确定的,一般就可以忽略掉。

1.1简单数据结构

像邓老师说的:提到算法就不可能不提到数据结构,它们是分不开的。好的数据结构可以帮助我们的程序更好的达到效果,大大降低复杂度。
在这里插入图片描述
数据结构中最常见的就是array数组了,它的简单简洁,可以帮助我们完成很多最基本的事情。c++STL标准模板库里的vector容器,在一些竞赛中使用的频率相当高。
在这里插入图片描述
vector提供了push_back()在尾部插入一个elem数据,pop_back()删除末尾的数据,c.assign(beg,end)将[beg,end)一个左闭右开区间的数据赋值给c,c.assign (n,elem)将n个elem的拷贝赋值给c,c.begin()返回指向第一个数据的迭代器,c.end()返回指向最后一个数据之后的迭代器。
在这里插入图片描述
随后就是list链表,list和array的优缺点很明显,一个查找方便,插入删除困难,另一个正好相反,查找困难,插入删除方便,这其实也反应了一个道理,没有最好的算法,只有最合适情况的算法。
在这里插入图片描述
栈也同样普遍简单,栈只要记住它只有一个接口可进可出,就像一个狭窄的死胡同,最先进去的,就必须最后出来。先进后出,后进先出。
在这里插入图片描述
栈与之相对的就是队列,见名知意,就是我们平常排的队,先来先得,后来后得,两个接口,一个进,一个出。在我们计算机运行时,一般cpu各种线程的运行顺序也是基于此。
在这里插入图片描述
前面介绍的数组,链表,栈,队列都是线性结构。而Tree可以说是半线性,因为它有大概的方向,由根到叶子节点,二叉树Binary Tree又是树家族中很特殊的一位,因为二叉树的每个节点下最多生出两个节点,对应计算机世界里的0和1是极其完美的。所以说Tree的世界里大概可以描述成两种,二叉树和非二叉树(三叉,四叉树等),而多叉树又可以转为二叉树,是极为方便的。邓老师在课程里讲解的方法我觉得非常形象且易于理解,首先我们找到非二叉树的根节点,此时它的下面可能有三个节点,或者四个乃至更多,但这些并没有关系,我们把它下面的节点,分为两类,就像一个古代父亲,有很多孩子,但也分为两类,一类是长子,一类是其它儿子,这时候把长子放在根节点的左边,其它孩子则是长子的兄弟姐妹,把次子们与根节点的连接线消除,增加次子们和自己长兄的连接线,这时每个节点都做这样的处理迭代,你最后最惊讶的发现,一个多叉树就变成了二叉树,且对于每一个节点来说,自己生出的左节点是自己的长子,右节点是自己的兄弟姐妹,这种方式无疑来说是非常神奇的,也很方便理解。像图中三幅图就是过程(从右到左),最右边是一颗多叉树,随后取消父节点和除长子外的连接线,增加长子与其他兄弟的连线,得到图二,此时已经是一颗二叉树了,但并不好看,通过移位得到图一,好看了很多。
在这里插入图片描述
然后就是数据结构中较为复杂的Graph图,a是无向图,b是有向图,c则是有向图而且还有权重。数据结构是方便来解决生活中实际问题的,并不是单纯好玩创造出来,所以我们可以从这个角度去看待,会有助于我们更好的理解。比如城市间的道路图,就可以用a来表示,如果道路上有的设立关卡,是单方面通行的,则可以用图b表示,如果过路还要收钱,不同的路收取的费用不同,则可以用图c表示。在计算机中解决这种问题,用矩阵来存储是一种解决方式,例子就在图的下方,但可以发现,a图的矩阵其实只要保存一半就好,因为是没有方向的,但在这里可能就会想,那这样是不是能节省一半的存储空间呢?事实上是这样的,但理论上并不是,回到我们介绍BigO的时候,我们说过常数是可以忽略的,所以二分之一n和一个n其实在理论上相差不大,甚至可以理解为是相等的。
在这里插入图片描述
这时候抛出一个问题,如果要建立人与人之间的关系图怎么办,相信大家都可以想到画个图,谁和谁认识,就在他们两之间画一跟线,如果表示关系有多好,可以用个权重,权重越大则关系越好。那么在计算机里存储的时候,也可以像上面一样,用矩阵存储关系。但很快你就会发现,不说全球,单说中国现在14亿人,就记作10的10次方吧(像上面的思想一样,计算机不在乎小细节,计算机更在乎的是数量级),那么要开辟的单元格就是10的10次方乘以10的10次方也就是10的二十次方,显然这是很难实现的,那么你有什么好的方法嘛?
在这里插入图片描述
聪明的朋友可能很快意识到,上面的方法理论上可行,但实际很难达到,因为要开辟的空间太大了,但细心的同学又会发现,这个办法不仅难实现,而且还浪费了大量的资源,比如拿A同学来说,用矩阵的方式表示的话,A那一行可以有10的10次方个单元,但其实其中能填数据的,只有几千个而已,厉害一点的也不过几万个,所以这意味着这个方法可行,却浪费了大量的资源。那有什么好的方法吗?
显然这时候要是理解了数组的列表的概念,可以比较轻松的想到,每个人都有自己的关系,所以每个人都要存在的(也就是10的10次方是少不了的),但每个人对应的关系却不那么多,那么用数组来存储每个人,但在每个人的与别人的关系里,不用数组存储,改用链表,有关系的我才放进来,那么要开辟的空间就大大减少了。那么具体是多少呢?算每人都爱社交,认识的朋友达到五位数,那么需要开辟10的10次方乘10的5次方,也就是10的15次方,虽然还是很大,但比我们之前算得的10的二十次方小的太多了,相差10的5次方倍,可能有的朋友认为10的5次方并没有什么,但如果把你现在银行卡里的余额乘以10的5次方呢?相信那是一个巨大的数字!介绍完这个例子,是不是感觉到了算法的魅力呢,运用恰当,算法将会发挥无穷的能力!

2-贪心(正式入算法)

到这一些基础数据结构也讲完了,那差不多要正式进入算法了。
贪心是一种很常见的思维,虽然这在我们汉语里有点贬义,但是在算法中却不是。贪心是指在大局中,只看见眼前的那部分,在局部达到最优,而不管其它部分,看似很愚蠢,但是其实在很多情况下,贪心策略具有神奇的功效。通过一次次改动局部,达到最后可行的效果。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
讲贪心,首先讲排序。上面的图很明显可以看出,是无序到有序的过程。排序也是算法中很经典的问题,一般来说排序需要n平方的复杂度,但像快速排序和二路归并等优秀算法都可以拥有n乘log n的复杂度,而还有一些很特殊的方法甚至可以达到一个n的复杂度,这是惊人的!后面围绕排序,我们也会有很多算法和解决方案。
在这里插入图片描述
第一种方式叫native,也就是单纯的,这种方式只是为了最基本的达到排序的目的。图中都是伪代码实现,大家领会精神就可,不必死磕细节。大家可以仔细思考一下为什么if语句中用的或两个条件?而且两个条件的顺序能交换吗?在这里插入图片描述
这是演示图,native的想法很简单,首先从第一个开始(当然第一个直接++),然后到第二个,再把当下的这个和前一个做比较,如果小于前面那个,则应该把当下这个小的放到更前面去,也就是先和前面一个交换位置。在整个算法中,算法是贪心的也是愚蠢的,它是鼠目寸光的,只能看见当下两个,也就是第i个和i-1个,当第i个还小于i-1个,则交换位置,然后眼睛里的i和i-1变成i-1和在前面一个也就是i-2,若一直小,则一直换,直到眼睛里的两个是顺序的,也就是i不小于i-1,然后再往后走(i++),眼睛里装新的两个元素。
在这里插入图片描述
但这种native是效率很低的,虽然说能达到效果,但细心的同学会发现做了很多无用功,比如第i个其实是最小的,需要把它换到第一个,在换到第一个之后,第二个到第i个这时候肯定已经是顺序的了,但该算法并不知道,还是要比较第二个和第三个,第三个和第四个,直到第i个,这样就会做i-1步无用功。于是对于此,就有了improve的算法,improve算法相当于在把第i个不符合位置的元素移到前面去的时候,记住了第i个位置,在顺位后,直接回到第i个元素那,不必再从它所在的位置慢慢移到i。
在这里插入图片描述
在这里插入图片描述
上面两图就是improve的演示过程,把i到k的重复工作给取消了,相对native来说是一大改进,但时间复杂度还是n平方的。

在这里插入图片描述
随后介绍了起泡(冒泡)排序,起泡排序在排序算法中也是相当经典了,原理也是贪心,只盯着两个元素,从0号位置和1号位置开始,比较两个的大小,把大的放在右边也就是这里的1号位置,再盯住下两个元素1号位置和2号位置,再比较大小,大的放右边,也就是这里的2号位置,循环往复,一直到第n个元素,这时候你会发现n个元素中最大的那个已经被你得到了,而且刚好在n号位置,那么再做n-2次,依次得到第2大的元素一直到第n大的元素,因为这个过程每次都是两个两个之间交换,而且是顺序的,像鱼🐟吐的泡泡一样一次上浮,且越来越大,所以起名为起泡排序。(还是很有意思的哈!)
在这里插入图片描述
最开始的无序图,
在这里插入图片描述
慢慢选两个里面大的那个,而且慢慢放在右边,
在这里插入图片描述
当第一次结束时,发现最大的就在最右边,
在这里插入图片描述
再慢慢做第二次过程,
在这里插入图片描述
有趣的邓老师,有趣的起泡排序!
在这里插入图片描述
起泡排序的伪代码,过程就是上面说的那样,在这里,邓老师把bubble这个过程单独拿出来写在了下面,更方便我们理解。
在这里插入图片描述
但在这个过程中,会出现很多问题,比如当一个需要排序的数组,本身除了最大的元素位置在0号位置外,其他的已经顺序了,那么在第一次排序,把最大的元素移到了n-1号位置时,整体已经顺序了,但算法并不知道,它还是会做n-2次的无用功,那么我们怎么让算法知道已经顺序了呢?方法时做一个判断,当第一次执行完,执行第二次排序时,给一个变量,如果第二次排序没有遇见逆序对也就是算法眼睛里的那两个永远是顺序的,那么就说明此时n个元素整体已经顺序了,这是可以判断到的,那么当算法得到这个判断时就提前中止排序,这样就省去了这部分的无用功。
在这里插入图片描述
那么举一反三,在上面那种情况里固然有用,但是如果又一种中间情况,也就是在一次排序后,中间大部分已经顺序了,只有极少数部分是逆序的,那么第二次还是会交换一些逆序的位置,我们的判断树还是false,但在假如第二次排序时,前面一点点是存在逆序的,后面整体是有序的,而我们还是要在交换逆序的后,慢慢走过那些顺序的,这部分顺序的元素,其实已经不用排序了,但算法还是排了序,这也是一部分无用功,那么怎样能把这个省掉呢?
在这里插入图片描述
如图中的伪代码,现在已经不再是放一个判断数了,而是干脆记录一个位置,记录最后一次交换(也就是还有逆序对)的位置,在这一次排序结束后说明,标记的位置右边皆顺序了,则在后面的排序过程中可以省去这一部分操作,是极其精妙的。

3-贪心(2)

第一次课贪心的后面部分,邓老师就讲了两个知识点,一个哈夫曼树,一个矩阵排序。哈夫曼树是一种二叉树
在这里插入图片描述
如图就是一颗很简单的哈夫曼树,用叶子节点去代表一个个字母,但你会发现,树的不同形状,叶子代表的不同字母,产生的效果都是不一样的。在该二叉树的左边,我们给零,右边给一,最后得到main这个由四个字母组成的单词的哈夫曼编码,你会惊讶的发现每个字母的编码之间,没有哪一个字母的编码是另一个字母编码的前缀,这是极好的!
在这里插入图片描述
哈夫曼编码可以很好的解决,传输信号之间带宽不够的现象,同样一份文章用哈夫曼编码和随便编码,所需要传送的比特是相差很大的,如图中三种结果。哈夫曼编码可以使需要传输的比特最少,这也是神奇之处!
在这里插入图片描述
构造哈夫曼树过程,最开始很多节点,排好序,选出最小的两个,合成一个新的节点,再插回去,使之顺序,(这也是贪心的体现,只盯着局部两个最小的)
在这里插入图片描述
不断迭代往复这个过程,
在这里插入图片描述
最终得到一颗完整的哈夫曼树!
在这里插入图片描述
如图中间是哈夫曼树,传输一个main只要8bit,而其它树的方式要更多一些,在同权重的情况下,转换的秘诀是最底部的叶子节点和最高的叶子节点层数不能超过一!
在这里插入图片描述
最后一个内容就是矩阵排序了,经常会遇见这种情况,一个无序的矩阵,在把每一列排好序后,再把每一行排好序,这时候你会发现每一列还是有序的,这同样很神奇,那么怎么证明呢?证明的方法有很多,邓老师给出来的是我们之前讲过的bubble:
在这里插入图片描述
我们同样贪心的看待每一次排序,在列排完序后,对行排序时,我们看中相邻的两行,每行看中同列的两个,这样我们看重四个,这时候,b是大于a的,y是大于x的,但a与x之间我们不知道大小,b与y之间我们不知道大小,这样的话,就会出现四种情况,当我们每种情况都假设出来的时候,会发现,四种情况行排序后都会是列也是顺序的。我们举一种情况,a大于x,b小于y,那么a和x之间会交换,交换后,a小于y,因为a小于b,b又小于y,x小于b,因为x小于a,a又小于b,所以证明出,列排序后,再行排序,不会影响列的顺序,一个小矩阵四个元素证得,但整个过程都是由小矩阵组成的,所以最终大矩阵同样符合!

结尾

第一次课就这么结束了,还是比较基础和简单的,但后面九节课难度和知识点会更多更大,希望博主能够好好吸取其中的奥妙,并勤于实践代码,最终,入宝山,而不空手归!

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值