数据结构学习(C++)续——查找(搜索)【1】

类别:VC语言 点击:0 评论:0 推荐:

相信每个人都曾感受过找东西的痛苦,大多数人也感受过计算机参与资料管理后所带来的便捷,而学过编程的也曾为了某个问题(比如实现“如果不存在则加入”这样的算法描述——排列组合算法的初级阶段)而实现过查找。在SGI-STL的stl_algo.h里面有这样一段代码:

template <class _InputIter, class _Tp>

inline _InputIter find(_InputIter __first, _InputIter __last,

                       const _Tp& __val,

                       input_iterator_tag)

{

  while (__first != __last && !(*__first == __val))

    ++__first;

  return __first;

}

这个应该能代表我们曾经写过的所有顺序查找代码,关于为什么写成了这个样子,《C++沉思录》第17章有详细的说明。或许VC6的STL代码更容易理解些:

template<class _II, class _Ty> inline

       _II find(_II _F, _II _L, const _Ty& _V)

       {for (; _F != _L; ++_F)

              if (*_F == _V)

                     break;

       return (_F); }

要是这样就完事了该多好,但是,上面的代码只是对于无规则数据的通用查找方法,如果我们想更快,就必须给数据排列添加规则,这样我们就能利用规则来提高查找效率。O(n)和O(logn)毕竟是不能同日而语的,更不要提O(1)了。

正是人们对于速度的追求才导致了这一部分由上面的3行代码变成了庞大无比的一大系算法(其实这也是不得以的事情,5分钟的等待就足以令一个人发狂了)。对于速度的明显提高是建立在n很大的基础上的,这样就不得不涉及到外存,你可以看到,下面的很多演化是因为外存的介入,如果不注意这点,常常会对我们所做的努力感到疑惑。

回忆一下查英汉字典的过程,将有助于我们发现一些查找策略——理论来自于实践,好像不会再有人对这句话有异议了吧?

首先,字典的正文是字典有序的(这个提法有些古怪,和字典的排序方法一样的称作字典有序,然后我又说字典是字典有序的——反正查字典的人都明白字典是怎么排序的,我就不解释了),因此,有些英汉字典没有目录——只要前言和附录不是太多,一般都不会有目录——实际上我们查字典也通常不用目录。具体怎么查呢?

大体上翻一下——a打头就少翻点,p打头就多翻点——如果当前页的末单词(一般来说在页的右上角的单词)排在所查单词的前面,就往后翻——翻多少页自己估量——如果当前页的首单词排在所查单词的后面,就往前翻——翻多少页自己估量。重复这个过程,直到所查单词位于当前页的首末单词之间,然后在当前页查找——这个过程也可以前后比较,但通常都是从前向后搜索一遍。

回忆这个过程,我们能得到很多启示,首先就是“折半查找”思想。

折半查找

当前值大于所查值就往前搜索,小于就往后搜索,这是折半查找的基本思想。想当年曾经为这个算法花费了不少脑细胞,看来还是我太笨的缘故:

int binfind(int a[], int N, int x)

{

       int low = 0, high = N - 1, mid;

       while (low <= high)

       {

              mid = (low + high) / 2;

              if (a[mid] == x) return mid;

              if (a[mid] < x) low = mid + 1;

              else high = mid - 1;

       }

       return N;

}

没有写成模板形式的,是因为支持折半查找的限制比较多,上面的虽然适用范围很小,但是很容易理解。注意到“查字典”的向前(向后)翻的页数是自己估量的,一个查字典熟练的人和一个生手的差别就常常体现在这里。在计算机里,这个“估量”就是插值。这很容易理解——一本书1000页,怎样快速翻到第300页?很显然,翻到书厚度3/10的地方,当然,这不会很准确,但离第300页已经很近了。这个例子的启示是,在均匀序列里,插值应该是有效的。

int insfind(int a[], int N, int x)

{

       int low = 0, high = N - 1, mid;

       if (x > a[high]) return N;//防止越界

       while (low < high)

       {

              mid = low + (x - a[low])*(high - low)/(a[high] - a[low]);

              if (a[mid] == x) return mid;

              if (a[mid] < x) low = mid + 1;

              else high = mid - 1;

       }

       return N;

}

测试程序

#include <cstdio>

#include <cstdlib>

#include <ctime>

#include "find.h"

#define N 30000

int a[N];

class timer//单位ms

{

public:

       void start() { start_t = clock(); }

       clock_t time() { return (clock() - start_t); }

private:

       clock_t start_t;

};

int main()

{

       timer TIMER; int i, t;

       for (i = 0; i < N; i++) a[i] = 2*i+1;

       TIMER.start();

//     for (i = 0; i < N; i++) seqfind(a, N, rand()%N);

       t = TIMER.time();

       printf("N=%d %dseqfind TimeSpared: %dms\n", N, N, t);

//     printf("%d", a[seqfind(a, N, 2763)]);

       TIMER.start();

       for (i = 0; i < N; i++) binfind(a, N, rand()%N);

       t = TIMER.time();

       printf("N=%d %dbinfind TimeSpared: %dms\n", N, N, t);

//     printf("%d", a[binfind(a, N, 2763)]);

       TIMER.start();

       for (i = 0; i < N; i++) insfind(a, N, rand()%N);

       t = TIMER.time();

       printf("N=%d %dinsfind TimeSpared: %dms\n", N, N, t);

//     printf("%d", a[insfind(a, N, 2763)]);

       return 0;

}

测试结果

N=30000 30000seqfind TimeSpared: 12107ms

N=30000 30000binfind TimeSpared: 40ms

N=30000 30000insfind TimeSpared: 10ms

注意,这个测试里面包含了查找失败的情况,否则简单的顺序查找的性能不会那么差。

本文地址:http://com.8s8s.com/it/it1272.htm