3.3 在C语言下构造马尔科夫链数据结构

20人阅读 评论(0) 收藏 举报
分类:

3.3 在C语言下构造马尔科夫链数据结构

    在文章的开头,我们要强调一下文章的目的和整体脉络,以便在长文中丢失自我。

1.目的

    我们给定一个输入(前缀),算法将自动匹配它的后缀,并根据选定的后缀更新前缀再重选后缀直至文档结束

2.脉络

    对于我们期望的这种数据结构,我们要明白的是,它要服务马尔科夫链算法。回忆这个算法,我们不难发现算法中需要通过前缀去寻找后缀。而这之中,状态(State)又是一个很重要的存在。因为散列表(也就是哈希表)中对应的元素就是“状态”。

    由此,我们可以先定义状态,它是和散列表相对应的,我们可以通过散列函数来定位到“状态”;(lookup函数)

    状态中包含着前缀(两个单词)和数量未明确的后缀(后缀是一个单词);(结构体)

    随着我们对文本的读取,我们需要增加“状态“的数量,也就是增加前缀和其对应的后缀。(add与addsuffix函数)

3.主体

    我们首先定义一些常量:

enum
{
	NPREF = 2,			/*前缀中词的数量*/
	NHASH = 4093,		/*哈希表(散列表)大小*/
	MAXGEN = 10000		/*录入的最大单词数量*/
};

    我们设定一个很大的数组,这是因为我们预计程序可能会读取很大的文件,或许是一整本书。我们也选择了一个很大的散列表,这样,即使有10,000个前缀(词对)散列表中的平均链长依旧会很短(大概是两到三个前缀那么长)。数组越大,链的期望长度越短,查询进行的也就更快。过短的散列表会使得链长过长,查询时间变慢;过长的散列表则会导致储存空间的溢出。

    对于整个输入来讲,我们希望其能达到如下的目标:

    1.散列表中元素以状态(state)类型进行储存

    2.状态中包含前缀以及数量不能确定的后缀(Suffix)

    3.每个前缀和其对应的后缀都可以很好的联系在一起

    因此,我们定义如下几个结构体:

typedef struct State State;
typedef struct Suffix Suffix;
/*散列表中的元素*/
struct State
{
	/*前缀数量是确定的*/
	char *pref[NPREF];
	/*状态中要可以包含后缀及指向下一个前缀*/
	Suffix *suf;
	State *next;
};
/*后缀*/
struct Suffix
{
	char *word;
	/*可以构成多个后缀*/
	Suffix *next;
};
/*这个就是散列表*/
State *statetab[NHASH];

    为了方便理解,我们给出示意图:


    我们需要一个作用于前缀的散列函数,而这里要注意的是,前缀的形式是字符串数组。这就要求当输入是字符串数组的时候,散列函数任然可以给出一个对应于散列表的值。而我们的散列函数是:

/*散列表键值计算函数*/
unsigned int hash_number(char *s[NPREF])
{
	unsigned int h;
	unsigned char *p;
	int i;

	h = 0;
	for (i = 0; i < NPREF; i++)
	{
		for (p = (unsigned char *)s[i]; *p != '\0'; p++)
		{
			h = MULTIPLIER * h + *p;
		}
	}
	/*返回哈希表中的键值*/
	return h;
}

    可以看到,散列表中h的计算方法是十分具有唯一性的。也就是说,对于每一个不同的前缀,我们都会有一个唯一的h与其相对应,即使碰巧h相同,散列表中的状态也不会很多(一般不会超过5个)

    因此,在寻找前缀位置时,散列表的存在,就会让速度变得十分快捷。

State *lookup(char *prefix[NPREF], int create)
{
	int i, h;
	State *sp = NULL;

	h = hash_number(prefix);
	for (sp = statetab[h]; sp != NULL; sp = sp->next)
	{
		for (i = 0; i < NPREF; i++)
		{
			/*如果输入的prefix和哈希表中对应的sp->pref[i]不相同*/
			/*证明不是要找的前缀*/
			/*注意前缀中含有两个单词*/
			if (strcmp(prefix[i], sp->pref[i]) != 0)
			{
				break;
			}
		}
		if (i == NPREF)
		{
			return sp;
		}
	}
	/*在哈希表中没有与输入前缀相对应的部分时*/
	if (create)
	{
		sp = (State *)malloc(sizeof(State));
		for (i = 0; i < NPREF; i++)
		{
			sp->pref[i] = prefix[i];
		}
		sp->suf = NULL;
		/*将新生成的状态(State)加入到散列表中(哈希表)*/
		sp->next = statetab[h];
		statetab[h] = sp;
	}
	return sp;
}

    以上那些,都是建立在散列表已经形成的基础上的,那么如何构建一个散列表。就需要我们的build函数等了。

void build(char *prefix[NPREF], FILE *f)
{
	char buf[100], fmt[10];
	sprintf(fmt, "%%%ds", sizeof(buf) - 1);
	/*将文本中符合“fmt”定义的单词读取到buf中*/
	/*fmt定义为最大99个字符,因为基本所有单词不会超过20个字符*/
	while (fscanf(f, fmt, buf) != EOF)
	{
		/*使用字符串复制函数strdup*/
		add(prefix, strdup(buf));
	}
}
void add(char *prefix[NPREF], char *suffix)
{
	/*定位到prefix,并且把后缀suffix加入到prefix对应的sp中*/
	State *sp;
	sp = lookup(prefix, 1);
	addsuffix(sp, suffix);
	/*前缀的更新*/
	memmove(prefix, prefix + 1, (NPREF - 1) * sizeof(prefix[0]));
	prefix[NPREF - 1] = suffix;
}

    build函数是一个很精巧的函数(尤其注意在遍历文档(*f)中和add等函数的联动,运用散列表达到相当棒的效果)。它遍历文章,寻找与给定前缀相匹配的后缀。

    在遍历文档时,只要给出了文档的前两个单词,函数之间的联动就可以将整个文档形成散列表:

    1.给定前两个单词时,散列表为空,文档指针在开头。

    2.读入两个单词作为prefix后,将其后面的一个单词作为后缀,并更新前缀。同时,散列表中将会留有这个前缀和其对应的后缀

    3.循环2过程直到结尾

    注意a:当遇到完全相同的前缀时(散列函数值相同、英文相同),由于散列表中已经有这个前缀,相当于更新了这个前缀的后缀

    注意b:当遇到相同的前缀时(散列函数值相同、英文不同),这个新的前缀仍会被生成到散列表的对应位置上。只不过散列表的对应位置上将会出现两个状态(参看lookup函数)

    当完成以上所有步骤时,有关马尔科夫链的数据结构就搭建完成了,理解需要很长时间。下面是结构的代码,下一篇CSDN文章中将会给出具体的主函数实现。

    下面是数据结构,而具体的带着主函数的实现将在下一篇文章中讲到:

    算法实现

#include "stdafx.h"
#include "stdlib.h"
#include "string.h"
#include <iostream>

using namespace std;

enum
{
	NPREF = 2,			/*前缀中词的数量*/
	NHASH = 4093,		/*哈希表(散列表)大小*/
	MAXGEN = 10000,		/*录入的最大单词数量*/
	MULTIPLIER = 31,
	BUFSIZE = 100
};
char fmt[] = "%99s";

typedef struct State State;
typedef struct Suffix Suffix;
/*散列表中的元素*/
struct State
{
	/*前缀数量是确定的*/
	char *pref[NPREF];
	/*状态中要可以包含后缀及指向下一个前缀*/
	Suffix *suf;
	State *next;
};
/*后缀*/
struct Suffix
{
	char *word;
	/*可以构成多个后缀*/
	Suffix *next;
};
/*这个就是散列表*/
State *statetab[NHASH];

/*哈希表键值计算函数*/
unsigned int hash_number(char *s[NPREF])
{
	unsigned int h;
	unsigned char *p;
	int i;

	h = 0;
	for (i = 0; i < NPREF; i++)
	{
		for (p = (unsigned char *)s[i]; *p != '\0'; p++)
		{
			h = MULTIPLIER * h + *p;
		}
	}
	/*返回哈希表中的键值*/
	return h;
}

State *lookup(char *prefix[NPREF], int create)
{
	int i, h;
	State *sp = NULL;

	h = hash_number(prefix);
	for (sp = statetab[h]; sp != NULL; sp = sp->next)
	{
		for (i = 0; i < NPREF; i++)
		{
			/*如果输入的prefix和哈希表中对应的sp->pref[i]不相同*/
			/*证明不是要找的前缀*/
			/*注意前缀中含有两个单词*/
			if (strcmp(prefix[i], sp->pref[i]) != 0)
			{
				break;
			}
		}
		if (i == NPREF)
		{
			return sp;
		}
	}
	/*在哈希表中没有与输入前缀相对应的部分时*/
	if (create)
	{
		sp = (State *)malloc(sizeof(State));
		for (i = 0; i < NPREF; i++)
		{
			sp->pref[i] = prefix[i];
		}
		sp->suf = NULL;
		/*将新生成的状态(State)加入到散列表中(哈希表)*/
		sp->next = statetab[h];
		statetab[h] = sp;
	}
	return sp;
}

/*读取文档并且构建前缀、后缀*/

void build(char *prefix[NPREF], FILE *f)
{
	char buf[100], fmt[10];
	sprintf(fmt, "%%%ds", sizeof(buf) - 1);
	/*将文本中符合“fmt”定义的单词读取到buf中*/
	/*fmt定义为最大99个字符,因为基本所有单词不会超过20个字符*/
	while (fscanf(f, fmt, buf) != EOF)
	{
		/*将符合“fmt”标准的单词复制到buf中*/
		/*使用字符串复制函数strdup*/
		add(prefix, strdup(buf));
	}
}

/*添加函数,将会把整篇文档都加到散列表中*/
/*遍历文档,文章中所有前缀都能涉及*/
void add(char *prefix[NPREF], char *suffix)
{
	/*定位到prefix,并且把后缀suffix加入到prefix对应的sp中*/
	State *sp;
	sp = lookup(prefix, 1);
	addsuffix(sp, suffix);
	/*前缀的更新*/
	memmove(prefix, prefix + 1, (NPREF - 1) * sizeof(prefix[0]));
	prefix[NPREF - 1] = suffix;
}

/*为某一个特定的前缀添加后缀*/
void addsuffix(State *sp, char *suffix)
{
	Suffix *suf;
	suf = (Suffix *)malloc(sizeof(Suffix));
	suf->word = suffix;
	suf->next = sp->suf;
	sp->suf = suf;
}
int main()
{
    return 0;
}


查看评论

c语言实现线性表的建立,初始化,插入,删除,查找,遍历以及时间复杂度分析

顺序表的特点是元素的逻辑顺序与物理顺序相同 顺序表结构:#include #define InitSize 100 typedef int ElemType;typedef struct{ ...
  • weixin_36372879
  • weixin_36372879
  • 2017-08-21 16:47:05
  • 259

c语言下对鼠标键盘的控制

  • 2012年03月24日 14:52
  • 1KB
  • 下载

C语言中计算机概念

C语言中计算机概念:栈:栈(stack)是软件中最重要的概念之一,几乎每一个程序都使用了栈, 没有栈就没有函数,没有局部变量. ! 堆:光有栈还远远不够, 因为栈在函数返回的时候就会被释放掉,无法用栈...
  • hejinjing_tom_com
  • hejinjing_tom_com
  • 2017-06-30 11:17:56
  • 299

单片机C语言下LCD多级菜单的一种

  • 2011年04月10日 17:40
  • 78KB
  • 下载

三角星图

/*   * Copyright (c) 2011, 烟台大学计算机学院   * All rights reserved.   * 文件名称:test.cpp   * 作 ...
  • liuweili2012
  • liuweili2012
  • 2012-10-13 10:18:19
  • 1014

C语言中的常用的几种系统时间结构体类型

在C语言涉及中经常需要定时触发事件,涉及到获取系统时间,其结构体类型有多种。Unix/Linux系统下有以下几种时间结构: 1、time_t 类型:长整型,一般用来表示从1970-01-01 0...
  • Season_hangzhou
  • Season_hangzhou
  • 2013-04-02 13:42:56
  • 1055

c语言下进行socket通信

  • 2013年07月01日 23:44
  • 33KB
  • 下载

学生成绩管理系统

  • 2014年04月09日 10:13
  • 1KB
  • 下载

c#委托基础;c#委托Vs C++函数指针

今天看到c#的委托,忽然来劲,想了解一下。这篇文章入门还是可以: http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-i...
  • xinpo66
  • xinpo66
  • 2015-06-26 10:58:56
  • 490

C语言 数据结构 线性表 顺序表 线性表的顺序存储结构

//C语言--数据结构--线性表  顺序表 线性表的顺序存储结构 //其实顺序表就相当于一个数组 #include #include #include #include #include ...
  • qq_34814092
  • qq_34814092
  • 2017-06-06 21:42:41
  • 118
    个人资料
    持之以恒
    等级:
    访问量: 4026
    积分: 513
    排名: 9万+
    最新评论