4.6 KiB
4.3 查找与插入算法
在实际应用中,查找数据和插入新数据都是很常见的操作,在本节中可以看到,高效的查找和插入操作都要求数据首先是有序的,这依赖于上一节的排序算法。可以对很多种数据结构实施排序、查找和插入操作,但到本节为止,我们都是以数组为基本结构进行讲解的,其他类型数据结构的排序、查找和插入操作将在各自章节进行说明。
4.3.1 顺序查找
顺序查找是最简单的查找方式,假设有一个长度为 N 的数组,只要从数组的第一个元素开始访问,到最后一个元素结束,中间如果遇到了匹配的值,就返回。
/*
* @file main.c
*/
#include <stdio.h>
#define DATA_SIZE 15
int main(void)
{
int i;
long find, findidx;
long data[DATA_SIZE];
// 读取数据到 data 的代码
...
// 查找数值为 25 的数据
find = 25;
findidx = -1;
for(i=0; i<DATA_SIZE; i++)
{
if(find==data[i])
{
findidx = (long)i;
printf("Idx=%d, Val=%d.\n", findidx, data[i]);
break;
}
}
return 0;
}
最好的情况下,待查找的数据中第一项即为要查找的数据;最坏的情况下,最后一项才是要查找的数据。顺序查找的时间复杂度为 O(N)。
4.3.2 二分查找法
有没有更好的查找法呢?曾经有个猜商品价格的节目,只要猜对价格这个商品就免费送给参与者,嘉宾每次给出一个价格,主持人会告知是高于真实价格还是低于真实价格。如果我们是参与者,如何更迅速的猜对价格呢?假设商品的价格在 0-100¥ 之间,我们可以先尝试下 50¥;如果价格低了,就试下 75¥;如果高于真实价格就猜 62.5¥...
每猜一次,价格范围都能够缩小一半,因此很快就能猜到正确的价格。可以使用类似的思路实现查找算法,称之为二分查找法。用二分查找法查找一个数组的方法如下:
假设数组中有 7 个元素,要查找其中为 'F' 的元素,先从中间的开始找,也就是序号为 3 的元素(6/2=3),也就是 'D'。但是 D<F,因此下次查找 3-6 之间的元素,而 (3+6)/2=4.5,那么我们可以取序号为 4 的元素(向下取整),也可以取序号为 5 的元素(向上取整)。由于向下取整比较容易,因此得到元素 'E',仍然小于 'F',接下来就要查找 4-6 之间的元素,将得到序号为 5 的元素 'F',这就是我们要找的那个。
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
元素 | A | B | C | D | E | F | G |
如果使用顺序查找算法,需要进行 6 次比较,而使用二分查找法,只需要 3 次,二分查找法的时间复杂度为 O(logN) (以 2 为底 N 的对数),显然,数组越长越能体现二分查找法的优势。
二分查找法之所以有效,是因为以上数组是有序数组,在进行查找时,已经知道了数组是从大到小排序还是从小到大排序的。如果数组是无序的,那么很难知道下一个要找的元素所在位置。因此,要进行快速查找,通常需要对数据进行排序,一般情况下排序只需要进行一次,而查找数据是反复进行的操作,因此为了查找而对数据进行排序是非常有意义的。
4.3.3 插入算法
在实际应用中,插入新数据是很常见的操作。在插入新数据以前,需要先确定要插入的位置,这往往是查找算法的工作。在无序数组中,只能使用顺序查找法,而有序数组中可以使用二分查找法。
在确定了要插入数据的位置后,需要将从插入位置开始到结尾的全部数据向后移动一个位置,将插入的位置空出,之后才能将新数据写入到空出的位置中。
可以看出,在数组中插入新的数据项,往往伴随着旧数据的移动,如果新数据需要插入到末尾,那就是最理想的情况——不需要移动任何数据。但如果新数据需要插入到队首,那就要将原有的 N 个数据都移动一次。下图展示了在数组中定位、移动和插入的具体过程。
练习
某中学级进行了期末考试,学生的姓名、性别、学号(具有唯一性)、年级、班级、成绩都保存在电脑中,实现一个程序:
1. 任意给出学号或学生姓名、输出对应的学生信息和数学成绩;
2. 允许插入新的学生信息和数学成绩。