二分查找(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
=============================================================================
总结
本文通过一个二分查找的题目,以及同学们在解答题目中暴露出来的问题,分析了一个安全可靠高效的二分查找,应该注意哪些问题。并简要分析了数据库内核实现中的二分查找实现,希望对大家在以后设计二分查找算法时,有所帮助。



Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas





Apa yang berlaku apabila anda mematikan Cari Saya pada iPhone? Cari iPhone Saya membantu anda mencari peranti yang hilang atau dicuri. Apabila didayakan, Cari iPhone Saya membolehkan anda menjejak lokasi peranti anda pada peta, memainkan bunyi dan membantu anda mencari peranti anda. Cari Saya juga termasuk Kunci Pengaktifan untuk menghalang sesiapa daripada menggunakan iPhone anda. Apabila anda mematikan Cari iPhone Saya, anda kehilangan semua ciri ini, yang mungkin menyukarkan untuk memulihkan peranti Apple yang hilang. Walaupun Cari iPhone Saya sangat berguna, anda harus melumpuhkannya apabila anda ingin menjual, menderma, menukar telefon anda atau menghantarnya untuk penggantian bateri atau sebarang perkhidmatan lain. Melakukan ini akan memastikan tiada sesiapa boleh mengakses maklumat tentang anda

Gunakan fungsi Array.IndexOf dalam C# untuk mencari indeks elemen dalam tatasusunan Dalam program C#, apabila kita perlu mencari indeks elemen dalam tatasusunan, kita boleh menggunakan fungsi Array.IndexOf. Fungsi Array.IndexOf mencari elemen yang ditentukan dalam julat tatasusunan yang ditentukan dan mengembalikan indeks kejadian pertamanya. Jika elemen tidak dijumpai, -1 dikembalikan. Berikut ialah kod sampel yang menunjukkan cara menggunakan fungsi Array.IndexOf untuk mencari elemen dalam tatasusunan.

Apl Cari Saya Apple membolehkan anda mencari iPhone anda atau peranti lain untuk mengelakkannya daripada hilang atau dilupakan. Walaupun Cari Saya ialah alat yang berguna untuk menjejak peranti, anda mungkin mahu melumpuhkannya jika anda bimbang tentang isu privasi, tidak mahu menghabiskan bateri anda atau atas sebab lain. Nasib baik, terdapat beberapa cara untuk mematikan Cari Saya pada iPhone, semuanya akan kami terangkan dalam artikel ini. Cara Mematikan Cari Saya pada iPhone [4 Kaedah] Anda boleh mematikan Cari Saya pada iPhone dalam empat cara. Jika anda menggunakan Kaedah 1 untuk mematikan Cari, anda boleh melakukan ini daripada peranti yang anda mahu nyahdayakannya. Untuk meneruskan kaedah 2, 3 dan 4, iPhone yang anda ingin matikan Finder harus dimatikan atau

Nombor siri cakera keras dan alamat MAC adalah pengecam penting dalam perkakasan komputer dan sangat berguna dalam mengurus dan menyelenggara sistem komputer. Artikel ini akan memperkenalkan cara mencari nombor siri cakera keras dan alamat MAC. 1. Cari nombor siri cakera keras Nombor siri cakera keras ialah pengecam unik yang digunakan oleh pengeluar cakera keras untuk mengenal pasti dan menjejaki cakera keras. Dalam sistem pengendalian yang berbeza, kaedah mencari nombor siri cakera keras adalah sedikit berbeza. Windows: Buka Prompt Perintah (cari "cmd" dalam menu Mula) dan masukkan arahan berikut dan tekan Enter: wmicdisk

Masalah penilaian kesan pengelompokan dalam algoritma pengelompokan memerlukan contoh kod khusus Pengelompokan ialah kaedah pembelajaran tanpa pengawasan yang mengelompokkan sampel yang serupa ke dalam satu kategori dengan mengelompokkan data. Dalam algoritma pengelompokan, cara menilai kesan pengelompokan adalah isu penting. Artikel ini akan memperkenalkan beberapa penunjuk penilaian kesan pengelompokan yang biasa digunakan dan memberikan contoh kod yang sepadan. 1. Indeks penilaian kesan pengelompokan Pekali Siluet Pekali siluet menilai kesan pengelompokan dengan mengira kehampiran sampel dan tahap pemisahan daripada kelompok lain.

Penjelasan terperinci tentang peranan fail .ibd dalam MySQL dan langkah berjaga-jaga yang berkaitan MySQL ialah sistem pengurusan pangkalan data hubungan yang popular, dan data dalam pangkalan data disimpan dalam fail yang berbeza. Antaranya, fail .ibd ialah fail data dalam enjin storan InnoDB, digunakan untuk menyimpan data dan indeks dalam jadual. Artikel ini akan menyediakan analisis terperinci tentang peranan fail .ibd dalam MySQL dan menyediakan contoh kod yang berkaitan untuk membantu pembaca memahami dengan lebih baik. 1. Peranan fail .ibd: menyimpan data: fail .ibd ialah storan InnoDB

Fungsi glob() dalam PHP digunakan untuk mencari fail atau direktori dan merupakan fungsi operasi fail yang berkuasa. Ia boleh mengembalikan laluan fail atau direktori berdasarkan padanan corak yang ditentukan. Sintaks fungsi glob() adalah seperti berikut: glob(corak, bendera) dengan corak mewakili rentetan corak yang akan dipadankan, yang boleh menjadi ungkapan kad bebas, seperti *.txt (fail yang sepadan berakhir dengan .txt), atau laluan Fail tertentu. flags ialah parameter pilihan yang digunakan untuk mengawal fungsi

Dikenali dengan prestasi yang berkuasa dan ciri serba boleh, iPhone tidak terlepas daripada cegukan atau kesukaran teknikal sekali-sekala, ciri biasa di kalangan peranti elektronik yang kompleks. Mengalami masalah iPhone boleh mengecewakan, tetapi biasanya penggera tidak diperlukan. Dalam panduan komprehensif ini, kami menyasarkan untuk menyahmistifikasi beberapa cabaran yang paling biasa dihadapi yang berkaitan dengan penggunaan iPhone. Pendekatan langkah demi langkah kami direka untuk membantu anda menyelesaikan isu lazim ini, menyediakan penyelesaian praktikal dan petua penyelesaian masalah untuk mengembalikan peralatan anda dalam keadaan berfungsi terbaik. Sama ada anda menghadapi masalah atau isu yang lebih kompleks, artikel ini boleh membantu anda menyelesaikannya dengan berkesan. Petua Penyelesaian Masalah Umum Sebelum menyelidiki langkah penyelesaian masalah khusus, berikut adalah beberapa yang berguna
