3.6 马尔科夫链算法的具体实现(C++环境下)

3.5 马尔科夫链算法的具体实现(Java环境下)

    由于我不是一个Java的程序员,只略懂一点Java的皮毛,所以不敢写,如果有大佬可以写的话,欢迎补充。我也会进行转载。

3.6 马尔科夫链算法的具体实现(C++环境下)

    因为C++语言几乎是C的一个超集,只要注意某些写法和规则,C++一样可以以C的形式去使用,实际上在上一篇中,我得所有输入输出就是用的C++中的基本输入输出流。对于C++来讲,更合适的用法应该是定义一些类,建立起程序中需要的各种对象,这样可以隐藏起很多实现细节。在这里,我们更进一步,使用C++的STL(标准模板库),它不仅给我们提供了很多内部机制,更重要的是,它已经被ISO列入到C++的语言定义中了,我们可以放心的使用。

    在这里,说到为什么有C++还要学习C,甚至我第一个写的实现就是C。个人来讲,可能是觉得C比较好玩吧,所有的操作都是你可以看到的。我喜欢将C比作一把双刃剑,喜欢那种锋利的感觉(是不是玄幻看多了)。还有可能是学校第一门计算机语言类的课就讲的C,到现在还能记住拿C+OpenCV1.0写的曼德勃罗集合...

    STL提供了许多的容器类,例如向量、链表、集合,还包括了许多检索、排序、插入和删除等等的基本算法。利用C++的模板特性,每个STL算法都能用到很多不同的容器类上。容器类的元素可以是用户定义类型或者是内部类型的。这里的容器都被描述为C++模板,可以对特定类型进行实例化。例如,STL里有一个vector容器类,由它可以导出各种具体类型,比如vector<int>;vector<string>等。所有的vector操作,包括排序的标准算法,都可以直接应用到这些数据类型。

    STL里,除了有vector容器(它与Javavector类差不多),还提供了一个deque容器类。deque(念为deck)是一种双端队列,它正好能符合我们对前缀操作的需要:可以用它存放NPREF个元素,丢掉最前面的元素并在后面添一个新的,这都是O(1)操作。实际上,STLdeque比我们需要的东西更一般,它允许在两端进行压入和弹出,而执行性能方面的保证是我们选择它的原因。

    STL还额外提供了一个map容器,其内部用来实现基于平衡的树。在map中可以储存(关键码——值)的数对。map的内部代码实现保证从任何关键码出发提取相关值的操作都是O(logn)的。虽然这种的效率没有散列表高,但是胜在不需要额外写很多代码(虽然我个人还是很喜欢散列表)

    当我们拥有了这些强力工具后,就可以进行代码的编写了。首先我们先进行一下声明:

typedef deque<string> Prefix;
map<Prefix, vector<string>> statetab;

    另外别忘了:

using namespace std;

    STL提供了deque的模板,记法deque<string>将它指定为以字符串为元素的deque。由于这个类型将在程序里多次出现,在这里用一个typedef声明,将它另外命名为Prefix。映射类型中将存储前缀和后缀,因为它在程序里只出现一次,我们就没有给它命名。这里声明了一个map类型的变量statetab,它是从前缀到后缀向量的映射。

    对于整个程序来说,add函数应该是其中比较不好理解的一部分。

void add(Prefix &prefix, const string &s)
{
	/*判断读入的字符串是否符合要求*/
	if (prefix.size() == NPREF)
	{
		statetab[prefix].push_back(s);
		prefix.pop_front();
	}
	prefix.push_back(s);
}

    这几个非常简单的语句确实做了不少事情。map容器类重载了下标运算符 ([ ]运算符),使它在这里成为一种查询运算。表达式 statetab[prefix]statetab里完成一次查询,prefix作为查询的关键码,返回对于所找到的项的一个引用。如果对应的向量不存在,这个操作将建立一个新向量。vectordeque类的push_back函数分别把一个新字符串加到向量或deque的最后;pop_frontdeque里弹出头一个元素。

    对于使用这种方法来说,实现简单(当然我还是喜欢C),但是速度相比C来说就会差得很远,虽然还不是最慢的。

    喜欢C可能还是因为对于基础的东西,喜欢自己一步步去写吧。

    Coding time

#include "stdafx.h"
#include <vector>
#include <string>
#include <iostream>
#include <deque>
#include <cstdio>
#include <map>

using namespace std;

/*deque这个容器可以储存NPREF个元素,并且可以丢掉第一个并在最后添加一个新的元素进去*/
typedef deque<string> Prefix;
/*map中可以储存(关键码——值)的数对*/
map<Prefix, vector<string>> statetab;

enum
{
	NPREF = 2,			/*前缀中词的数量*/
	NHASH = 99999,		/*哈希表(散列表)大小*/
	MAXGEN = 10000,		/*录入的最大单词数量*/
	MULTIPLIER = 31,
	BUFSIZE = 100
};
string NONWORD = "\n";
int go_on = 1;

void build(Prefix &prefix, istream &in);
void add(Prefix &prefix, const string &s);
void generate(int nwords);

int main()
{
	int nwords = MAXGEN;
	Prefix prefix;
	for (int i = 0; i < NPREF; i++)
	{
		add(prefix, NONWORD);
	}
	build(prefix, cin);
	add(prefix, NONWORD);
	generate(nwords);
    return 0;
}

void build(Prefix &prefix, istream &in)
{
	string buf;
	/*读入的是buf(可认为是后缀)*/
	while (cin >> buf)
	{
		add(prefix, buf);
		printf("是否继续:1(是) 0(否)");
		scanf("%d", &go_on);
		if (!go_on)
		{
			break;
		}
	}
}

void add(Prefix &prefix, const string &s)
{
	/*判断读入的字符串是否符合要求*/
	if (prefix.size() == NPREF)
	{
		statetab[prefix].push_back(s);
		prefix.pop_front();
	}
	prefix.push_back(s);
}

void generate(int nwords)
{
	Prefix prefix;
	int i;
	for (i = 0; i < NPREF; i++)
	{
		add(prefix, NONWORD);
	}
	for (i = 0; i < nwords; i++)
	{
		vector<string> &suf = statetab[prefix];
		const string &w = suf[rand() % suf.size()];
		if (w == NONWORD)
		{
			break;
		}
		cout << w << endl;
		prefix.pop_front();
		prefix.push_back(w);
	}
}

后记:

    对于Awk和Perl语言,我完全没有接触过,因此在这里就不做说明。

    对于这么多的语言来讲,程序设计实践这本书中给出了代码运行的各种时间,在下图中我们可以看出:C代码的执行时间要远远快于其他类型的代码。当然,随着时间的发展,cpu的运行速度也越来越快,但这个时间仍然具有参考意义。



猜你喜欢

转载自blog.csdn.net/Coulson_Zhao/article/details/79969330
3.6