基础算法介绍 —— 二分查找算法
不知不觉在目前的公司待满3年了,打算回家找份工作。面试中被问到关于算法的题目:有哪些常见的查找算法?下来就把我所掌握的查找算法分享给大家,本文主要介绍二分查找算法。
算法定义(摘自百度):二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
上述定义可以看到这种二分查找算法是基于一个有序序列的算法,因此仅适用于已经排好顺序的情况下使用,否则需要先对序列进行排序。
下面用Java代码来描述两种形式的二分查找算法:
一、递归二分查找算法
1 package algorithm; 2 3 /** 4 * 题目: 现有一个int型数组,其元素顺序排列。现给出一个数字,返回其在数组中的下标,如不存在返回-1. 5 * 6 */ 7 public class BinarySearch { 8 9 // 用来记录查找次数 10 private static int count = 0; 11 12 /** 13 * 14 * @param number 15 * 待查找数字 16 * @param array 17 * 待查找数组 18 * @return 数组下标或-1 19 */ 20 public static int getIndex(int number, int[] array) { 21 count = 0; 22 if (array == null || array.length == 0) { // 如果数组对象为空或者数组元素个数为0,直接返回-1 23 count++; 24 return -1; 25 } 26 return binarySearch(0, array.length - 1, array, number); // 调用递归方法 27 } 28 29 /** 30 * 31 * @param startIndex 32 * 起始下标 33 * @param endIndex 34 * 结束下标 35 * @param array 36 * 数组 37 * @param number 38 * 待查找数字 39 * @return 数组下标或-1 40 */ 41 private static int binarySearch(int startIndex, int endIndex, int[] array, int number) { 42 count++; 43 // startIndex大于endIndex,说明已查找完成但并未查找到number 44 if (startIndex > endIndex) { 45 return -1; 46 } 47 // 获取中间元素下标 48 int index = (startIndex + endIndex) / 2; 49 // 获取中间元素数值 50 int _number = array[index]; 51 // 比较中间元素与待查找数字 52 if (_number < number) { 53 // 如果中间元素数值小与待查找数字,则将查找范围缩小至 起始下标+1~中间元素下标 54 return binarySearch(index + 1, endIndex, array, number); 55 } else if (_number > number) { 56 // 如果中间元素数值大与待查找数字,则将查找范围缩小至 中间元素下标~中间元素下标-1 57 return binarySearch(startIndex, index - 1, array, number); 58 } 59 return index; 60 } 61 62 public static int getCount() { 63 return count; 64 } 65 66 }
二、非递归二分查找算法:
1 package algorithm; 2 3 public class BinarySearchNonRecursive { 4 5 // 记录循环次数 6 private static int count = 0; 7 8 public static int getIndex(int number, int[] array) { 9 10 // 判断数组是否为空,判断数组长度 11 if (array == null || array.length == 0) { 12 return -1; 13 } 14 15 // 初始化起始下标、结束下标、返回值 16 int startIndex = 0; 17 int endIndex = array.length - 1; 18 19 while (startIndex <= endIndex) { 20 count++; 21 int middleIndex = (startIndex + endIndex) / 2; 22 int _number = array[middleIndex]; 23 24 if (_number < number) { 25 // _number小于number,下次循环查找的起始下标为middleIndex + 1; 26 startIndex = middleIndex + 1; 27 } else if (_number > number) { 28 // _number大于number,下次循环查找的结束下标为middleIndex - 1; 29 endIndex = middleIndex - 1; 30 } else { 31 // _number等于number,查找到数字,返回结果 32 return middleIndex; 33 } 34 } 35 36 // 当startIndex > endIndex时,说明未查找到数字。结束循环,返回-1 37 return -1; 38 39 } 40 41 public static int getCount() { 42 return count; 43 } 44 45 }
下面是测试代码:
1 package algorithm; 2 3 import org.junit.Before; 4 import org.junit.Test; 5 6 public class BinarySearchTest { 7 8 private int[] array; 9 private int[] testArray; 10 11 @Before 12 public void setUp() throws Exception { 13 array = new int[] { 1, 2, 3, 9, 15, 26, 37, 48, 69, 110, 116, 117, 244, 374, 529 }; 14 testArray = new int[] { 1, 5, 3, 9, 37, 48, 22, 69, 244, 529, -888 }; 15 } 16 17 @Test 18 public void test() { 19 20 System.out.println("====递归二分查找算法===="); 21 for (int i = 0; i < testArray.length; i++) { 22 System.out.println("测试数值为 : " + testArray[i]); 23 24 int result = BinarySearch.getIndex(testArray[i], array); 25 26 System.out.println("查找结果为 : " + result); 27 System.out.println("查询次数 : " + BinarySearch.getCount() + "\n"); 28 29 } 30 System.out.println("\n====非递归二分查找算法===="); 31 for (int i = 0; i < testArray.length; i++) { 32 System.out.println("测试数值为 : " + testArray[i]); 33 34 int result = BinarySearchNonRecursive.getIndex(testArray[i], array); 35 36 System.out.println("查找结果为 : " + result); 37 System.out.println("循环次数 : " + BinarySearchNonRecursive.getCount() + "\n"); 38 39 } 40 41 } 42 43 }
输出测试结果:
====递归二分查找算法==== 测试数值为 : 1 查找结果为 : 0 查询次数 : 4 测试数值为 : 5 查找结果为 : -1 查询次数 : 5 测试数值为 : 3 查找结果为 : 2 查询次数 : 4 测试数值为 : 9 查找结果为 : 3 查询次数 : 2 测试数值为 : 37 查找结果为 : 6 查询次数 : 4 测试数值为 : 48 查找结果为 : 7 查询次数 : 1 测试数值为 : 22 查找结果为 : -1 查询次数 : 5 测试数值为 : 69 查找结果为 : 8 查询次数 : 4 测试数值为 : 244 查找结果为 : 12 查询次数 : 4 测试数值为 : 529 查找结果为 : 14 查询次数 : 4 测试数值为 : -888 查找结果为 : -1 查询次数 : 5 ====非递归二分查找算法==== 测试数值为 : 1 查找结果为 : 0 循环次数 : 4 测试数值为 : 5 查找结果为 : -1 循环次数 : 4 测试数值为 : 3 查找结果为 : 2 循环次数 : 4 测试数值为 : 9 查找结果为 : 3 循环次数 : 2 测试数值为 : 37 查找结果为 : 6 循环次数 : 4 测试数值为 : 48 查找结果为 : 7 循环次数 : 1 测试数值为 : 22 查找结果为 : -1 循环次数 : 4 测试数值为 : 69 查找结果为 : 8 循环次数 : 4 测试数值为 : 244 查找结果为 : 12 循环次数 : 4 测试数值为 : 529 查找结果为 : 14 循环次数 : 4 测试数值为 : -888 查找结果为 : -1 循环次数 : 4
从console内容可以看出,两种不同形式二分查找算法的结果是相同的。
注:一些情况下同样的测试数据,递归二分查找和非递归二分查找的执行次数不一致,这是因为在非递归二分查找算法中,当startIndex>endIndex时,没有进入while语句中,count没有自增;而在递归二分查找算法中,startIndex>endIndex作为一个结束条件,是在递归函数内部判断的,因此记录调用递归函数次数的count自增。这里笔者没有对执行count++的位置进行特殊处理,是为了更好的体现出两种形式的算法的执行步骤。
总结:两种形式的二分查找算法本质思想都是一样的,即每次将查找数据与中间元素对比大小,未匹配则缩小二分之一的查找范围,直到最终查找到元素或返回未查找到结果。
最后,希望本文能帮助到有需要的朋友,同时也欢迎大家批评指正文中的不足之处。