一、折半查找
1、折半查找:折半查找又称为二分查找。前提是线性表中的记录必须是关键码有序,线性表必须采用顺序存储。其基本思想就是在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的坐半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,则查找失败为止。注:这里是指以从小到大的顺序排列的。
2、实例分析
比如有一个有序表数组[1,3,5,7,9,11,13,15,17,19,21],它是按照从小到大的顺序来进行排列的,现在需要在该有序表中查找元素19,步骤如下:
3、使用递归实现折半查找
/**************************************************************/ /** 折半查找 **/ /**************************************************************/ #include <stdio.h> /** * 递归实现折半查找,查找成功返回所在数组下标,否则返回-1 * @param data:数据元素所在数组 * @param low:起始下标 * @param high:结束下标 * @param searchValue:待查找元素值 */ int BinarySearch_Recursion(int data[], int low, int high, const int searchValue) { int mid; if (low > high) { return -1; } mid = (low + high) / 2; if (data[mid] == searchValue) { return mid; } if (data[mid] > searchValue) { high = mid - 1; BinarySearch_Recursion(data, low, high, searchValue); } if (data[mid] < searchValue) { low = mid + 1; BinarySearch_Recursion(data, low, high, searchValue); } } int main() { int data[] = {1,3,5,7,9,11,13,15,17,19,21}; int index = BinarySearch_Recursion(data, 0, 10, 19); printf("%d\n", index); return 0; }4、使用迭代实现折半查找
/**************************************************************/ /** 折半查找 **/ /**************************************************************/ #include <stdio.h> /** * 迭代法实现折半查找,查找成功返回所在数组下标,否则返回-1 * @param data:数据元素所在数组 * @param length:数组长度 * @param searchValue:待查找元素值 */ int BinarySearch_Iterator(int data[], int length, int searchValue) { int low, high, mid; low = 0; high = length - 1; while (low <= high) { mid = (low + high) / 2; if (data[mid] == searchValue) { return mid; } if (data[mid] < searchValue) { low = mid + 1; } if (data[mid] > searchValue) { high = mid - 1; } } return -1; } int main() { int data[] = {1,3,5,7,9,11,13,15,17,19,21}; int index = BinarySearch_Iterator(data, 11, 19); printf("%d\n", index); return 0; }
5、时间复杂度
将该有序表的查找过程绘制成一棵二叉树,如下,从图上来看,如果查找的关键字是不中间记录11的话,折半查找等于是把静态有序查找表分成了两棵子树,即查找结果只需要找其中一半数据记录即可,等于是把工作量少了一半,如此反复。根据二叉树的性质"具有n个结点的完全二叉树的深度为⌊log2n⌋+1",虽然尽管折半查找判定二叉树并不是完全二叉树,但是由该性质可以推导得出,最坏情况是查找到关键字或查找失败的次数为⌊log2n⌋+1,而最好情况就是1了,所以折半查找的时间复杂度为O(log2n)。但是由于折半查找的前提条件是需要有序表顺序存储,对于比较稳定的查找表来说,排列一次就不需要再频繁判续,用折半查找来进行查找就比较适合,但是对于需要频繁执行插入或删除操作的数据集来说,需要经常维护有序的排序,所以对于这种情况下折半查找是不适合的。
二、插值查找
1、从折半查照中可以看出,折半查找的查找效率还是不错的。可是为什么要折半呢?为什么不是四分之一、八分之一呢?打个比方,在牛津词典里要查找“apple”这个单词,会首先翻开字典的中间部分,然后继续折半吗?肯定不会,对于查找单词“apple”,我们肯定是下意识的往字典的最前部分翻去,而查找单词“zero”则相反,我们会下意识的往字典的最后部分翻去。所以在折半查找法的基础上进行改造就出现了插值查找法,也叫做按比例查找。所以插值查找与折半查找唯一不同的是在于mid的计算方式上,它的计算方式为:
mid = low + (high - low) * (searchValue - data[low]) / (data[high] - data[low])
2、时间复杂度
插值查找的时间复杂度也是O(log2n),但是对于数据集合较长,且关键字分布比较均匀的数据集合来说,插值查找的算法性能比折半查找要好,其它的则不适用。
三、斐波那契查找
1、斐波那契查找也叫做黄金分割法查找,它也是根据折半查找算法来进行修改和改进的。
2、对于斐波那契数列:1、1、2、3、5、8、13、21、34、55、89……(也可以从0开始),前后两个数字的比值随着数列的增加,越来越接近黄金比值0.618。比如这里的89,把它想象成整个有序表的元素个数,而89是由前面的两个斐波那契数34和55相加之后的和,也就是说把元素个数为89的有序表分成由前55个数据元素组成的前半段和由后34个数据元素组成的后半段,那么前半段元素个数和整个有序表长度的比值就接近黄金比值0.618,假如要查找的元素在前半段,那么继续按照斐波那契数列来看,55 = 34 + 21,所以继续把前半段分成前34个数据元素的前半段和后21个元素的后半段,继续查找,如此反复,直到查找成功或失败,这样就把斐波那契数列应用到查找算法中了。如下图:
从图中可以看出,当有序表的元素个数不是斐波那契数列中的某个数字时,需要把有序表的元素个数长度补齐,让它成为斐波那契数列中的一个数值,当然把原有序表截断肯定是不可能的,不然还怎么查找。然后图中标识每次取斐波那契数列中的某个值时(F[k]),都会进行-1操作,这是因为有序表数组位序从0开始的,纯粹是为了迎合位序从0开始。所以用迭代实现斐波那契查找算法如下:
/********************************************************************/ /** 斐波那契查找 **/ /********************************************************************/ #include <stdio.h> #define FIB_MAXSIZE 100 /** * 生成斐波那契数列 * @param fib:指向存储斐波那契数列的数组的指针 * @param size:斐波那契数列长度 */ void ProduceFib(int *fib, int size) { int i; fib[0] = 1; fib[1] = 1; for (i = 2; i < size; i++) { fib[i] = fib[i - 1] + fib[i - 2]; } } /** * 斐波那契查找,查找成功返回位序,否则返回-1 * @param data:有序表数组 * @param length:有序表元素个数 * @param searchValue:待查找关键字 */ int FibonacciSearch(int *data, int length, int searchValue) { int low, high, mid, k, i, fib[FIB_MAXSIZE]; low = 0; high = length - 1; ProduceFib(fib, FIB_MAXSIZE); k = 0; // 找到有序表元素个数在斐波那契数列中最接近的最大数列值 while (high > fib[k] - 1) { k++; } // 补齐有序表 for (i = length; i <= fib[k] - 1; i++) { data[i] = data[high]; } while (low <= high) { mid = low + fib[k - 1] - 1; // 根据斐波那契数列进行黄金分割 if (data[mid] == searchValue) { if (mid <= length - 1) { return mid; } else { // 说明查找得到的数据元素是补全值 return length - 1; } } if (data[mid] > searchValue) { high = mid - 1; k = k - 1; } if (data[mid] < searchValue) { low = mid + 1; k = k - 2; } } return -1; } int main() { int data[] = {1,3,5,7,9,11,13,15,17,19,21}; int index = FibonacciSearch(data, 11, 19); printf("%d\n", index); return 0; }3、同样,斐波那契查找的时间复杂度还是O(log 2 n ),但是 与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小,但是还是得视具体情况而定。