Javascript中的算法-二进制搜索说明

If you want to gain new problem-solving skills and level up your Computer Science knowledge, look no further than Scrimba's free one-hour course, The Working Developer's Guide To Algorithms. It was designed for those who don't have a background in Computer Science and feel they would benefit from learning to think algorithmically.

如果您想获得新的解决问题的技能并提高计算机科学知识,那么,除了Scrimba的免费一小时课程“ The Working Developer's Guide to Algorithms” ,别无所求。 它是为那些没有计算机科学背景并且认为将从学习算法思考中受益的人设计的。

该课程做什么? (What does the course do?)

Our guide takes you through how to craft six different binary search algorithms. In classic Scrimba style, it contains a bunch of challenges along the way, so you'll gain the muscle memory you need to improve your skills as a software developer and work better with algorithms going forward.

我们的指南将指导您如何制作六种不同的二进制搜索算法。 在经典的Scrimba风格中,它包含了许多挑战,因此您将获得所需的肌肉记忆力,以提高自己作为软件开发人员的技能,并更好地应对未来的算法。

You'll learn:

您将学到:

  • Binary Search

    二元搜寻
  • Big O notation

    大O符号
  • Imperative code

    命令代码
  • Recursion

    递归
  • Tail recursion

    尾递归
  • Array splitting

    阵列分割
  • Array view

    阵列视图
  • Partition

    划分

Each algorithm is taught in three stages:

每种算法分三个阶段教授:

  • Walkthrough: Jonathan introduces the algorithm conceptually.

    演练:乔纳森(Jonathan)从概念上介绍了该算法。

  • Implementation: We get our hands dirty by crafting our own versions of the algorithm.

    实现:我们通过精心设计自己的算法版本来弄脏自己。

  • Solution: Jonathan shows us his implementation for comparison.

    解决方案:乔纳森(Jonathan)向我们展示了他的实施方案以进行比较。

先决条件 (Prerequisites)

You'll get the most out of this course if you have a good understanding of Javascript and are ideally already working as a developer or are a Bootcamp graduate.

如果您对Java脚本有很好的了解,并且理想情况下已经是开发人员,或者是Bootcamp毕业生,那么您将从本课程中获得最大收益。

If you're not there yet, check out Scrimba's great free tutorials Introduction to JavaScript and Introduction to ES6+.

如果还没有,请查看Scrimba很棒的免费教程JavaScript 入门和ES6 +入门

讲师简介 (Intro to the instructor)

Jonathan Lee Martin is a software developer, web educator, speaker, and author. He helps other developers achieve their professional and personal goals through writing, speaking, immersive Bootcamps, workshops, and online tutorials.

乔纳森·李·马丁(Jonathan Lee Martin)是软件开发人员,网络教育家,演讲者和作家。 他通过写作,演讲,沉浸式训练营,研讨会和在线教程来帮助其他开发人员实现其职业和个人目标。

With clients including companies such as NASA and HP, he's just the person to take you through the learning journey. So let's get started!

在包括NASA和HP这样的公司在内的客户中,他就是带您完成学习之旅的人。 因此,让我们开始吧!

二元搜寻 (Binary Search)

Graph of Sweeper vs Splitter searches.
Click the image to access the course.

单击图像访问课程。

In the first cast, Jonathan introduces to the concepts of Big-O notation and binary search, the algorithm we'll be working with.

第一个演员表中 ,乔纳森(Jonathan)介绍了Big-O表示法二进制搜索的概念,这是我们将要使用的算法。

Big-O notation is a means of describing the worst-case performance of an algorithm. A Big O of O(n) says that if an array has a length of n elements, the run time will be proportional to n. In other words, an array of seven entries will take 7 lookups in the worst case, just as an array of 7 million entries will take 7 million entries in the worst case. We can also say this algorithm's runtime is linear, as illustrated in the graph above.

Big-O表示法是描述算法最坏情况性能的一种方式。 O(n)的大O表示如果数组的长度为n个元素,则运行时间将与n成正比。 换句话说,在最坏的情况下,七个条目的数组将进行7次查找,而在最坏的情况下,一列700万个条目将花费700万个条目。 我们也可以说该算法的运行时间是线性的,如上图所示。

Binary search is one of several strategies for answering the question "Where does this element appear in a list?"

二进制搜索是回答“此元素在列表中的什么位置?”这一问题的几种策略之一。

When answering the question, there are two main approaches:

回答问题时,有两种主要方法:

  • Sweeper: Checking through each item in the list until the correct item is found.

    清除程序 :检查列表中的每个项目,直到找到正确的项目。

  • Splitter / Binary Search: Splitting the list in half, checking whether you have gone too far or not far enough to locate the item, searching either right or left side respectively and repeating until the item is located.

    拆分 / 二进制搜索 :将列表拆分成两半,检查您是否过分或不足以找到该项目,分别在右侧或左侧搜索,然后重复直到找到该项目。

We can think of these approaches in terms of checking an old-school paper phone book. The sweeper approach would involve looking through each and every entry from the start until the correct one is located. The splitter approach is the one most people would use - opening the book randomly and seeing whether you need to go forwards or back until the entry is located.

我们可以从检查一本过时的纸质电话簿中想到这些方法。 清扫方法将涉及从头开始仔细检查每个条目,直到找到正确的条目为止。 拆分器方法是大多数人会使用的方法-随机打开书本,看看是否需要向前还是向后直到找到条目。

Binary Search is more efficient than the sweeper approach, particularly for larger lists. But it only works when the list has already been sorted.

二进制搜索比清除方法更有效,特别是对于较大的列表。 但是它仅在列表已排序时才有效。

While the sweeper approach has a linear runtime (see graph above) and Big O of O(n), the splitter approach has a sub-linear runtime and a Big O of O(log n).

扫除器方法具有线性运行时间(请参见上图),且O(n)的大为O,而分离器方法具有亚线性运行时间,而O(log n)的大为O。

势在必行 (Imperative)

In the first challenge cast, Jonathan encourages us to get our hands dirty by implementing binary search in a traditional style, that is with a Big O of O(n), using a fixed amount of memory and loops.

在第一个挑战中 ,乔纳森(Jonathan)鼓励我们使用固定数量的内存和循环,以传统方式实现二进制搜索,即使用O(n)的Big O进行二进制搜索。

Jonathan provides us with a test suite we can use to ensure our solution is successful and encourages us to try the challenge ourselves before checking out his implementation. No spoilers here, so head over to the cast to give it try yourself.

乔纳森(Jonathan)为我们提供了一个测试套件,我们可以使用该套件来确保我们的解决方案成功,并鼓励我们在检查其实施之前自己尝试挑战。 这里没有剧透,所以去演员们试试吧。

While this solution is short and close to the original formulation of binary search, you've probably noticed that the solution was difficult to write and not the best solution from a software craftsmanship point of view. Read on to find out ways to level up the solution...

尽管此解决方案简短且接近于二进制搜索的原始格式,但您可能已经注意到,该解决方案很难编写,从软件Craft.io的角度来看不是最佳解决方案。 继续阅读以找出升级解决方案的方法...

递归 (Recursion)

In this cast, we look at improving our binary search by implementing a new version with a few constraints. While our solution should still have a Big O of O(n), it should not use loops and must use recursion. All variables should be initialized with the const operator so they can't be mutated.

在此演员表中 ,我们着眼于通过实施一些有限制的新版本来改进二进制搜索。 尽管我们的解决方案仍应具有O(n)的Big O,但不应使用循环,而必须使用递归。 所有变量都应使用const运算符进行初始化,以免发生突变。

Jonanthan kicks us off with a skeleton version of the solution and then encourages us to try the challenge on our own:

Jonanthan提出了该解决方案的框架版本,然后鼓励我们自己尝试挑战:

let binarySearchWithRecursion = (array, element, compare = defaultCompare) => {
	return -1;
};

export default binarySearchWithRecursion;

If you've completed this challenge, you've probably seen that this solution is a lot easier to read but is quite verbose. In the worst case, it can also result in infinite recursion. Continue with the course to see whether there are ways of streamlining the solution...

如果您已经完成了这项挑战,那么您可能已经发现该解决方案更容易阅读,但很冗长。 在最坏的情况下,它还会导致无限递归。 继续学习本课程,以查看是否存在简化解决方案的方法...

尾递归 (Tail Recursion)

The challenge for the next cast is to improve our previous implementation by reducing duplication.

下一个转换的挑战是通过减少重复来改善我们以前的实现。

Jonathan warns us that the solution will look worse than the previous two solutions, however, it sets us up for some better optimizations further down the line. Head over to the course now to try the challenge for yourself and see Jonathan's solution.

乔纳森(Jonathan)警告我们,该解决方案的外观将比前两个解决方案差,但是,它为我们提供了一些更好的优化方法。 立即前往课程,亲自尝试挑战,看看乔纳森的解决方案。

阵列分割 (Array Splitting)

If you completed the previous challenge, you may have felt that we're still passing a lot of extra information into our binary search via recursion. This cast looks at a way of cleaning that up called array splitting.

如果您完成了上一个挑战,您可能会觉得我们仍在通过递归将大量额外信息传递到二进制搜索中。 这个转换着眼于一种清理阵列分裂的方法

We can think of array splitting in terms of our phone book example from earlier - whenever we decide that half the phone book is irrelevant, we just tear it off and throw it away. Similarly, our next solution should disregard any parts of the array which don't include our desired entry.

我们可以从较早的电话簿示例中想到阵列拆分-每当我们确定电话簿的一半不相关时,我们就将其撕下并扔掉。 同样,我们的下一个解决方案应忽略不包含所需条目的数组的任何部分。

To help us achieve this, Jonathan starts us off with some skeleton code:

为了帮助我们实现这一目标,乔纳森(Jonathan)首先提供了一些框架代码:

let binarySearchWithArraySplitting = (
	array,
	element,
	compare = defaultCompare
) => {
	return -1;
};

Then, as usual, he gives us free rein to try to the solution for ourselves before walking us through his own implementation.

然后,像往常一样,他让我们自由控制自己,然后逐步完成自己的实现。

Although this is an elegant method of binary search, because it involves making a copy of part of the array, it no longer has a Big O of O(n) and has a higher memory usage and slower run time. Continue with the course to find out whether there is a way to regain a higher performance with a similar code solution...

尽管这是一种很好的二进制搜索方法,但是由于它涉及复制数组的一部分,因此它不再具有O(n)的Big O,并且具有更高的内存使用量和更慢的运行时间。 继续学习本课程,以了解是否有办法使用类似的代码解决方案来恢复更高的性能...

阵列视图 (Array View)

In this cast, we look for ways of merging the higher performance of our previous solutions with the elegance of array splitting. To do this, we create an array-like object that responds to the same methods as an array. We'll then inject this object in place of the original array.

在本次发布中 ,我们寻找将先前解决方案的更高性能与优雅的阵列拆分相结合的方法。 为此,我们创建了一个类似数组的对象,该对象响应与数组相同的方法。 然后,我们将注入该对象代替原始数组。

Jonathan gets us started by initializing a function ArrayView which returns an object that expects three arguments: array, start and end. When invoked, ArrayView should return an object with four methods, length, toArray, slice and get.

乔纳森(Jonathan)通过初始化函数ArrayView使我们开始,该函数返回一个需要三个参数的对象: arraystartend 。 调用时, ArrayView应该返回带有四个方法的对象,它们分别是lengthtoArraysliceget

export let ArrayView = (
    array,
    start = 0,
    end = array.length,
) => ({
    length: end - start,
    toArray: () => array.slice(start, end),
    slice: () => ,
    get: () => ,
});

let binarySearchWithArrayView = (array, ...args) =>
    binarySearchWithArraySplitting(ArrayView(array), ...args)

Our challenge is to implement the slice and get methods of ArrayView without making a copy of the original array. Click through to try it out and then view Jonathan's walkthrough.

我们的挑战是实现ArrayViewsliceget方法而不复制原始数组。 单击以进行尝试,然后查看Jonathan的演练。

Although this solution produces better, more readable code, it is longer than some of our previous solutions. Continue with the course to find out if we can retain the benefits of ArrayView while lifting even more of the logic out of binary search code...

尽管此解决方案可产生更好的可读性更好的代码,但比我们以前的某些解决方案更长。 继续学习本课程,以了解我们是否可以保留ArrayView的优势,同时从二进制搜索代码中ArrayView更多逻辑...

阵列分割 (Array Partition)

In the final challenge cast of the course, Jonathan gives us a goal of extracting some of the cryptic bounce logic in our previous version into a data structure.

课程的最后挑战中,乔纳森(Jonathan)为我们提供了一个目标,即将我们先前版本中的一些神秘的反弹逻辑提取到数据结构中。

For this, we need a simple data structure which returns the middle, left or right part of an array. To start us off, Jonathan sets up a function ArrayPartition:

为此,我们需要一个简单的数据结构,该结构将返回数组的中间,左侧或右侧。 首先,Jonathan设置了一个ArrayPartition函数:

export let ArrayPartition = (array, pivot) => ({
	left: () => array.slice(0, pivot),
	middle: () => array.get(pivot),
	right: () => array.slice(pivot + 1, array.length),
});

Next, Jonathan sets up a new version of binary search called binarySearchWithPartition, which has a starting signature the same as binarySearchWithArraySplitting:

接下来,乔纳森(Jonathan)设置了一个新的二进制搜索版本,名为binarySearchWithPartition ,其起始签名与binarySearchWithArraySplitting相同:

let binarySearchWithPartition = (array, element, compare = defaultCompare) => {
	if (array.length === 0) {
		return -1;
	}
	const middle = Math.floor(array.length / 2);
	const comparison = compare(element, array.get(middle));

	if (comparison === 0) {
		return middle;
	}

	//bounce logic
	const [left, right] =
		comparison === -1 ? [0, middle - 1] : [middle + 1, array.length];
	//end of bounce logic

	const subIndex = binarySearchWithArraySplitting(
		array.slice(left, right),
		element,
		compare
	);

	return subIndex === -1 ? -1 : left + subIndex;
};

let binarySearchWithPartitionAndView = (array, ...args) =>
	binarySearchWithPartition(ArrayView(array), ...args);

Our challenge now is to rewrite binarySearchWithPartition with none of the bounce logic highlighted above, instead of creating an array partition and making calls to its left, middle and right methods.

现在,我们面临的挑战是使用上面未突出显示的bounce逻辑来重写binarySearchWithPartition ,而不是创建数组分区并对其左,中和右方法进行调用。

Head over to the course now to try the challenge for yourself. As Jonathan points out, this challenge is tricky, so it's ok to skip to his solution if you get stuck for too long but give it a go first on your own.

立即前往课程,亲自尝试挑战。 正如乔纳森(Jonathan)指出的那样,这个挑战是棘手的,因此,如果您被困太久而自己先尝试,可以跳过他的解决方案。

结语 (Wrap-Up)

You've made it to the end of the course - great work! We've covered several approaches to binary search, all with their own benefits and drawbacks, and we've built some great muscle memory for working effectively with algorithms.

您已经完成课程了-很棒的工作! 我们已经介绍了几种二进制搜索方法,每种方法都有其自身的优点和缺点,并且已经建立了一些强大的内存来有效地使用算法。

Now that you've seen six different approaches to binary search, you'll probably notice it pop up in many different places in programming.

既然您已经看到了六种不同的二进制搜索方法,您可能会注意到它出现在编程的许多不同地方。

Jonathan's full course featuring 10 algorithms will be coming out at the end of the year, but in the meantime, I hope you can put your newfound binary search skills to good use.

乔纳森(Jonathan)的完整课程包含10种算法,将于今年年底推出,但与此同时,我希望您可以充分利用新发现的二进制搜索技能。

Happy coding :)

快乐的编码:)

翻译自: https://www.freecodecamp.org/news/the-working-developers-guide-to-algorithms/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值