1. 查找
在 Pig Latin:一个模块化开发的案例研究 小节的piglatin.c
中,函数FindFirstVowel
的功能,是在一个字符串中查找第一个元音字母。
函数实现为:
static int FindFirstVowel(string word) {
int i, length;
length = StringLength(word);
for (i = 0; i < length; i++) {
if (IsVowel(IthChar(word, i)))
return i;
}
return -1;
}
该实现采用简单直接的算法来解决查找问题,即:
从字符串的第一个字母开始,该函数依次检查每一个字母。
1.1 在整数数组中查找
可以利用以上方法在一个数组中查找某个数据项。
例如,函数FindIntegerInArray
在一个有效长度为n的整数数组中查找整数key:
static int FindIntegerInArray(int key, int array[], int n) {
int i;
for (i = 0; i < n; i++) {
if (array[i] == key)
return i;
}
return -1;
}
使用FindIntegerInArray
函数写一个程序findcoin.c
来显示美国的硬币名称和它对应的值。
其中,FindIntegerInArray
函数返回数组coinValues
中相应硬币值对应的下标。
如果硬币是美国铸造的五种标准硬币之一,返回的下标再用来在coinNames
数组中选择相应的元素。即,得到硬币的名称。
- 并联数组(parallel array)
用相同的下标位置来存储相关的数据称为并联数组(如下图所示)。
![](https://i-blog.csdnimg.cn/blog_migrate/c46bc317b8f9e0986c933b93cd70956c.png)
具体实现如下:
#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#include "strlib.h"
/* Function Prototype */
static int FindIntegerInArray(int key, int array[], int n);
static int coinValues[] = {1, 5, 10, 25, 30};
static string coinNames[] = {"penny", "nickel", "dime", "quarter", "half-dollar"};
/* Main Program */
main() {
int value, idx, nCoins;
printf("This program looks up names of U.S. coins.\n");
printf("Enter coin value: ");
scanf("%d", &value);
nCoins = sizeof coinValues / sizeof coinValues[0];
idx = FindIntegerInArray(value, coinValues, nCoins);
if (idx != -1) {
printf("That's called a %s.\n", coinNames[idx]);
} else {
printf("There is no such coin.\n");
}
}
/* Function */
static int FindIntegerInArray(int key, int array[], int n) {
int i;
for (i = 0; i < n; i++) {
if (key == array[i])
return i;
}
return -1;
}
1.2 关于查找的另一个更复杂的例子
介绍一个更复杂的查找的应用。
假设要在程序中表示一张表(如下所示),给出各城市之间的距离(用英里表示):
![](https://i-blog.csdnimg.cn/blog_migrate/a3b83ffb67c8c4792c01b53a6d7809da.png)
声明一个全局变量mileageTable
,并用上表数据进行静态初始化:
#define NCities 12
static int mileage Table[NCities][NCities] = {
{0, 1108, 708, 1430, 732, 791, 2191, 663, 854, 748, 2483, 2625},
{1108, 0, 994, 1998, 799, 1830, 3017, 1520, 222, 315, 3128, 3016},
{708, 994, 0, 1021, 279, 1091, 2048, 1397, 809, 785, 2173, 2052},
{1430, 1998, 1021, 0, 1283, 1034, 1031, 2107, 1794, 1739, 1255, 1341},
{732, 799, 279, 1283, 0, 1276, 2288, 1385, 649, 609, 2399, 2327},
{791, 1830, 1091, 1034, 1276, 0, 1541, 1190, 1610, 1511, 1911, 2369},
{2191, 3017, 2048, 1031, 2288, 1541, 0, 2716, 2794, 2703, 387, 1134},
{663, 1520, 1397, 2107, 1385, 1190, 2716, 0, 1334, 1230, 3093, 3303},
{854, 222, 809, 1794, 649, 1610, 2794, 1334, 0, 101, 2930, 2841},
{748, 315, 785, 1739, 609, 1511, 2703, 1230, 101, 0, 2902, 2816},
{2483, 3128, 2173, 1255, 2399, 1911, 387, 3093, 2930, 2902, 0, 810},
{2625, 3016, 2052, 1341, 2327, 2369, 1134, 3303, 2841, 2816, 810, 0},
};
对应的城市名称可以被存储在一个一维数组cityTable
中,该数组的声明和初始化过程如下:
static string cityTable[NCities] = {
"Atlanta",
"Boston",
"Chicago",
"Denver",
"Detroit",
"Houston",
"LosAngeles",
"Miami",
"NewYork",
"Philadelphia",
"San Francisco",
"Seattle",
};
如何写一个程序读入两个城市的名字,显示它们之间的距离,运行结果如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/3402357e68612dd77376ded5c5f29532.png)
步骤如下:
(1) 以字符串的形式读入两个城市的名字。
(2) 在数组cityTable
中找到该城市名字所对应的下标位置。
(3) 用下标值在数组mileageTable
中找到对应的值,这个值就是两城市之间的距离。
假设能够实现一个在字符串数组中查找的函数FindStringInArray
,那么,程序的其余部分可以写成:
main() {
int city1, city2;
printf("This program looks up intercity mileage.\n");
city1 = GetCity("Enter name of city #1: ");
city2 = GetCity("Enter name of city #2: ");
printf("Distance between %s ", cityTable[city1]);
printf("and %s: ", cityTable[city2]);
printf("%d miles.\n", mileageTable[city1][city2]);
}
static int GetCity(string prompt) {
int idx;
char tmp;
string cityName;
cityName = malloc(100);
while (true) {
printf("%s", prompt);
// Inputting blank space is supported, but \n isn't.
scanf("%[^\n]", &cityName[0]);
scanf("%c", &tmp);
idx = FindStringInArray(cityName, cityTable, NCities);
if (idx >= 0) break;
printf("Unkown city name -- try again.\n");
}
return idx;
}
1.3 线性查找
如果遵循实现FindIntegerInArray
的逻辑,只需要改变参数类型,在循环中使用StringEqual
比较字符串的值。相应的代码如下:
static int FindStringInArray(string key, string array[], int n) {
int i;
// printf("key: %s\n", key);
for (i = 0; i < n; i++) {
// printf("array %d: %s\n", i, array[i]);
if (StringEqual(key, array[i]))
return i;
}
return -1;
}
- 线性查找算法(linear search algorithm)
搜索将从数组的第一个元素开始按顺序查找,直到发现匹配的元素为止,否则将在达到数组的最后一个元素时终止。
函数FindStringInArray
(和前文的FindIntegerInArray
)中使用的算法均为线性查找。
现实中,是否会从表头开始,按顺序找下去呢?
很可能不会。因为表中所列的城市名是按照字母顺序排列的。
San Francisco一定接近表的尾部,而Boston一定接近表的头部。
这种差异使得能很快找到这个值,而不需要检查整张表的大部分城市名。
1.4 二分查找
为了利用数组cityTable
已经按照字母顺序排列这个条件,需要采用一个新的算法。
假定所要查找的是下图所示数组中的San Francisco:
![](https://i-blog.csdnimg.cn/blog_migrate/cb3dcc04005dafefdf013b9f4de1aba4.png)
如果不再采用从数组第一个元素开始的线性搜索方法,而从数组的接近中间的位置取一个值,中间元素的下标值可以通过计算下标范围的两个端点的均值得到,即:
0
+
11
2
\frac{0+11}{2}
20+11
当使用整数进行运算时,该表达式的值为5。
存储在cityArray[5]
中的城市名是Houston。由于San Francisco所在位置一定在Houston的后面,因此可以立即排除下标值从0~5的城市名。
下面的查找中,可以采用相同的方法:
![](https://i-blog.csdnimg.cn/blog_migrate/d1261d80ebfc20d6b6f65d829797cbfa.png)
由此,整个搜索过程只需要三步。
- 二分查找(binary search)
首先在已排序的数组中查找中间元素,然后根据这个元素的值确定下一步将在哪个部分进行搜索,这种查找方法称为二分查找。
为了实现这种算法,需要记录两个下标值,即,表示要进行搜索的范围的两个端点的下标值。
在函数中,这两个下标值分别存储于变量lh
和rh
中,表示左边界(小的下标值)和右边界(大的下标值)。
使用二分查找的函数FindStringInSortedArray
的代码如下:
static int FindStringInSortedArray(string key, string array[], int n) {
int lh = 0;
int rh = n - 1;
int mid, val;
while (true) {
mid = (lh + rh) / 2;
val = StringCompare(key, array[mid]);
if (val == 0) {
return mid;
} if (val < 0) {
rh = mid;
} else {
lh = mid;
}
}
return -1;
}
1.5 查找算法的相对效率
对于查找算法来说,一个能对算法性能进行很好评估的比较方便的方法,就是计算调用StringEqual
或StringCompare
的次数,每次调用都是比较键值和数组中的某些元素。
假设在一个长度为
N
N
N的数组中用线性查找算法进行搜索,在最坏的情况下,FindStringInArray
函数将调用
N
N
N次StringEqual
函数,即,对数组中的每一个元素都要调用一次。
在函数FindStringInSortedArray
实现中所用的二分查找算法,每经过一次调用,可能的元素数都减半,最终搜索区间将变为1。
达到这一点所需的步数等于将 N N N依次除以2并最终得到1所需要的次数,该过程可用如下公式表示:
![](https://i-blog.csdnimg.cn/blog_migrate/b94d6dc1b3e7d84c7f69850cb9fa3805.png)
将所有的2相乘可得以下方程:
N
=
2
k
N = 2^k
N=2k
取对数可得k值:
k
=
l
o
g
2
N
k = log_2N
k=log2N
因此,使用线性查找算法查找由 N N N个元素组成的数组需要 N N N次的比较,而使用二分查找算法只需要 l o g 2 N log_2N log2N次比较就可以了。
二分查找算法唯一的问题是,数组必须是严格地按照某个顺序排列的。
如果不是,则必须采用线性查找算法。或者自己先把该数组按要求进行排序。
参考
《C语言的科学和艺术》 —— 第12章 查找和排序