【万字长文】不学算法你也应该知道的算法知识_你认为不学算法分析与设计一样能够编好程序这一个观点正确吗,并举一个例子加(1)

def sumTwo(a:int,b:int)->int:
 return a+b

例子3 两数之和(来真的了啊!):

给你一个非空数组,和一个目标值,从数组中找出两个元素使其的和与目标值相同,并返回一个长度为2的数组装这两个值对应的下标,只允许一个元素使用一次

————源于力扣第一题,简单难度

Java代码:

public class Hello{
public static void main(String[] a){
  System.out.println(); 
}
public static TwoSum(int[] nums1, int[] nums2, int target){
  /\*
 最容易想到的,也是没学过系统学习过算法的人的方法
 嵌套循环:
 遍历nums1,再遍历nums2,然后求和,与目标值做判断
 当然,想法很简单,却会出错,因为还要考虑元素只用到一遍
 \*/
  for (int i=0;i<nums.length;i++){
      for (int j=0;j<nums.length;j++){
          if (i!=j){
              if (nums[i]+nums[j]==target){
                  return new int[]{i, j};
              }
          }
      }
  }
  return null;
}
}

这里面就用到了一个数据结构,叫做数组,这里的算法就是将新数组求出来的过程!

Python代码:

class Solution:
 def twoSum(self, nums: List[int], target: int) -> List[int]:
     for i in range(len(nums)):
         for j in range(len(nums)):
             if i==j:
                 continue
             elif nums[i]+nums[j]==target:
                     return [i, j]

当然,例3的方法是很简陋的,更简单的方法还有哈希法和二分法,在这道题中,哈希法的时间复杂度是O(n),二分法的时间复杂度为O(nlogn)

感兴趣的话,可以看看我的这一篇博客:零基础的我刷力扣一周后,总结了点东西

这三个例子大概也能让你对什么是数据结构与算法有一个大致的了解了。那么接着看吧~

二、为什么要学算法

既然知道了算法是什么,那么你知道为什么要学习算法吗?

有人可能会说:因为我想在找工作的时候更有竞争力,更有话语权,所以我来学算法!这个当然没错,面向加薪学习~~

当然了,除了这个,也没什么别的办法能够令人疯狂了吧~~兴趣?有,很少的~

那么为什么公司喜欢会算法的人呢?换句话说,为什么会算法的人会更吃香呢?

因为公司运行你的代码是需要机器的,而机器也是分价钱的,如果你的算法很糟糕,那就会让公司的机器运行起来占用CPU资源多,拟人化一下那就是会很吃力,而且运行你得代码会更耗费时间。

而如果你会算法,甚至算法很好,那么你的算法就会让机器如释重负,甚至跑的更快!

想一想,机器同样的价钱,你会算法能够让机器干更多事,那公司肯定要你啊!别说工资,那一个机器都好贵的。

而算法的出现也是因为先有了问题,之前也说过算法是什么了,就是一个问题的解决方案,所以我们的算法是为了解决问题才出现的。

三、算法的特性

当然一个问题都是可以由多个算法解决的。俗话说,无规矩不成方圆。

那么不同的算法是不是得有相同的规则,或者说相同的特性,才能约束算法,不能任意一种算法就可以解决问题。

打个比方,就用曹冲称象这个例子吧。为了不浪费大家的脑子,我决定就说的简单一点。

现有大象一头,需要知道大象有多重。但是秤太小,无法精准测量。

请你设计一个算法来计算大象的重量。

曹冲的方法:把象放到大船上,在水面所达到的地方做上记号,再让船装载其它东西,称一下这些东西,那么比较下就能知道了。

方法1:我把大象切碎,一块一块量

方法2:造一个超大的秤,专门用来秤大象的重量

你觉得方法1和方法2怎么样?先不急着回答,我们先看看算法的这几个性质。

1. 有穷性

一个算法必须要有一个明确的终止条件来控制处理过程的终止,或者说结束。

什么意思呢?就是你的算法至少要有一个终止条件,或者说能够在特定时间内终止,并计算出结果。要不然你的方法有什么意义呢?

2. 确定性

你的算法的每个步骤都是有明确的目的,不能有别的意思。

就拿上面的例子,你说要把大象切碎,不能切着切着,我还是再造个秤吧。

3. 可行性

你的算法的没一个步骤都是可以被机器或者人执行的。

就像曹冲的方法一样,是切实可行的,而像方法12,则多少有点不切实际了。

4. 正确性

你的算法所计算的结果是能够用来解决这个问题的。

不能说你的算法无法解决问题,那样你的算法也是毫无意义的。

那么,在看完这四个特性后你怎么回答上面的问题呢?

当然,答案我也在特性里面说了出来,还不知道答案的,面壁思过去!

四、算法的分析

那么讲了这么多,我们怎么判断我们的程序是好是坏呢?那就是时间复杂度和空间复杂度。

是不是感觉这两个名词好难理解?没事,他其实也不难。

1. 时间复杂度

所谓时间复杂度就是算法执行的时间。而时间复杂度可以由两种方式:

  • 计算程序实际运行时间
  • 计算程序相对运行时间

我们知道,即使是同一种算法,在不同的编程语言不同的机器,不同的环境索引行的实际时间也是不一样的

所以第一种方式,就不是很银杏化!那么就只能是第二种方法了来判断我们的程序是否得到了优化~~

而时间复杂度又分为三种,最优情况最劣情况平均情况。而这个情况是由问题来决定的~

这三种情况我就不多说啦~看名字就知道了

时间复杂度统一使用O(x)来表示,也是俗称的大O表示法,而时间复杂度的大小与问题有关。

比方说:

  1. 我给你三个数,你把他们三个从小到大排序,时间复杂度是O(3),也可以说是 O(1)
  2. 我给你10**100个数,你把他们从小到大排序,时间复杂度是O(10**100),也可以近似取到O(1)
  3. 我给你几个数,你把他们从小到大排序,时间复杂度为O(n)

那么为什么改了一下题目,时间复杂度就不一样了呢?因为1,2是确定的值,不会受到外部影响而改变,所以只需要循环那么多次,不会改变所以时间复杂度是O(1);而3不一样,他给的值是不确定的,所以需要循环n次,时间复杂度就是O(n)

当然这个也是可以根据问题改变的,那就是对特定的值使用特定的算法。比如二分法,递归的时间复杂度就是O(logn),而O(n)是大于O(logn)的

而如果需要嵌套循环,那么可能是O(n**2)或者O(nlogn)。

常见的时间复杂度按照从小到大的顺序排列有:O(1),O(logn),O(n),O(nlogn),O(n**2),O(2**n),O(n!)

2. 空间复杂度

根据算法在执行过程所使用的存储空间,分为堆区和栈区,当然还有别的区。

而栈区中存放着变量名,堆区中存放着变量名指向的值。这么说可能不好理解,那就画个图吧~~

在这里插入图片描述

诶😱😨叭叭了这么多,好像还没到点上!

稍安勿躁嘛~~

这就来空间复杂度啦~

其实和时间复杂度差不多,我们使用常数,实数,或者对数组,字符串进行原地修改的时候,空间复杂度就是O(1),因为没有开辟新的空间

而如果是新建一个字符串,数组就是需要新开辟一个数组或者字符串空间,所以空间复杂度为O(n)

五、算法

关于一些生活中常见的算法…

这里不讲代码,只讲原理!

1. 贪婪算法

在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法 。

他也叫贪心算法。

举个栗子:

你去超市买东西,买了十三块钱的东西

你拿了一百块钱,给收银员,收银员会给你找零钱

贪婪算法就是:

先找你一张50的,再找你一张20的,再10的,再5的,再1的,再1的

当然这也是一般收银员会选择的方法,通过找给你最少张的纸币,找给你所需要的钱

这就是贪婪算法

贪婪算法所能解决的问题的特性:

  • 该问题至少有一个最优的解决方案。只有这样,才可能为解决问题构造候选方案集合。在前面的例子中候选方案集合就是不同面值的纸币组成的纸币集。
  • 随着问题的求解将建立另外两个集合,-一个集合(假设为集合a)由已经被选出并符合问题要求的候选解组成,另一个集合(假设为集合b)由已经被选出但并不符合问题要求的候选解组成。
  • 集合a最终要包含问题的解答。在集合a中未出现解答时,问题的候选解集合中至少还要剩余一个候选解。

贪婪算法的有点事简单,直观,容易实现,但是缺点也很明显,那就是他会认为当前选择是满足条件的就进行下一个,而不管当前操作是否会对下一操作产生影响。

2. 分治法

- 分治法就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并- 说简单点就是:把一个复杂的问题分成几个简单的问题,如果分的还不够简单,那就接着分,然后再把各个简单的问题解答,最终的答案就是所有简单问题答案的并集

举个栗子:

给你一个有序数组和一个目标值,请找出这个元素的下标

我们使用二分查找法

Java代码:

public class ErFen{
 public static void main(String[] a){

 }
 public static int Defg(int[] nums, int target){
     int start = 0;
     int end = 0;
     int mid = 0;
     for (int i=0;i<nums.lenrth;i++){
         mid = (start+end)/2;
         if (nums[mid]>target) end = mid;
         else start = mid+1;
     }
     return mid;
 }
}

Python代码:

def Defg(nums:list, target:int)->int:
 start = 0
 end = 0
 mid = 0
 for i in range(len(nums)):
     mid = (start + end) //2
     if nums[mid]>target: end = mid
     else: start = mid
 return mid

看不懂?没关系,看这是什么!!!大宝贝!——LeetCode刷题278-简单-第一个错误版本

分治法所能解决的问题的特性:

  • 整个问题可以分解为两个或者多个较小的问题
  • 对每个子问题的求解类似于对整个问题的求解
  • 如果子问题的规模或者难度仍旧很大,那就继续使用分治法

分治法的难度较高,技巧性较高,需要使用者对问题有着透彻的理解,效率也不高。

但是能解决很多复杂的问题。

3. 动态规划

在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。因此各个阶段决策的选取不能任意确定,它依赖于当前面临的状态,又影响以后的发展。当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线.这种把一个问题看作是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题称为多阶段决策问题。在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化的过程为动态规划方法

这个算法是极其难以理解的,就算理解,代码也很难敲出来。

而也可以这么理解,动态规划的塑像是与分治法的思想相反的,如果说分治法这种吧一个问题分解成多个简单问题的方法叫做自上而下的方法,那么动态规划就是一种自下而上的解决方法。

而动态规划也不会出现贪婪算法那种当前操作最优而不再以下一操作的情况。

举个栗子:

计算二项式Cnm的值

使用杨辉三角可以根据当前二项式生成更简单的二项式

而不会重复生成更简单的相同的二项式

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值