按照STL算法适配的主题,我首先适配了归并排序算法,首先上代码,包含头文件两个iterator.h algorithm_sort.h 以及主程序main.cpp
代码清单
iterator.h
#ifndef _ITERATOR_
#define _ITERATOR_
#include <cstddef>
using namespace std;
namespace XP
{
// iterator template
template <class Category, class T, class Distance = ptrdiff_t,
class Pointer = T*, class Reference = T&>
struct iterator
{
typedef Category iterator_categroy;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
};
// iterator_traits template
template <class I>
struct iterator_traits
{
typedef typename I::iterator_categroy iterator_categroy;
typedef typename I::value_type value_type;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
// partial specializations of value_type for original pointer
template <class T>
struct iterator_traits<T*>
{
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer_type;
typedef T& reference_type;
typedef random_access_iterator_tag iterator_categroy;
};
// partial specializations of value_type for original const pointer
template <class T>
struct iterator_traits<const T*>
{
typedef T value_type; // not const T
typedef ptrdiff_t difference_type;
typedef const T* pointer_type;
typedef const T& reference_type;
typedef random_access_iterator_tag iterator_category;
};
}
#endif
algorithm_sort.h
#ifndef _ALOGRITHM_SORT_
#define _ALOGRITHM_SORT_
#include "iterator.h"
#include <algorithm>
#include <vector>
template <class Iterator,class Predicate>
inline void merge_sort(Iterator _First, Iterator _Last,
Predicate _Predicate)
{
if (_First == _Last || _Last - _First == 1){
return;
}
else{
typedef iterator_traits<Iterator>::difference_type difference_type;
difference_type diff = (_Last - _First) / 2;
merge_sort(_First, _First + diff, _Predicate);
merge_sort(_First + diff, _Last, _Predicate);
_merge(_First, _First + diff, _First + diff, _Last, _Predicate);
}
}
template <class Iterator, class Predicate>
inline void _merge(Iterator _Tr1First, Iterator _Tr1last,
Iterator _Tr2First, Iterator _Tr2last,
Predicate _Predicate)
{
Iterator itTr1 = _Tr1First;
Iterator itTr2 = _Tr2First;
typedef iterator_traits<Iterator>::value_type value_type;
vector<value_type> result;
/// sort
while (itTr1 != _Tr1last && itTr2 != _Tr2last){
if (_Predicate(*itTr1, *itTr2)){
result.push_back(*itTr1);
itTr1++;
}
else{
result.push_back(*itTr2);
itTr2++;
}
}
if (itTr1 < _Tr1last)
result.insert(result.end(), itTr1, _Tr1last);
if (itTr2 < _Tr2last)
result.insert(result.end(), itTr2, _Tr2last);
/// assign
itTr1 = _Tr1First;
itTr2 = _Tr2First;
vector<value_type>::iterator it_res = result.begin();
while (it_res != result.end()){
if (itTr1 != _Tr1last){
*itTr1 = *it_res;
itTr1++;
}
else if(itTr2 != _Tr2last){
*itTr2 = *it_res;
itTr2++;
}
it_res++;
}
}
#endif
main.cpp
#include "stdafx.h"
#include <iostream>
#include "alogrithm_sort.h"
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int ia[5] = { 0, 4, 2, 3, 1};
merge_sort(&ia[0],ia + 5, less<>());
for (int i = 0; i < 5; i++)
{
cout << ia[i] << ' ';
cout << endl;
}
vector<int> iv(ia, ia + 5);
merge_sort(iv.begin(), iv.end(), less<>());
for (int i = 0; i < 5; i++)
{
cout << iv[i] << ' ';
cout << endl;
}
system("PAUSE");
}
算法分析
对于算法原理,我讲的肯定没有学者们讲的好,因此原理部分我就直接照抄书本的原话了:
合并排序是成功应用分治技术的完美例子。对于一个需要排序的数组A[0...n-1],合并排序将其一分为二:A[0..n/2-] 和 A[n/2...n-1](代码段1),并对每个子数组递归排序,然后把这两个排序好的子数组合并成一个有序数组(代码段2)。
对两个数组的合并可以通过以下算法来完成。初始状态下,两个指针数组下标分别指向两个待合并数组的第一个元素。然后比较这两个元素的大小,将较小的元素添加到一个新创建的数组中(代码段3)。接着,被复制的数组中的指针后移,指向该较小元素的后继元素。上述操作一直持续到两个数组中的一个被处理完为止。然后在未处理完的数组中,剩下的未处理的元素被复制到新数组的尾部(代码段4)。
算法的时间复杂度为O(nlogn).
适配到STL
在算法原理中,我为每个关键的步骤点在代码中标记了位置。算法的原理已经有了,但是离适配到STL还有一段距离。
为何要适配到STL?当然是因为STL有他的优越性所在,首先,STL是兼容所有STL的容器的,这意味着你的数据保存只要是STL容器,甚至数组,都可以运行该算法,不需要进行任何适配操作。其次,适配到STL允许自定义比较函数,通常情况下我们在使用STL的Sort函数时可能并不关注,但是实际上那是因为STL帮我们默认选择了less方式的比较函数,他是一个函数对象,也就是我们熟悉的从小到大的比较。适配到STL允许你自定义比较函数,对了,你升职可以为你的对象定义一个比较函数,这样在数学上不可比较的对象,你也可以对其进行排序。
这片代码中可能有令你感到疑惑的点,而且那部分肯定是来自于萃取器,这在我之前的文章 如何将算法适配到STL 中有过介绍,这里是第一次以实例的形式进行展示,我将一点点的介绍,在后面的文章中这部分将逐步省略。首先在merge_sort的函数声明:
template <class Iterator,class Predicate>
inline void merge_sort(Iterator _First, Iterator _Last,
Predicate _Predicate)
是不是感觉很熟悉?没错这就是sort函数(包含于algorithm)的声明改了下函数名和模板参数名而已,Iterator代表迭代器类型,_First代表起始位置,_Last代表结束位置,_Predicate 代表比较函数。传入迭代器是为了获取输入数据,比较函数用于比较两个对象的大小,它的实际形式应该是 bool Predicate(T arg1,Targ2),为true的情况下arg1为符合条件,否则为arg2。
if (_First == _Last || _Last - _First == 1){
return;
}
这部分相当于递归的终止判定,也就是这个分组只有一个元素的话,递归终止。
typedef iterator_traits<Iterator>::difference_type difference_type;
difference_type diff = (_Last - _First) / 2;
这段代码估计会让很多人感到困惑,实际上它只是一个萃取器的应用。目的是为了得到传入迭代器的元素究竟有多少个。也就是_Last - _First,但是结果的类型是什么呢?当然不能认为一定是int!实际上很多在STL表示大小的时候用的是size_type这个结构,那我们可以直接用size_type可以吗?当然也不行,原生指针迭代器相减的结果可不是一个结构体哦(虽然size_type可以被强制转换为int)。实际上迭代器本身已经为我们考虑到了。在iterator.h中有关于iterator的声明,是的,它没有用到,但是你也不用疑惑,如果你要自己设计一个符合STL标准的迭代器,那么你最好继承它,换言之,这个结构中的内部定义在STL同样适用。
template <class Category, class T, class Distance = ptrdiff_t,
class Pointer = T*, class Reference = T&>
struct iterator
{
typedef Category iterator_categroy;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
};
看到这个differce_type了吗,实际上在所有的迭代器内部都会有这样一个类型,可以看到其默认类型是ptrdiff_t(包含于cstddef中),而去头文件中看实现,它其实就是一个int
#ifdef _WIN64
typedef __int64 ptrdiff_t;
#else /* _WIN64 */
typedef _W64 int ptrdiff_t;
虽然又绕了回来,但是这是必要的。
iterator_traits<Iterator>::difference_type difference_type
通过萃取器,我们萃取出了迭代器中的表示两个迭代器间距的类型,并重新取了别名(虽然还是叫difference_type,但是有利于后面的书写,不用每次写这么长),然后用这个类型声明了一个变量初始化为迭代器的大小的二分之一。
difference_type diff = (_Last - _First) / 2;
接下来的就比较简单了,对生成出的两个子数组进行递归。
merge_sort(_First, _First + diff, _Predicate);
merge_sort(_First + diff, _Last, _Predicate);
对子数组的排序完成后,将这两个数组进行合并(利用计算出的偏移量,定位了迭代的前半部分和后半部分)。这里可能有疑问的是我并没有传入一个新的数组,像算法分析中写的那样来保存结果,但是换言之,STL原生的Sort也没有提供,排序后的结果直接作用于容器本身,在此特意解释一下。
最后的重点:合并子数组
合并数组的时候我们重新声明了两个迭代器,分别用来迭代两个子数组itTr1和itTr2
while (itTr1 != _Tr1last && itTr2 != _Tr2last){
if (_Predicate(*itTr1, *itTr2)){
result.push_back(*itTr1);
itTr1++;
}
else{
result.push_back(*itTr2);
itTr2++;
}
}
任意一个数组迭代完成就结束循环,比价函数在这里就派上用场了,前面提到过他的形式是 bool Predicate(T arg1,T arg2)。其参数是*itTr1和*itTr2,需要注意的是之所以可以对迭代器使用*运算符是因为它重载了这个运算符,重载的内容很简单,返回这个迭代器指向的实际内容,类型在迭代器中也声明过内部类型 value_type。
蓝色代码部分是对迭代完成后还没有被遍历到的部分直接加入到后面
if (itTr1 < _Tr1last)
result.insert(result.end(), itTr1, _Tr1last);
if (itTr2 < _Tr2last)
result.insert(result.end(), itTr2, _Tr2last);
这里没有直接对原来迭代器的部分进行更改,因为在后续的计算过程还有影响,所以用了一个临时的vector来进行暂存,也可以替换成其他容器,无伤大雅。
最后这部分对于算法来书没有什么意义,只是将暂存的数据按照排好的顺序写回到原来的数据中去而已。
itTr1 = _Tr1First;
itTr2 = _Tr2First;
vector<value_type>::iterator it_res = result.begin();
while (it_res != result.end()){
if (itTr1 != _Tr1last){
*itTr1 = *it_res;
itTr1++;
}
else if(itTr2 != _Tr2last){
*itTr2 = *it_res;
itTr2++;
}
it_res++;
}
最后来到了我们的测试环节,测试中为了测试我们的程序对容器的兼容性,我选取了原生数组和vector进行测试,主程序如下
int ia[5] = { 0, 4, 2, 3, 1};
merge_sort(&ia[0],ia + 5, less<>());
for (int i = 0; i < 5; i++)
{
cout << ia[i] << ' ';
cout << endl;
}
vector<int> iv(ia, ia + 5);
merge_sort(iv.begin(), iv.end(), less<>());
for (int i = 0; i < 5; i++)
{
cout << iv[i] << ' ';
cout << endl;
}
system("PAUSE");
less是比较器,表示按照升序进行排序,对0 4 2 3 1 进行排序,运行的结果为
可见设计可行无误。