在这回的学习中,我们能体会到泛型算法的威力,这是通过模板(抽象)实现的
泛型算法是一种用对其所用的数据结构进行尽可能少的假设的方法表达的算法,模板使得泛型(genericity)更为容易
下面以链表为例进行说明,看看链表如何使用这个算法
假设我们的链表节点类型如下:
泛型算法是一种用对其所用的数据结构进行尽可能少的假设的方法表达的算法,模板使得泛型(genericity)更为容易
首先来看一个简单的例子,假设我们想找整形数组中第一个等于某个给定值的元素,代码很简单,如下:
const int* find(const int* array, int n, int x)
{
const int* p = array;
for(int i=0; i<n; i++)
{
if(*p == x)
return p;
p++;
}
return 0;
}
我们想让这个算法变得尽量的通用,首先肯定要让其去除对特定类型int的依赖,我们采用模板对这个算法进行元素类型的泛化,变化后的代码如下:
template <class T>
T* find(T* array, int n, const T& x)
{
T* p = array;
for(int i=0; i<n; i++)
{
if(*p == x)
return p;
p++;
}
return 0;
}
注:这里不需要用const,因为如果我们用一个指向const类型的指针来调用find的话,T会把const作为它类型的一部分。又因为我们应用这个算法的类型T有可能是类类型,也许这个类类型根本不允许复制一个对象,也许这个类型复制的代价会很大,基于这些考虑我们算法函数的第三个参数改为使用const引用
我们想让这个算法变得更通用,那么我们应该去除find算法中一定要给出总共元素的个数n的限制,试想如果我们要对链表来使用这个算法,那我们必须得先给出链表的长度,这是不是太麻烦了?
为了通用性,我们又修改这个算法,代码如下:
template <class T>
T* find(T* start, T* beyond, const T& x)
{
T* p = array;
while(p != beyond && *p !=x)
++p;
return p;
}
template <class T>
T* find(S s, S b, const T& x)
{
while (s!=b && *s!=x)
++s;
return s;
}
下面以链表为例进行说明,看看链表如何使用这个算法
假设我们的链表节点类型如下:
struct Node
{
int value;
Node* next;
}
很容易我们可以得到查找链表特定元素的算法,如下:
Node* ListFind(Node* s, Node* b=0, const int& x)
{
while( s!=b && *s!=x )
s = s->next;
return s;
}
显然我们的ListFind函数与我们最终的find算法函数非常类似,不要奇怪,因为他们解决的是同样一个问题,只是针对不同的数据结构,所以算法形式的一致是不足为奇的(这就是抽象的魅力所在!)
要对链表应用通用的find函数,我们还需要定义一个辅助类,这个辅助类要建立链表与这个算法之间的联系,并且这个辅助类要能够支持!= * ++,为了能更好的理解,我们可以先设想这个辅助类应该是实现STL容器中的迭代器的功能
我们将这个辅助类命名为Nodep,具体定义如下:
class Nodep
{
public:
Nodep(Node* p = 0):pt(p) {};
int& operator*() { return pt->value; }
void operator++() { pt = pt->next; }
friend bool operator!=(const Nodep&, const Nodep&);
operator Node*() { return pt; } //类型转换符
private:
Node* pt;
};
bool operator!=(const Nodep& p, const Nodep& q)
{
return p.pt != q.pt;
}
#include <iostream>
using namespace std;
template <class S, class T>
S find(S s, S b, const T& x) // 泛型算法
{
while (s!=b && *s!=x)
++s;
return s;
}
struct Node //链表节点定义
{
int value;
Node* next;
};
typedef Node* List;
class Nodep //辅助类,辅助链表使用通用泛型算法
{
public:
Nodep(Node* p = 0):pt(p) {};
int& operator*() { return pt->value; }
void operator++() { pt = pt->next; }
friend bool operator!=(const Nodep&, const Nodep&);
operator Node*() { return pt; } //类型转换符
private:
Node* pt;
};
bool operator!=(const Nodep& p, const Nodep& q)
{
return p.pt != q.pt;
}
List CreateList(int* p) //建立链表
{
List head = new Node();
Node* tail = head;
head->next = NULL;
while(*p)
{
Node* t = new Node();
t->value = *p;
t->next = NULL;
tail->next = t;
tail = t;
p++;
}
return head;
}
void main()
{
int a[] = {1,2,3,4,0};
List l= CreateList(a);
Nodep start(l);
Nodep end(0);
Nodep n = find(start,end,4);
Node* p = n;
cout << p->value;
}
总结:
记住,利用模板写出的函数,不仅可以适用于不同的参数类型,还可以使用于不同的数据结构
也许有人会疑问,既然为了让特定的数据结构使用通用算法,我们需要定义一个辅助类,为什么直接使用我们的普通算法(比如ListFind)呢?要解开这个疑惑,请假设有m个数据结构和n个算法,如果我们不使用泛型,则数据结构和算法之间的映射有m*n个,也就是说针对每个数据结构的每个算法我们都需要去定义,而如果使用泛型,则我们只需要定义m个辅助类和n个算法,O(m*n)与O(m+n),那个更好呢?