主要内容
基本概念
在介绍查找算法前我们先重温几个重要概念:
1)数据:客观事物的符号表示,是所有能输入到计算机中,并能被计算机程序处理的符号的总称。如数学计算中用到的整数和实数,文本编辑中用到的字符串,多媒体程序处理的图形、图像、声音及动画等通过特殊编码定义后的数据。
2)数据元素:数据的基本单位,在计算机中通常作为一个整体进行考虑和处理。数据元素也称记录,用于描述一个完整的对象,该对象可以是一名学生的信息(记录),某一次棋局,图中的某一个顶点等。
3)数据项:组成数据元素的、有独立含义的、不可分割的最小单位。如学生记录中的学号、姓名、性别等都属于数据项。
4)数据结构:相互之间存在一种或多种特定关系的数据元素的集合。数据结构包括逻辑结构(逻辑表示)和存储结构(物理实现)。
5)查找表:由同一类型的数据元素(或记录)构成的集合,可以由线性表、树结构等多种数据结构来实现。
6)关键字:数据元素(或记录)中的某个数据项的值,相当于“数据库”知识中一行记录的主码(PRIMARY KEY)。
typedef struct /*定义数据元素结构体*/
{
KeyType key; /*关键字*/
OtherType other; /*其他数据项*/
} Element;
typedef struct /*以顺序表结构定义查询表*/
{
/*顺序表借助数组存储数据,因此需要空间基地址base(数组头)和当前长度length(数组长度)*/
Element *base;
int length;
} SSTable; /*SSTable表示顺序查询表*/
7)查找:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。同样可以类比数据库的数据查询。针对不同的数据结构,查找算法也会有所不同。
8)平均查找长度:衡量查找算法性能的一个标准。
在查找表的数据结构中,线性表是最简单的一种。
折半查找(Binary Search)
顾名思义,折半查找每一次查找比较都能使查找范围缩小一半,它是一种效率较高的查找方法。
但是折半查找要求线性表必须采用顺序存储结构,而且表中元素必须按关键字有序排列(有点类似数据库的索引)。
为了标记出查找范围,我们要设置三个变量low、high、middle分别表示查找范围的下界、上界和中间位置(每次查找后更新赋值)。
提前路过的圈毛君:“总是一不留心就把“查找”写成了“查询”(数据库留下的习惯),虽然这两个词意思一样,但无奈博主是强迫症_(:з」∠)_喜欢维持前后文的用词一致性。”
下面给出非递归算法的代码:
int Bin_Search(SSTable t, KeyType key)
{
int low = 1, high = t.length;
while(low <= high)
{
int mid = (low + high) / 2;
if(t.base[mid] == key) return mid;
else if(t.base[mid] < key) low = mid+1;
else high = mid-1;
}
return 0;
}
在每次循环中,low、high、mid中的其中两个值都要作更新,而且这个不断缩小查找范围的过程也很像一个不断深入、细化的树或图的遍历过程,因此我们很容易想到一个词——递归。
下面给出折半查找的递归算法的实现:
int Bin_Search(SSTable t, KeyType key, int low, int high)
{
if(low <= high)
{
int mid = (low + high) / 2;
if(t.base[mid] == key) return mid;
else if(t.base[mid] < key) low = mid + 1;
else high = mid-1;
}
Bin_Search(t, key, low, high);
}
折半查找过程可用二叉树来描述,由此得到的二叉树称为判定树。借助判定树可以很快求出折半查找的平均查找长度。
优点:
查找效率比顺序查找高,时间复杂度为O(log2n)。
缺点:
只适用于顺序存储结构的有序表,所以查找前必须先对表进行排序,而排序本身就是一种费时的运算。而且,对于有序表并不方便进行插入、删除操作,因此折半查找不适用于数据元素经常发生变动的查询表。