二分查找(Binary Search)需要注意的问题,以及在数据库内核中的
问题背景 今年的实习生招聘考试,我出了一道二分查找(Binary Search)的题目。题目大意如下: 给定一个升序排列的自然数数组,数组中包含重复数字,例如:[1,2,2,3,4,4,4,5,6,7,7]。问题:给定任意自然数,对数组进行二分查找,返回数组正确的位置,给出函数
问题背景
今年的实习生招聘考试,我出了一道二分查找(Binary Search)的题目。题目大意如下:
给定一个升序排列的自然数数组,数组中包含重复数字,例如:[1,2,2,3,4,4,4,5,6,7,7]。问题:给定任意自然数,对数组进行二分查找,返回数组正确的位置,给出函数实现。注:连续相同的数字,返回第一个匹配位置还是最后一个匹配位置,由函数传入参数决定。
我为什么会出这道题目?
-
二分查找在数据库内核实现中非常重要
在数据库的内核实现中,二分查找是一个非常重要的逻辑,几乎99%以上的SQL语句(所有索引上的范围扫描/等值查询/Unique查询等),都会使用到二分查找进行数据的定位。
考虑一个数据库表t1(a int primary key, b int),表上的b字段有一个B+树索引,表中记录的b字段取值,就是题目中的[1,2,2,3,4,4,4,5,6,7,7]序列。此时,给定以下的两条查询语句,就是使用到了不同的二分查找逻辑:
SQL1: ? select * from t1 where b >?4;
SQL2: select * from t1 where b >= 4;
针对SQL1,索引的二分查找,就需要跳过所有的4,从最后一个4之后开始返回所有记录;针对SQL2,二分查找就需要定位到第一个4,然后顺序读取所有记录。
除此之外,针对数据库中其他的查询逻辑,二分查找还需要附带更多的功能,例如:
SQL3: select * from t1 where b 2;
SQL4: select * from t1 where b 2;
由于数据库索引同时支持反向扫描,因此SQL3、SQL4的语句,都可以使用索引反向扫描。反向扫描时,SQL3需要定位到索引中的第一个2;而SQL4,则需要定位到索引的最后一个2,然后开始反向返回满足查询条件的索引记录。
-
二分查找在程序设计中,是一个十分基础并且易错的功能
第一个真正正确的二分查找算法,在第一个二分查找实现之后的12年,才被发表出来。通过Google,输入Binary Search或者是二分查找关键字,有大量的相关的文章或者博客讨论此话题。
二分查找实现,需要注意的问题
本文不准备详细介绍一个正确的二分查找应该是如何实现的,毕竟现在网上有着大量的正确版本。接下来,根据批改试卷过程中发现的一些问题,做一些简单的分析,希望对大家实现一个有效的二分查找算法,甚至是一个数据库内可用的二分查找算法,有所帮助。
问题一:是否检查参数的有效性
大量的试卷,在给出此问题的解决算法时,直接拿着low,high参数开始进行计算,但是却没有检查low/high参数。low/high是否相同,数组中是否存在记录?low/high构成的区间是否有效?代码的鲁棒性不足。
在数据库的二分查找实现中,一般是对一个索引页面进行二分查找。索引页面中有可能根本不存在用户的记录(索引页面中的记录全部被删除,又没有与兄弟页面合并时),此时,low/high均为0,此时如果根据low/high计算出来的mid进行记录的读取,就存在逻辑错误。
问题二:二分查找中值的计算
这是一个经典的话题,如何计算二分查找中的中值?试卷中,大家一般给出了两种计算方法:
算法一: mid = (low + high) / 2
算法二: mid = low + (high – low)/2
乍看起来,算法一简洁,算法二提取之后,跟算法一没有什么区别。但是实际上,区别是存在的。算法一的做法,在极端情况下,(low + high)存在着溢出的风险,进而得到错误的mid结果,导致程序错误。而算法二能够保证计算出来的mid,一定大于low,小于high,不存在溢出的问题。
回到数据库二分查找,数据库的一个索引页面(大小一般是8k或者是16k),能够存储的索引记录是有限的,因此肯定不会出现(low + high)溢出的风险。这也是为什么InnoDB中的中值,采用的就是算法一的实现。但是,作为一个严谨的程序设计人员,还是推荐使用算法二,将任何潜在的风险,扼杀于摇篮之中。
问题三:递归实现二分查找
超过一半的试卷,使用了递归调用的方式实现二分查找。不能说递归实现有错,而是在于实现效率问题。总所周知,递归调用存在着压栈/出栈的开销,其效率是比较低下的。而以数据库这样一个极端优化代码效率,提供快速查询响应的系统来说,效率是第一位的。不建议使用递归方式实现二分查找,至少在数据库内核实现中是不允许使用的。据我所知,所有的开源数据库系统,例如:InnoDB,PostgreSQL都未采用递归方式实现二分查找。
问题四:如何查找第一个/最后一个等值
回到题目,要求根据传入的参数不同,返回第一个/最后一个等值项。在本文的背景部分,我也解释了此问题对应的数据库查询(>,>=查询需求是不同的)。在试卷中,超过80%的同学的答案都是先进行二分查找,待定位到相同值之后,再根据传入的flag(用户需求:flag = 1,返回第一个等值项;flag = 0,返回最后一个等值项),进行顺序遍历,直至定位到满足条件的项。
同样,不能说这个实现是错的,但是也存在着性能问题。性能性能性能,永远是数据库内核实现考虑的重点之一(相信也是所有应用程序的一个指标)。数据库中,除了主键索引/Unique索引能够保证键值唯一之外,很多二级辅助索引都是存在相同键值的,有时相同键值的项会超过千项(考虑一个用户的订单,或者是购买记录)。
假设一个索引页面,保存着400项记录,均为相同键值。此时,使用先二分查找,后顺序遍历的算法,二分查找只能使用一次,顺序遍历199次,最终对比了200次。效率非常之低。当然,我也欣喜的看到另外一小部分同学的做法(我期待看到的算法),用flag来纠正每次比较的最终结果。例如:比较相等(相等用0表示,大于为1,小于为-1),但是flag = 1,则返回纠正后的比较结果为1,需要移动二分查找的high到mid,继续二分(反之,若flag = 0,则返回纠正后的结果为-1,需要移动二分查找的low到mid,继续二分)。如此一来,等值仍旧可以进行二分查找,最终的对比只需要9次,远远小于200次。
此问题,进一步引出了下一个问题,数据库中如何实现一个通用的,更为复杂的二分查找算法?
问题五:数据库中的二分查找实现举例
数据库中的二分查找,更为复杂,需要实现一个通用型的二分查找算法,使用于各种不同的SQL查询场景。
InnoDB针对不同的SQL语句,总结出四种不同的Search Mode,分别为:
#define????PAGE_CUR_G ? ? ? ? ?1????????>查询
#define????PAGE_CUR_GE ? ? ? ? 2????????>=,=查询
#define????PAGE_CUR_L ? ? ? ? ?3????????
#define????PAGE_CUR_LE ? ? ? ? 4????????
然后根据这四种不同的Search Mode,在二分查找碰到相同键值时进行调整。例如:若Search Mode为PAGE_CUR_G或者是PAGE_CUR_LE,则移动low至mid,继续进行二分查找;若Search Mode为PAGE_CUR_GE或者是PAGE_CUR_L,则移动high至mid,继续进行二分查找。
我们的TNT引擎,采用了与InnoDB不同的方案,但是也实现了相同的功能。TNT引擎针对相同键值的调整总结为下图,在此我就不做解释了,大家可以尝试着自己进行分析。
/* 操作符 includeKey???? forward???? compare result: 1 ? ?0????????-1 */
=============================================================================
>=????????????1????????????1????| ? ? ? ? ? ?1????????????-1????????-1
= ? ? ? ? ? ? 1????????????1????|????????????1????????????-1????????-1
> ? ? ? ? ? ? 0????????????1????|????????????1 ? ? ? ? ? ??1????????-1
-1????????-1
1????????-1
=============================================================================
总结
本文通过一个二分查找的题目,以及同学们在解答题目中暴露出来的问题,分析了一个安全可靠高效的二分查找,应该注意哪些问题。并简要分析了数据库内核实现中的二分查找实现,希望对大家在以后设计二分查找算法时,有所帮助。



ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









iPhone で「探す」をオフにするとどうなりますか? 「iPhone を探す」は、紛失または盗難に遭ったデバイスを見つけるのに役立ちます。 「iPhone を探す」を有効にすると、地図上でデバイスの位置を追跡したり、サウンドを鳴らしたり、デバイスを見つけやすくしたりできます。 Find My には、他人が iPhone を使用できないようにするためのアクティベーション ロックも含まれています。 「iPhone を探す」をオフにすると、これらの機能がすべて失われるため、紛失した Apple デバイスの回復が困難になる場合があります。 「iPhone を探す」は非常に便利ですが、携帯電話を販売、寄付、下取りに出したり、バッテリー交換やその他のサービスに送ったりする場合は、この機能を無効にする必要があります。これにより、誰もあなたに関する情報にアクセスできなくなります

C# で Array.IndexOf 関数を使用して、配列内の要素のインデックスを検索します。C# プログラムでは、配列内の要素のインデックスを検索する必要がある場合、Array.IndexOf 関数を使用できます。 Array.IndexOf 関数は、指定された配列範囲内で指定された要素を検索し、最初に出現した要素のインデックスを返します。要素が見つからない場合は、-1 が返されます。以下は、Array.IndexOf 関数を使用して配列内の要素を検索する方法を示すサンプル コードです。

Apple の Find My アプリを使用すると、iPhone またはその他のデバイスの位置を特定して、紛失したり忘れられたりするのを防ぐことができます。 Find My はデバイスを追跡するのに便利なツールですが、プライバシーの問題が心配な場合、バッテリーを消耗したくない場合、またはその他の理由で無効にすることもできます。幸いなことに、iPhone で「探す」をオフにする方法はいくつかあり、この記事ですべて説明します。 iPhoneで「探す」をオフにする方法【4つの方法】 iPhoneで「探す」をオフにする方法は4つあります。方法 1 を使用して検索をオフにした場合は、検索を無効にするデバイスからこれを行うことができます。方法 2、3、4 を続行するには、「電話を探す」をオフにする iPhone の電源をオフにするか、

ハードドライブのシリアル番号と MAC アドレスは、コンピュータ ハードウェアの重要な識別子であり、コンピュータ システムの管理と保守に非常に役立ちます。この記事では、ハードディスクのシリアル番号とMACアドレスを確認する方法を紹介します。 1. ハードドライブのシリアル番号を見つける ハードドライブのシリアル番号は、ハードドライブの製造元がハードドライブを識別および追跡するために使用する一意の識別子です。オペレーティング システムが異なると、ハード ドライブのシリアル番号を見つける方法が若干異なります。 Windows: コマンド プロンプトを開き (スタート メニューで「cmd」を検索)、次のコマンドを入力して Enter キーを押します: wmicdisk

クラスタリング アルゴリズムのクラスタリング効果評価問題には、特定のコード例が必要です クラスタリングは、データをクラスタリングすることによって、類似したサンプルを 1 つのカテゴリにグループ化する教師なし学習手法です。クラスタリングアルゴリズムでは、クラスタリングの効果をどのように評価するかが重要な問題となります。この記事では、一般的に使用されるいくつかのクラスタリング効果評価指標を紹介し、対応するコード例を示します。 1. クラスタリング効果評価指標 シルエット係数 シルエット係数は、サンプルの近さや他のクラスタとの分離度を計算することでクラスタリング効果を評価します。

MySQL における .ibd ファイルの役割とそれに関連する注意事項の詳細な説明 MySQL は人気のあるリレーショナル データベース管理システムであり、データベース内のデータは別のファイルに保存されます。このうち、.ibd ファイルは InnoDB ストレージ エンジンのデータ ファイルであり、データとインデックスをテーブルに保存するために使用されます。この記事では、MySQL における .ibd ファイルの役割を詳細に分析し、読者の理解を深めるために関連するコード例を示します。 1. .ibd ファイルの役割: データの保存: .ibd ファイルは InnoDB ストレージです

PHP の glob() 関数は、ファイルまたはディレクトリを検索するために使用され、強力なファイル操作関数です。指定されたパターン一致に基づいてファイルまたはディレクトリのパスを返すことができます。 glob() 関数の構文は次のとおりです。 glob(pattern, flags) ここで、 pattern は照合するパターン文字列を表し、*.txt (.txt で終わるファイルの照合) などのワイルドカード式にすることができます。特定のファイルパス。 flags は、関数を制御するために使用されるオプションのパラメータです。

強力なパフォーマンスと多彩な機能で知られる iPhone は、複雑な電子機器によく見られる、時折起こる問題や技術的な困難を免れません。 iPhone の問題が発生するとイライラすることもありますが、通常は警報を発する必要はありません。この包括的なガイドでは、iPhone の使用に関連して最も一般的に遭遇する課題のいくつかをわかりやすく説明することを目的としています。当社の段階的なアプローチは、これらの一般的な問題の解決に役立つように設計されており、機器を最高の動作状態に戻すための実用的な解決策とトラブルシューティングのヒントを提供します。不具合やより複雑な問題に直面している場合でも、この記事はそれらを効果的に解決するのに役立ちます。一般的なトラブルシューティングのヒント 具体的なトラブルシューティング手順を詳しく説明する前に、役立つ情報をいくつか紹介します。
