8月阅读周·秒懂算法:用常识解读数据结构与算法:算法为何重要

举报
叶一一 发表于 2024/08/26 14:09:03 2024/08/26
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读七个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScri...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读七个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》

当前阅读周书籍《秒懂算法:用常识解读数据结构与算法》

算法为何重要

算法就是完成特定任务所需的一组操作。

有序数组

有序数组和第1章中的“传统”数组几乎完全一致。它们唯一的区别在于有序数组中的值是按顺序排列的。也就是说,插入新值时,这个值必须被放到一个合适的格子中,以免打乱数组的顺序。

以数组[3, 17, 80, 202]为例,假设要插入值75。如果这是一个传统数组,那么可以像下图这样在末尾插入75。但如果这是有序数组,那么别无选择,只能把75插入合适的位置,以保证数组的值是递增的,下面来一步步分析这个过程。

第1步:检查索引0处的值来确定要在它前面还是后面插入新值75。因为75比3大,所以必须插到它右边。不过,因为依然不知道具体位置,所以必须检查下一个格子。这样的步骤叫作比较。我们会比较要插入的值和有序数组中现有的值。

第2步:检查下一个格子的值。75比17大,所以还得继续右移。

第3步:检查下一个格子的值。这次的值是80,比要插入的75大。因为我们碰到了第一个比75大的值,所以得出结论:要保证有序数组的有序性,75必须紧挨着放在80的左边。为此,需要右移数据为75腾出空间。

第4步:把最后一个值右移。

第5步:把倒数第二个值右移。

第6步:把75插到正确的位置。

在这个例子中,数组有4个元素,插入用了6步。而对于包含N个元素的有序数组,插入则需要花N+2步。

有序数组的查找

假设我们有一个常规数组[17, 3, 75, 202, 80]。如果要查找一个数组中不存在的值22,则需要检查每个元素,因为22有可能在数组中的任何位置。除非在检查到数组末尾之前就找到这个值,否则只能全部检查一遍。

而对有序数组来说,即便值不在数组中,也能提前结束查找。假设要在有序数组[3, 17,75, 80, 202]中查找值22,因为22不可能在75右边,所以只需检查到75即可。

有序数组线性查找的Ruby实现如下:

def linear_search(array, search_value)
  # 遍历数组中的每个元素:
  array.each_with_index do |element, index|
    # 如果找到了值,就返回其索引:
    if element == search_value
      return index
    # 如果找到了一个比所查找值大的元素,那么可以提前退出循环:
    elsif element > search_value
      break
    end
  end
  # 如果没有找到所查找的值,则返回nil:
  return nil
end

这种方法有两个参数:array是查找的有序数组,search_value是要查找的值。

要在上面的范例数组中查找22,可以像下面这样调用上面的函数:

p linear_search([3, 17, 75, 80, 202], 22)

如你所见,linear_search方法在寻找search_value时需要遍历数组的每一个元素。当遍历到的element大于search_value时,查找立刻结束。这是因为剩下的数组中不可能含有search_value。

二分查找

下面来看看如何将二分查找应用于有序数组。假如有序数组有9个元素。假设我们想在这个有序数组中查找7。二分查找的过程如下:

第1步:从中间的格子开始查找。因为可以用数组长度除以2来计算其索引,所以可以立刻跳转到这个格子,然后检查其中的值。因为值是9,所以我们知道7在它的左边。这样就排除了9右边的一半的格子(包括它自己)。

第2步:在9左边的格子中,检查最中间的值。因为中间有两个值,所以可以随机选择其中一个,这里以左边的值为例,这个值是4,7一定在它的右边。我们可以排除4及其左边的格子。

第3步:现在还有两个可能是7的格子。随机选择两个格子中左边的那个。

第4步:检查最后一个格子。找到7用了4步。

代码实现:二分查找

以下是二分查找的Ruby实现:

def binary_search(array, search_value)
  # 首先,设定要查找的值所在位置的上下限。下限就是数组的第一个值,而上限就是最后一个值:
  lower_bound = 0
  upper_bound = array.length - 1
  # 在循环中不停检查上下限之间最中间的值:
  while lower_bound <= upper_bound do
    # 我们找到了上下限之间的中点:(因为在Ruby中整数除法的结果会向下取整,所以无须担心这个值不是整数。)
    midpoint = (upper_bound + lower_bound) / 2
    # 检查中点的值:
    value_at_midpoint = array[midpoint]
    # 如果中点的值就是要查找的值,那么查找结束。如果不是,那么就根据这个值与要查找的值的大小关系调整上下限:
    if search_value == value_at_midpoint
      return midpoint
    elsif search_value < value_at_midpoint
      upper_bound = midpoint - 1
    elsif search_value > value_at_midpoint
      lower_bound = midpoint + 1
    end
  end
  # 如果下限已经超过上限,那么就意味着要查找的值不在这个数组中:
  return nil
end

binary_search方法同样以array和search_value为参数。可以像下面这样调用这个方法:

p binary_search([3, 17, 75, 80, 202], 22)

二分查找与线性查找

对于比较小的有序数组,二分查找相对于线性查找的优势并不大。

对线性查找来说,有多少个元素就有多少步。每次加倍数据量,查找的步骤数也要加倍。而二分查找在加倍数据量时,只需要多1步。

有序数组的插入更慢,而查找更快。

总结

要达成一个计算目标通常有多种方法,而你选择的算法可能会严重影响代码的速度。

还有一点很重要:通常没有完美适用于任何情况的数据结构或者算法。例如,有序数组可以用二分查找,但这并不意味着就应该一直使用它。在某些不太需要查找数据而只需插入数据的场合,因为插入操作更迅速,所以传统数组可能更合适。

分析算法的方法就是计算其需要的步骤数。


作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。