在各种查找算法中,线性查找是最简单的一种。如果您曾经逐项查看清单,直到找到想要的内容,那么您已经做过一次线性查找了!
除了容易理解之外,线性查找的另一个优点是它可以用于未排序的数据,这与某些其他查找算法不同。由于这种通用性,当无法对数据进行排序时,它就是一个可用的选项。本文将使用 Python 进行讲解,如果在开始之前您想先温习一下 Python,可以查看 Python Programming 技能路径。
什么是线性查找?
线性查找是一种通过逐个检查列表中的每个元素来定位特定值的算法。它从第一个元素开始,与目标进行比较,然后继续遍历列表,直到找到目标或到达列表末尾。它是一个简单直观的算法。
线性查找不需要数据预先排序 ,因此主要用于未排序的数据集。这使它在无法进行排序、或您处理的是原始数据时非常实用。然而,这一优点也带来了代价:与需要预排序数据的其他算法相比,它的效率较低。
当您处理相对较小的数据集或无法对数据进行排序时,线性查找是理想之选。下面的示意图给出了简化说明。
何时选择线性查找
让我们看看它的优势以及一个明显的缺点:
为什么选择线性查找?
在我看来,线性查找有三大优点:
当简单至上时
线性查找最大的优势之一就是简单。该算法易于理解和实现。无需考虑复杂的排序或拆分数据。只需从列表开头开始,逐个检查元素,直到找到所需内容。
当您没有时间排序时
与二分查找等其他算法不同,线性查找不要求数据集已排序。因此,当您需要快速找到某个元素时,它再合适不过。
当您需要通用性时
线性查找的另一优势在于其通用性。它不仅适用于数组,还适用于其他 Python 数据结构 ,如链表。只要能按顺序访问元素,线性查找就能完成任务。它足够灵活,可处理不同的数据类型,从数字到字符串,甚至对象。
为什么要谨慎考虑线性查找?
有一个需要注意的问题:
当效率很重要时
线性查找的主要缺点在于其低效,尤其在大型数据集上。其时间复杂度为 O(n) ,这意味着在最坏情况下,您可能需要检查列表中的每一个元素!如果您处理的是成千上万甚至数百万条记录,这会显著拖慢速度。不过,对于小型数据集,这种低效可能可以忽略不计。
在 Python 中实现线性查找算法
在 Python 中,常见的线性查写法有两种:迭代法和递归法。为演示这两种方法,我们先创建一个不含重复的 100 个数字的简单数据集。
numbers = [ 12 , 7 , 19 , 3 , 15 , 25 , 8 , 10 , 42 , 56 , 77 , 23 , 89 , 65 , 33 , 99 , 14 , 2 , 37 , 81 ,
48 , 64 , 22 , 91 , 6 , 40 , 59 , 87 , 28 , 53 , 74 , 9 , 16 , 39 , 71 , 93 , 54 , 32 , 61 , 27 ,
35 , 18 , 49 , 68 , 83 , 46 , 57 , 29 , 95 , 11 , 26 , 44 , 78 , 5 , 66 , 73 , 41 , 85 , 31 , 50 ,
20 , 63 , 88 , 34 , 90 , 60 , 67 , 4 , 52 , 36 , 62 , 80 , 21 , 84 , 98 , 13 , 45 , 69 , 30 , 38 ,
47 , 17 , 92 , 55 , 70 , 76 , 82 , 24 , 43 , 1 , 100 , 58 , 96 , 75 , 97 , 94 , 86 , 72 , 51 , 79 ] 技术支持
迭代法
迭代法可能是理解线性查找最容易的方式。您遍历整个列表,逐个检查元素,直到找到目标或到达末尾。
首先,我们来创建函数:
遍历列表 arr 中的每个元素。
将每个元素与目标进行比较。
如果匹配,返回找到目标的索引。
如果遍历结束仍未找到目标,返回 1 表示该元素不存在。
def linear_search_iterative ( arr, target) :
for i in range ( len ( arr) ) :
if arr[ i] == target:
return i
return - 1 技术支持
接下来,我们在数据集上运行该函数,并打印结果:
target = 91
index = linear_search_iterative( numbers, target)
if index != - 1 :
print ( f"Target { target} found at index { index} " )
else :
print ( f"Target { target} not found!" ) 技术支持
运行此代码,我们在第 23 个元素处找到了目标值。
递归法
递归法使用一个自我调用的函数在列表中进行查找。每次递归调用处理一个元素,然后继续处理下一个元素。该过程持续到找到目标或到达列表末尾为止。
我们来创建递归函数:
从检查第一个元素(索引 0)开始。
如果目标匹配,则返回该索引。
如果目标不匹配,则递归调用同一函数,并将索引加 1 以检查下一个元素。
递归持续进行,直到找到目标或到达列表末尾。
def linear_search_recursive ( arr, target, index= 0 ) :
if index >= len ( arr) :
return - 1
if arr[ index] == target:
return index
return linear_search_recursive( arr, target, index + 1 ) 技术支持
我们可以使用与上文相同的代码来运行该函数,它为目标返回相同的索引值:23。
请注意,这种方法使用更多内存,因为每次递归调用都会在调用栈中增加一层。 请参阅 Understanding Recursive Functions in Python 以进一步了解递归。
时间与空间复杂度
评估算法效率的一种方式是查看其时间和空间复杂度。请查看 Analyzing Complexity of Code through Python 教程,了解衡量算法复杂度的不同方法。
时间复杂度
算法的时间复杂度指的是随输入规模增长,完成所需时间如何增加。 线性查找的时间复杂度 为 O(n),其中 n 是列表中的元素数量。这意味着在最坏情况下,算法可能需要检查每一个元素,才能找到目标(或确定目标不在列表中)。 这相当低效,尤其对大型数据集而言。因此,线性查找最适合在我们只有较小的、未排序且只需查找一次的数据集上使用。
空间复杂度
算法的空间复杂度衡量的是随着输入规模增长所需内存的多少。线性查找算法的空间复杂度取决于我们采用的方法。 对于迭代法,空间复杂度为 O(1),这意味着无论输入列表多大,算法只需要常量级的内存。这是因为迭代版本不需要除输入列表和少量用于记录当前索引的变量之外的额外内存。
对于递归法,空间复杂度为 O(n),即所需内存随输入列表大小线性增长。这是由于递归函数调用会为列表中的每个元素在调用栈中增加一层。列表越大,存储这些调用所需的内存就越多,因此递归方法的空间效率更低。
线性查找的实际应用
尽管线性查找并非大型数据集的最高效算法,但在许多场景中具有实用价值。下面看看哪些情况下它是完成任务的最佳工具。
排序不切实际的小型数据集
当处理小型数据集且不值得花时间或资源去排序时,我通常会首选线性查找。在这些情况下,与其为更复杂的查找算法付出排序的开销,不如利用线性查找的简洁性,反而更高效。
查找目标的首次出现位置
当您需要找到目标值的首次出现位置时,线性查找也是常见选择。由于线性查找按顺序逐个遍历元素,它天然会返回目标的第一次出现位置。这在例如在字符串中搜索某个字符首次出现的位置等场景中特别有用。
当只需最少的准备时
有时,线性查找的简单性让它成为最佳选项。比如,您只是快速检索一次、且不打算重复使用这些数据,或者需要迅速得到一个大致答案时,线性查找几乎无需设置,可以为您节省时间。
线性查找与其他查找算法
线性查找只是众多查找算法中的一种。我们来看看另外两种常见的查找算法:二分查找与哈希查找。
线性查找 vs. 二分查找
二分查找 是在有序数组中通过反复将查找区间一分为二来定位目标值位置的算法。其时间复杂度为 O(log n),远比线性查找高效。然而,它只适用于已排序的数据。当您的数据集已排序时,二分查找几乎总是更佳选择,因为它每一步都将搜索空间减半,大幅减少找到目标所需的比较次数。
在数据未排序的情况下,线性查找可能更合适,因为无论数据是否排序,线性查找都能工作。不过,时间复杂度为 O(n) 的线性查找在已排序数据集上效率要低得多。
线性查找 vs. 哈希查找
哈希查找是借助哈希表快速定位元素的方法。其时间复杂度为 O(1),明显快于线性查找或二分查找。不过,这种速度以代价为前提:您需要先构建哈希表,这既耗时又需要额外内存。哈希查找最适合需要对大型数据集进行多次查找的场景,这样前期的构建成本可以随着时间摊销。
相比之下,线性查找无需任何前期准备,更为直接。当您只需要查找一次或少量几次时,线性查找是更好的选择,因为没必要为哈希表的构建投入额外时间。在线性查找中也更节省空间,因为无需为算法存储哈希表。
常见陷阱及规避方法
尽管线性查找很直观,但仍有一些常见错误需要留意。
迭代中的“差一”错误
实现线性查找时最常见的错误之一是“差一”错误,即在错误的索引开始或结束迭代。务必记住,Python 列表是从零开始索引的,即第一个元素的索引为 0。如果忘记这一点,您可能会不小心跳过第一个元素,或者多迭代一步,导致结果不正确或报错。
误解返回的索引含义
另一个常见陷阱是误解在列表中未找到目标时函数的返回值。按照惯例,许多查找算法(包括线性查找)会返回 -1 表示目标不存在。然而,一些初学者可能会认为它返回 None、False 或其他值,从而在解读结果时产生困惑。
何时不应使用线性查找
如前所述,线性查找在大型数据集上会很快变得低效。因此,它最好用于小型、未排序的数据集,或快速、一次性的查找。
结论
作为最直观的查找方法之一,线性查找按顺序检查列表中的每个元素,易于理解和实现。对于小型数据集上的快速一次性查找,尤其是在数据未排序时,线性查找是一个可靠的选择。
如果您对查找算法感兴趣,不妨看看我在该系列中的其他文章:Binary Search in Python 、Depth-First Search in Python 和 Breadth-First Search in Python 。您或许也会对更进阶的话题感兴趣: AVL Tree: Complete Guide With Python Implementation 和 Introduction to Network Analysis in Python 。
在各种查找算法中,线性查找是最简单的一种。如果您曾经逐项查看清单,直到找到想要的内容,那么您已经做过一次线性查找了!
除了容易理解之外,线性查找的另一个优点是它可以用于未排序的数据,这与某些其他查找算法不同。由于这种通用性,当无法对数据进行排序时,它就是一个可用的选项。本文将使用 Python 进行讲解,如果在开始之前您想先温习一下 Python,可以查看 Python Programming 技能路径。
什么是线性查找?
线性查找是一种通过逐个检查列表中的每个元素来定位特定值的算法。它从第一个元素开始,与目标进行比较,然后继续遍历列表,直到找到目标或到达列表末尾。它是一个简单直观的算法。
线性查找不需要数据预先排序,因此主要用于未排序的数据集。这使它在无法进行排序、或您处理的是原始数据时非常实用。然而,这一优点也带来了代价:与需要预排序数据的其他算法相比,它的效率较低。
当您处理相对较小的数据集或无法对数据进行排序时,线性查找是理想之选。下面的示意图给出了简化说明。
何时选择线性查找
让我们看看它的优势以及一个明显的缺点:
为什么选择线性查找?
在我看来,线性查找有三大优点:
当简单至上时
线性查找最大的优势之一就是简单。该算法易于理解和实现。无需考虑复杂的排序或拆分数据。只需从列表开头开始,逐个检查元素,直到找到所需内容。
当您没有时间排序时
与二分查找等其他算法不同,线性查找不要求数据集已排序。因此,当您需要快速找到某个元素时,它再合适不过。
当您需要通用性时
线性查找的另一优势在于其通用性。它不仅适用于数组,还适用于其他 Python 数据结构,如链表。只要能按顺序访问元素,线性查找就能完成任务。它足够灵活,可处理不同的数据类型,从数字到字符串,甚至对象。
为什么要谨慎考虑线性查找?
有一个需要注意的问题:
当效率很重要时
线性查找的主要缺点在于其低效,尤其在大型数据集上。其时间复杂度为 O(n),这意味着在最坏情况下,您可能需要检查列表中的每一个元素!如果您处理的是成千上万甚至数百万条记录,这会显著拖慢速度。不过,对于小型数据集,这种低效可能可以忽略不计。
在 Python 中实现线性查找算法
在 Python 中,常见的线性查写法有两种:迭代法和递归法。为演示这两种方法,我们先创建一个不含重复的 100 个数字的简单数据集。
迭代法
迭代法可能是理解线性查找最容易的方式。您遍历整个列表,逐个检查元素,直到找到目标或到达末尾。
首先,我们来创建函数:
遍历列表
arr中的每个元素。将每个元素与目标进行比较。
如果匹配,返回找到目标的索引。
如果遍历结束仍未找到目标,返回
1表示该元素不存在。接下来,我们在数据集上运行该函数,并打印结果:
运行此代码,我们在第 23 个元素处找到了目标值。
递归法
递归法使用一个自我调用的函数在列表中进行查找。每次递归调用处理一个元素,然后继续处理下一个元素。该过程持续到找到目标或到达列表末尾为止。
我们来创建递归函数:
我们可以使用与上文相同的代码来运行该函数,它为目标返回相同的索引值:23。
请注意,这种方法使用更多内存,因为每次递归调用都会在调用栈中增加一层。请参阅 Understanding Recursive Functions in Python 以进一步了解递归。
时间与空间复杂度
评估算法效率的一种方式是查看其时间和空间复杂度。请查看 Analyzing Complexity of Code through Python 教程,了解衡量算法复杂度的不同方法。
时间复杂度
算法的时间复杂度指的是随输入规模增长,完成所需时间如何增加。线性查找的时间复杂度为 O(n),其中 n 是列表中的元素数量。这意味着在最坏情况下,算法可能需要检查每一个元素,才能找到目标(或确定目标不在列表中)。这相当低效,尤其对大型数据集而言。因此,线性查找最适合在我们只有较小的、未排序且只需查找一次的数据集上使用。
空间复杂度
算法的空间复杂度衡量的是随着输入规模增长所需内存的多少。线性查找算法的空间复杂度取决于我们采用的方法。对于迭代法,空间复杂度为 O(1),这意味着无论输入列表多大,算法只需要常量级的内存。这是因为迭代版本不需要除输入列表和少量用于记录当前索引的变量之外的额外内存。
对于递归法,空间复杂度为 O(n),即所需内存随输入列表大小线性增长。这是由于递归函数调用会为列表中的每个元素在调用栈中增加一层。列表越大,存储这些调用所需的内存就越多,因此递归方法的空间效率更低。
线性查找的实际应用
尽管线性查找并非大型数据集的最高效算法,但在许多场景中具有实用价值。下面看看哪些情况下它是完成任务的最佳工具。
排序不切实际的小型数据集
当处理小型数据集且不值得花时间或资源去排序时,我通常会首选线性查找。在这些情况下,与其为更复杂的查找算法付出排序的开销,不如利用线性查找的简洁性,反而更高效。
查找目标的首次出现位置
当您需要找到目标值的首次出现位置时,线性查找也是常见选择。由于线性查找按顺序逐个遍历元素,它天然会返回目标的第一次出现位置。这在例如在字符串中搜索某个字符首次出现的位置等场景中特别有用。
当只需最少的准备时
有时,线性查找的简单性让它成为最佳选项。比如,您只是快速检索一次、且不打算重复使用这些数据,或者需要迅速得到一个大致答案时,线性查找几乎无需设置,可以为您节省时间。
线性查找与其他查找算法
线性查找只是众多查找算法中的一种。我们来看看另外两种常见的查找算法:二分查找与哈希查找。
线性查找 vs. 二分查找
二分查找是在有序数组中通过反复将查找区间一分为二来定位目标值位置的算法。其时间复杂度为 O(log n),远比线性查找高效。然而,它只适用于已排序的数据。当您的数据集已排序时,二分查找几乎总是更佳选择,因为它每一步都将搜索空间减半,大幅减少找到目标所需的比较次数。
在数据未排序的情况下,线性查找可能更合适,因为无论数据是否排序,线性查找都能工作。不过,时间复杂度为 O(n) 的线性查找在已排序数据集上效率要低得多。
线性查找 vs. 哈希查找
哈希查找是借助哈希表快速定位元素的方法。其时间复杂度为 O(1),明显快于线性查找或二分查找。不过,这种速度以代价为前提:您需要先构建哈希表,这既耗时又需要额外内存。哈希查找最适合需要对大型数据集进行多次查找的场景,这样前期的构建成本可以随着时间摊销。
相比之下,线性查找无需任何前期准备,更为直接。当您只需要查找一次或少量几次时,线性查找是更好的选择,因为没必要为哈希表的构建投入额外时间。在线性查找中也更节省空间,因为无需为算法存储哈希表。
常见陷阱及规避方法
尽管线性查找很直观,但仍有一些常见错误需要留意。
迭代中的“差一”错误
实现线性查找时最常见的错误之一是“差一”错误,即在错误的索引开始或结束迭代。务必记住,Python 列表是从零开始索引的,即第一个元素的索引为 0。如果忘记这一点,您可能会不小心跳过第一个元素,或者多迭代一步,导致结果不正确或报错。
误解返回的索引含义
另一个常见陷阱是误解在列表中未找到目标时函数的返回值。按照惯例,许多查找算法(包括线性查找)会返回
-1表示目标不存在。然而,一些初学者可能会认为它返回None、False或其他值,从而在解读结果时产生困惑。何时不应使用线性查找
如前所述,线性查找在大型数据集上会很快变得低效。因此,它最好用于小型、未排序的数据集,或快速、一次性的查找。
结论
作为最直观的查找方法之一,线性查找按顺序检查列表中的每个元素,易于理解和实现。对于小型数据集上的快速一次性查找,尤其是在数据未排序时,线性查找是一个可靠的选择。
如果您对查找算法感兴趣,不妨看看我在该系列中的其他文章:Binary Search in Python、Depth-First Search in Python 和 Breadth-First Search in Python。您或许也会对更进阶的话题感兴趣: AVL Tree: Complete Guide With Python Implementation 和 Introduction to Network Analysis in Python。