C++泛型线性查找算法——find

《泛型编程和STL》笔记及思考。

线性查找可能是最为简单的一类查找算法了。他所作用的数据结构为一维线性的空间。这篇文章主要介绍使用 C++ 实现泛型算法 find的过程。

C 版本

首先介绍 C find 算法的实现,用以引入 C++ 版本。

  1. char *find1(char *first,char *last,int c) {
  2. while(first != last && *first != c)
  3. ++first;
  4. return first;
  5. }

该版本的算法循环检查每个元素,尾后指针(last)作为结束标识。

使用举例如下:

  1. char A[N];
  2. ...
  3. char *result = find1(A,A + N,c);
  4. if(result == A + N)
  5. printf("search failed\n");
  6. else printf("found it");

C 实现的 find 算法实现很简单,但使用范围很局限,只能应用于字符数组中对指定字符的查找。

C++ 版本

由于 C 版本 find 的使用范围局限性,在 C++ 中,我们可以使用泛型对策,利用 template 将函数的参数类型参数化。

首先,我们可以考虑设计一个类似 C 版本的 find 算法,以任意类型 T 的指针作为参数,代替原来的 char 指针。所以该方法声明如下:

  1. template<class T>
  2. T *find2(T *first,T *last,T value);

这样 find 方法就不在局限于一种类型可以使用了。

不过,STL 的泛型做法不像上述那般显而易见。STL 的线性查找算法是这样定义的:

  1. template<class Iterator,class T>
  2. Iterator find(Iterator first,Iterator last,const T& value) {
  3. while(first != last && *first != value)
  4. ++first;
  5. return first;
  6. }

为什么是 find 而不是看起来更浅显的 find2 呢?

原因简单的说,是因为这样的函数比 find2 更加的一般化。这种一般化的一个主要体现就是,它不再依赖数据结构的具体实现。比如,在链表上的线性查找。

链表上的查找

我们将把 find 用于单链表的线性查找,来证实 find 的一般性。虽然数组和链表对元素的组织方式不同,但是 基于线性序列的 find 仍可以适用于二者。

下面是一个链表结点的数据结构:

  1. struct int_node {
  2. int val;
  3. int_node* next;
  4. };

链表的遍历方法:

  1. int_node* p;
  2. for (p = list_head; p != NULL; p = p->next)
  3. //pass

单链表是一个线性序列,线性查找是一种常见的行为。既然是线性查找,而我们之前又写过线性查找算法,那么我们不应该编写重复的代码,而是考虑重用这个函数。

首先考虑我们实现过的第一个泛型查找算法 find2。find2 接受的参数为一个范围 [first,last),这个范围通过传递两个指针来界定。但是这里有个显而易见的问题,指向结点的指针如何在单链表上前进?假设我们有一个 int_node 指针 first 并传递给 find2 函数,然后我们希望通过 first++ 来实现指针的移动,注意问题便在这里,first ++ 操作无法到达下一个元素。因为 find2 算法应对的是线性序列使用数组实现的情况,而现在,序列元素的组织方式为链式结构,指针前进的方式不再是通过增加元素指针的值(first++)来实现。对于链表,操作应当是 first = first->next。

如何解决这个问题?

方案一 :使用 C++ 中的操作符重载

如果上述的 operator++ 行为不符合需要,那么就重新定义他的行为,

也即:

  1. ++ 操作: a = a + 1;
  2. ++ 操作: a = a->next;

使得 find2 可以正常工作。

然而,重新定义参数类型为 int_node* 类型的 operator++ 操作符是不可能的,C++ 允许我们定义操作符的表达式意义,单不允许变动既有的语法意义(我们不能随便的将一个指针的自加行为改变为其他的操作,就像不能将整数 + 运算符定义为 减、乘操作,这是不合适的)。

__方案二 __ : 增加一个包装类(外覆类 wrapper class)

我们通过编写一个简单的外覆类(wrapper class)使他看起来像一个 int_node* ,而他的 operator++ 有更为合适的定义。

外覆类的定义:

  1. template<class Node> //这里传递的参数是 类型 int_node
  2. struct node_wrap {
  3. Node* ptr;
  4. node_wrap(Node* p = 0) : ptr(p) { }
  5. Node& operator* const { return *ptr; }
  6. Node* operator-> const { return ptr; }
  7. node_wrap& oeprator++() { ptr = ptr->next; return *this; }
  8. node_wrap operator++(int) { node_wrap tmp = *this; ptr = ptr->next; return tmp; }
  9. bool operator== (const node_wrap& i) const { return ptr == i.ptr; }
  10. bool operator!= (const node_wrap& i) const { return ptr != i.ptr; }
  11. };

事实上我们还是重载了 operator++ 的行为,但是现在是在外覆类上的重载,而不是指针上的重载,对于外覆类来说,这种行为是合适的。

最后,由于 find 函数中的

  1. while(... *first != value)

语句中,*first != value 这个不等运算符的操作并没有定义,所以下面对他进行定义:

  1. bool operator!= (csonst node_wrap& i,int n) const { return i.value != n; }

那么现在,我们欲查找 int_node 中的某一个特定值,我们不需要在重复编写任何代码了,我们可以重复利用 find,将查找动作写成单一函数调用:

  1. find(node_wrap<int_node>(list_head),node_wrap<int_node>(),val);

其中第二个参数是利用 node_wrap 的缺省构造函数做出。他产生出一个内含 null 指针的 node_wrap。由于链表最后一个结点的 next 指针即为 null,所以我们会从 list_head 查找直至链表尾端。至此,我们重用了已有的算法来作用于链表上。

外覆类做了什么?

他将我们原始的结点指针包装了起来,同时定义或重载了一些操作,使得整个外覆类对外显示出一个指针常见的操作接口,以便其他的组件可以透明的将他作为一个指针来使用。而因不同的数据组织方法而形成指针操作差异将由外覆类负责包装和隐藏,并由他在类的内部将这种差异进行具体的实现,导致最终的结果是,他将原本有差异的事物,统一了起来。

带来的好处是什么?

在考虑他带来的好处之前,我们先想想没有他是怎样的情况。如果没有外覆类的包装,每当我们实现一个概念上相同的数据结构时,我们要将所有在这个数据结构上存在的算法实现一遍,即便已经存在相同概念的模型的算法,但是由于数据的组织方式不同存在的一些差异,我们很难重用这些算法。

外覆类带来的好处显而易见的是我们可以重用已经实现过的算法。而得以实现这一点的关键就在于外覆类消除了差异性,对外提供了统一的接口,而差异性越少,我们能重复利用的部分就越多。

作者:Skipper

出处:http://www.cnblogs.com/backwords/p/9321683.html

本博客中未标明转载的文章归作者 Skipper 和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

C++泛型线性查找算法——find的更多相关文章

  1. 线性查找算法(BFPRT)

    BFPRT算法的作者是5位真正的大牛(Blum . Floyd . Pratt . Rivest . Tarjan). BFPRT解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素, ...

  2. BFPRT(线性查找算法)

    BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分 析,BFPRT可以保证在最坏情况下仍为线性时间复杂度.该算法的思想与快速排序思想相似,当然,为使得算法 ...

  3. Python 查找算法_众里寻他千百度,蓦然回首那人却在灯火阑珊处(线性、二分,分块、插值查找算法)

    查找算法是用来检索序列数据(群体)中是否存在给定的数据(关键字),常用查找算法有: 线性查找: 线性查找也称为顺序查找,用于在无序数列中查找. 二分查找: 二分查找也称为折半查找,其算法用于有序数列. ...

  4. 查找算法(顺序查找、二分法查找、二叉树查找、hash查找)

    查找功能是数据处理的一个基本功能.数据查找并不复杂,但是如何实现数据又快又好地查找呢?前人在实践中积累的一些方法,值得我们好好学些一下.我们假定查找的数据唯一存在,数组中没有重复的数据存在. (1)顺 ...

  5. 数据结构与算法---查找算法(Search Algorithm)

    查找算法介绍 在java中,我们常用的查找有四种: 顺序(线性)查找 二分查找/折半查找 插值查找 斐波那契查找 1)线性查找算法 示例: 有一个数列: {1,8, 10, 89, 1000, 123 ...

  6. Java 查找算法

    1 查找算法介绍 在 java 中,我们常用的查找有四种: 1) 顺序(线性)查找 2) 二分查找/折半查找 3) 插值查找 4) 斐波那契查找   2 线性查找算法 有一个数列: {1,8, 10, ...

  7. 数组查找算法的C语言 实现-----线性查找和二分查找

    线性查找  Linear Search 用户输入学生学号的成绩 二分查找  Binary Search 要求数据表是已经排好序的 程序存在小的瑕疵

  8. 算法之二分查找PK线性查找

    列表查找(线性查找) 本质就是列表的index() 顺序查找 也叫线性查找,从列表第一个元素开始,顺序进行搜索,知道找到元素或搜索到列表最后一个元素为止. 以下是示例代码: def line_sear ...

  9. 算法:线性查找(重点isFlag标志)

    package com.atguigu; public class Main { public static void main(String[] args) { String[] arr=new S ...

随机推荐

  1. PMM安装-第一篇

    一 简介 今天来聊聊 PMM安装使用 二 安装 1 server端执行   curl -sSL https://get.daocloud.io/docker | sh    docker pull p ...

  2. CentOS 6.5下快速搭建ftp服务器[转]

    CentOS 6.5下快速搭建ftp服务器 1.用root 进入系统 2.使用命令 rpm -qa|grep vsftpd 查看系统是否安装了ftp,若安装了vsftp,使用这个命令会在屏幕上显示vs ...

  3. 【逆向工具】IDA使用4-控制台逆向分析 Reverse004.exe 获取密码

    工具 吾爱破解版本OD.IDA6.8 OD使用-动态分析 OD快捷方式 F2 下断点,也就是指定断点的地址F3加载一个可执行程序,进行调试分析F4程序执行到光标处 F5 缩小.还原当前窗口 F7 单步 ...

  4. Linux系统编程【转】

    转自:https://blog.csdn.net/majiakun1/article/details/8558308 一.Linux系统编程概论 1.1 系统编程基石 syscall: libc:标准 ...

  5. 一篇不错的CUDA入门

    鉴于自己的毕设需要使用GPU CUDA这项技术,想找一本入门的教材,选择了Jason Sanders等所著的书<CUDA By Example an Introduction to Genera ...

  6. Project Euler Problem 10

    Summation of primes Problem 10 The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of ...

  7. 018_nginx_proxy死循环问题

    今天线上遇到一个请求一次,触发多次的请求,而且直接把nginx机器压垮了.经排查,经过如下: 一. server{ server www.jyall.com; location /latestrele ...

  8. day1 diff命令递归比较目录下的文件

    一.diff实战 (1)递归比较文件夹下所有的文件及目录的不同 diff --brief -Nr dir1/ dir2/                               Reference ...

  9. centos6.5环境下zookeeper-3.4.6集群环境部署及单机部署详解

    centos6.5环境下Zookeeper-3.4.6集群环境部署 [系统]Centos 6.5 集群部署 [软件]准备好jdk环境,此次我们的环境是open_jdk1.8.0_101 zookeep ...

  10. Ext.util.Format.date与Ext.Date.format区别, 转换时间戳

    在Extjs中装时间戳使用如下两种都可以: Ext.util.Format.date(time,'U'); Ext.Date.format(time, 'U'); 为了找到它们的区别,查看源代码,以E ...