数据结构和设计模式02(树,图)


1.图

图分为两种:有向图、无向图。

图的表示方法:邻接表、邻接矩阵。

邻接矩阵:可以直接用二维数组表示,0表示没有边,1表示有边,可以加上权重。优点:可以快速判断两点是否存在边,缺点:稀疏图时不必要的空间浪费。特点:对称。

邻接表:   

用到的数据结构是(网上大多数这么做的,其实我觉得顶点表和边表可以共用一个数据结构)

一个是顶点表,包括顶点和指向下一个边表头节点的指针

一个是边表, 数据结构跟顶点不同,存储的是顶点的序号,和指向下一个的指针

刚开始的时候把顶点表初始化,指针指向null。然后边表插入进来,是插入到前一个,也就是直接插入到firstedge指向的下一个,而后面的后移

<span style="font-family:SimSun;font-size:10px;background-color: rgb(255, 255, 255);">typedef char VertexType;
typedef struct node   //边表节点  
{
	int adjvex;
	node* next;
}EdgeNode;

typedef struct     //顶点表节点  
{
	VertexType vertex;
	EdgeNode* firstedge;
}VertexNode;

typedef VertexNode AdjList[MaxVertexNum];

typedef struct
{
	AdjList adjlist;
	int n, e;

}ALGraph;</span>

 

图的广度优先算法(BFS):

称之为广度优先,是因为算法始终首先发现距离起始顶点较近的顶点,然后才发现较远的顶点。假设搜索的出发顶点为s,则首先搜索与s直接相邻的顶点,然后再搜索这些相邻顶点的相邻顶点。在搜索过程中可以记录每个顶点到起始顶点s的距离。这种搜索算法能生成一棵以s为根、包括所有s可达的顶点的广度优先搜索树(BFS树)。图中各顶点的访问次序对应于广度优先搜索树中各节点由顶至底的层次。

在这里我们设计算法跟踪图中各个顶点的访问次序,记录各个顶点在BFS树中的层数(搜索深度)以及父顶点。首先将各个顶点着白色,在跟踪各个顶点访问次序时,第一次被访问的顶点的颜色改变为灰色,直至与之相邻的所有顶点都被访问时颜色改变为黑色。在这个过程中颜色为黑色和白色的顶点之间被颜色为灰色的顶点分割开来。

广度优先搜索算法的实现借助于队列结构,额外需要存储颜色数组,原理图如下:按照图的原理,写出来不难(注意:需要维护的有,颜色数组,深度,输入为:图的邻接表,起始顶点)。

形成的搜索树如下:



图的深度优先,看原理图如下:


深度优先搜索是尽可能深的搜索一个图,对于一个新发现的节点,如果还有以此为起点还未探测到的边,就沿此边探测下去。当顶点v的所有边都被探寻过后,搜索将回溯到发现顶点v的起始点的那些边。这一过程一直进行到一发现从原点可达的所有点为止。如果还存在未发现的顶点,则选择其中一个座位源顶点,重复上过程

引申:Trie树,字典树,是hash树的变形,常用在搜索引擎(来自前辈的一篇博文:http://www.cnblogs.com/tanky_woo/archive/2010/09/24/1833717.html)。

又称单词查找树Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。

从上面的图中,我们或多或少的可以发现一些好玩的特性。
  第一:根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
      第二:从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串。
      第三:每个单词的公共前缀作为一个字符节点保存。

二:使用范围
     既然学Trie树,我们肯定要知道这玩意是用来干嘛的。
     第一:词频统计。
            可能有人要说了,词频统计简单啊,一个hash或者一个堆就可以打完收工,但问题来了,如果内存有限呢?还能这么
             玩吗?所以这里我们就可以用trie树来压缩下空间,因为公共前缀都是用一个节点保存的。
     第二: 前缀匹配
           就拿上面的图来说吧,如果我想获取所有以"a"开头的字符串,从图中可以很明显的看到是:and,as,at,如果不用trie树,
            你该怎么做呢?很显然朴素的做法时间复杂度为O(N 2 ) ,那么用Trie树就不一样了,它可以做到h,h为你检索单词的长度,
            可以说这是秒杀的效果。
举个例子:现有一个编号为1的字符串”and“,我们要插入到trie树中,采用动态规划的思想,将编号”1“计入到每个途径的节点中,  那么以后我们要找”a“,”an“,”and"为前缀的字符串的编号将会轻而易举。  
#define MAX 26
typedef struct Trie
{
	Trie *next[MAX];
	int v;   //根据需要变化
};

Trie *root;
Trie的查找(最主要的操作)
(1) 每次从根结点开始一次搜索;
(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;   (3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。   
(4) 迭代过程……   
(5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。

生成Trie树:

void createTrie(char *str)
{
    int len = strlen(str);
    Trie *p = root, *q;
    for(int i=0; i<len; ++i)
    {
        int id = str[i]-'0';
        if(p->next[id] == NULL)
        {
            q = (Trie *)malloc(sizeof(Trie));
            q->v = 1;    //初始v==1
            for(int j=0; j<MAX; ++j)
                q->next[j] = NULL;
            p->next[id] = q;
            p = p->next[id];
        }
        else
        {
            p->next[id]->v++;
            p = p->next[id];
        }
    }
    p->v = -1;   //若为结尾,则将v改成-1表示
}
查找:

int findTrie(char *str)
{
    int len = strlen(str);
    Trie *p = root;
    for(int i=0; i<len; ++i)
    {
        int id = str[i]-'0';
        p = p->next[id];
        if(p == NULL)   //若为空集,表示不存以此为前缀的串
            return 0;
        if(p->v == -1)   //字符集中已有串是此串的前缀
            return -1;
    }
    return -1;   //此串是字符集中某串的前缀
}










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值