看数据结构写代码(60 ) 键树的多重链表表示(Trie树)

trie树,是用 树的 多重链表来表示 树的。每个节点 有 d 个指针域。若从键树中的某个节点到叶子节点的路径上每个节点都只有一个孩子,则可以把 路径上的所有节点压缩成一个叶子节点,且在叶子节点中 存储 关键字 以及 根关键字相关的信息。

当节点的度 比较大时,选择 Trie树,要比 双链表树更为合适。

tire树的 数据 压缩 是 挺与众不同的。

下面 给出 具体的 代码:

源代码工程文件网盘地址:http://pan.baidu.com/s/1cyTg6

// TrieTree.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <cstdlib>
#include <cstring>
#include "stack.h"

#define BRANCH_MAX_SIZE  27//分支节点最大指针数
#define MAX_SIZE 16
#define LEAF_END_CHAR '$'
enum E_Kind{
	E_Kind_Branch,
	E_Kind_Leaf,
};

struct KeyType{
	char k [MAX_SIZE];
	int len;
};

void initKey(KeyType * kt,char *k){
	kt->len = strlen(k);
	strncpy(kt->k,k,kt->len+1);
}

typedef struct TrieNode{
	E_Kind kind;
	union {
		struct {
			TrieNode * ptr[BRANCH_MAX_SIZE];
			int num;
		}branch;
		struct {
			char record[MAX_SIZE];
		}leaf;
	};
}* TrieTree;

TrieNode * makeNode(E_Kind kind){
	TrieNode * node = (TrieNode*) malloc(sizeof(TrieNode));
	node->kind = kind;
	if (kind == E_Kind_Branch){
		for (int i = 0; i < BRANCH_MAX_SIZE; i++){
			node->branch.ptr[i] = NULL;
		}
		node->branch.num = 0;
	}
	return node;
}

TrieNode * makeLeafNode(char * key){
	TrieNode * leaf = makeNode(E_Kind_Leaf);
	strncpy(leaf->leaf.record,key,strlen(key)+1);
	return leaf;
}

void initTree(TrieTree* t){
	*t = NULL;
}

void destoryTree(TrieTree * t){
	if (*t != NULL)
	{
		TrieTree p = *t;
		if (p->kind == E_Kind_Branch){
			for (int i = 0; i < BRANCH_MAX_SIZE; i++){
				if (p->branch.ptr[i] != NULL){
					destoryTree(&p->branch.ptr[i]);
				}
			}
		}
		free(*t);
		*t = NULL;
	}
}

int getIndex(char * k,int i){
	KeyType kt;
	initKey(&kt,k);
	int index = 0;
	if(i != kt.len){
		index = kt.k[i] - 'a' + 1;
	}
	return index;

}

struct Result{
	bool isFind;
	TrieNode * t;
	int index;
};
//找到了返回 保存记录的 叶子节点;
//否则返回插入位置
Result search(TrieTree t,char * k){
	KeyType kt;
	initKey(&kt,k);
	Result r;
	for (int i = 0; t && t->kind == E_Kind_Branch && i<= kt.len; i++){
		int index = getIndex(k,i);
		r.t = t;
		r.index = i;
		t = t->branch.ptr[index];
	}
	if (t &&  t->kind == E_Kind_Leaf  && strcmp(t->leaf.record,k) == 0){//节点可以压缩,所以没有 == kt.len的条件.
		r.isFind = true;
		r.t = t;
	}
	else{
		r.isFind = false;
	}
	return r;
}

void insertTree(TrieTree *t,char * key){
	if (*t == NULL){
		*t = makeNode(E_Kind_Branch);
	}
	Result r = search(*t,key);
	if (r.isFind == false){
		int index = getIndex(key,r.index);
		TrieTree p = r.t->branch.ptr[index];
		TrieNode * leaf = makeLeafNode(key);
		if (p == NULL){
			r.t->branch.ptr[index] = leaf;
			r.t->branch.num ++;
		}
		else{
			TrieTree q = r.t;
			int times = r.index+1;
			int len = strlen(key);
			while (times <= len){//为字符 相同的节点 建立 分支
				int  index1 = getIndex(key,times);
				int index2= getIndex(p->leaf.record,times);
				TrieNode * branch = makeNode(E_Kind_Branch);
				q->branch.ptr[index] = branch;
				if (index1 != index2){
					branch->branch.ptr[index1] = leaf;
					branch->branch.ptr[index2] = p;
					branch->branch.num += 2;
					break;
				}
				else{
					branch->branch.num = 1;
					q = branch;
					index = index1;
					times++;
				}
			}
		}
	}

}

void deleteTrie(TrieTree * t,char * k){
	if(*t != NULL){
		linkStack stack;
		stackInit(&stack);
		stackPush(&stack,*t);
		KeyType kt;
		initKey(&kt,k);
		TrieTree p = *t;
		int i = 0;
		for (; p && p->kind == E_Kind_Branch && i <= kt.len; i++){
			int index = getIndex(k,i);
			stackPush(&stack,p);
			p = p->branch.ptr[index];
		}
		if (p && p->kind == E_Kind_Leaf && strcmp(p->leaf.record,k) == 0){
			TrieTree brotherNode = NULL;
			while (!stackEmpty(stack)){
				TrieTree f;
				stackPop(&stack,&f);
				free(p);
				int index = getIndex(k,i);
				f->branch.ptr[i] = NULL;
				f->branch.num --;
				if (f->branch.num == 0){
					p = f;
					i--;
					if (f == *t){//空树了..
						free(*t);
						*t = NULL;
					}
				}
				else{
					break;
				}
			}
		}
		else{//没找到
			return;
		}
	}
}

static char testArray[][MAX_SIZE] = {//18 个
    "cao","cai","chang","chao","cha","chen",//6
    "wen","wang","wu",//3
    "zhao",//1
    "li","lan","long","liu",//4
    "yun","yang",//2
    "zha","l",
};  

int _tmain(int argc, _TCHAR* argv[])
{
	TrieTree root;
	initTree(&root);
	for (int i = 0; i < 18; i++){
		insertTree(&root,testArray[i]);
	}
	for (int i = 0; i < 18; i++){
		char * s = testArray[i];
		Result r = search(root,s);
		if (r.isFind){
			printf("查找%s ,结果为:%s\n",s,r.t->leaf.record);
		}
		else{
			printf("查找%s ,未找到\n",s);
		}
	}
	destoryTree(&root);
	return 0;
}

运行截图:

时间: 2024-08-01 22:45:14

看数据结构写代码(60 ) 键树的多重链表表示(Trie树)的相关文章

看数据结构写代码(59) 键树的双链表示法

杂谈; 打败自己的 往往不是敌人,而是自己.坚持不易,且行且珍惜. 键树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高 键树的根节点不包含字符,除根节点外的非叶子节点都只包含一个字符,叶子节点存储具体信息.: 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串:

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(36) 图的邻接表表示与实现

图的邻接表表示法,是为每一个顶点建立一个链表,链表里存放着相同弧尾的 弧的信息,这些链表顺序存放在数组中.下面是无向图g2的邻接表 邻接表 比 邻接矩阵 节省空间,同时 也带来一些操作上的 不便,例如 看 两个顶点是否 相邻,需要 遍历 链表,在 求 无向图顶点的度时,只需 遍历 顶点的链表,而 求 有向图 顶点的度 需要 遍历 整个图 查找 弧头 为这个顶点的 个数. 如果 不想这样做,可以 建立 逆邻接表,即 链表里 存放着 相同 弧头的 弧 的信息. 下一节 要说的 十字链表 类似于这种结

看数据结构写代码(39) 图的遍历(深搜和广搜)

图的遍历算法 有两种 :深度优先搜索遍历 和 广度 优先搜索遍历.深度优先搜索遍历类似与 树的 先序遍历.广度优先搜索遍历类似与树的层序遍历.只不过 图 可以有 不连通的 节点,所以 得 遍历 整个顶点数组. 深搜遍历 总是 先访问当前节点的邻接点,而 广搜算法 是 先访问顶点的邻接点 要 先于 后访问顶点的邻接点 被 访问. 具体遍历顺序如下: 以下代码 以 图的 邻接多重表 为 基本结构进行 遍历. 首先更改 上节 的 查找 邻接点 和 下一个邻接点的 返回值,以及 邻接点的 代码 有误,少

看数据结构写代码(57) AVL树的删除

上一节 已经说了 AVL树的插入 操作,可是 只有 插入,没有删除,怎么能叫 动态 查找表呢. 呵呵,博主 赶紧 去 研究了一番.下面 是成果: AVL树的删除 大致 分为 两大块: 1. 查找节点 并 删除 2. 保持 删除 后 平衡因子的 影响 1. 首先 找到 这个 节点,如果 节点 不存在,直接 退出 函数 if (*tree == NULL){//没找到 return false; } 2.如果 存在,分为 四种情况:(根 二叉 排序树的 删除 类似) 1.节点 为 叶子 节点,直接

看数据结构写代码(66) 败者树

计算机的 内存 是 有限的,无法 存入 庞大的数据.当 遇到 大数据需要排序时,我们 需要 将 这些 数据 分段 从 硬盘里 读到 内存中,排好序,再 写入到 硬盘中,这些段 叫做 归并段.最后将 这些 分段 合并 成 一个 最终  完整 有序的 数据. 这里 操作的 时间 =  内部 排序 时间 +  外存读写时间 + 内部归并所需时间. 其中 外存 读写时间 最耗时,外存读写时间 = 读写次数 * 读写数据的时间 ,读写 数据的时间 因 设备 性能 而 影响,我们 无法控制,我们 只能 控制

看数据结构写代码(54)次优查找树

查找顺序表时,若 每个元素的概率 都相等 用 二分查找 效率 最高.但是 如果 概率 不相等时,(SOST)静态最优查找表 效率 要高于 二分查找.静态最优查找表 是 使得 从 根 到 每个节点的路径 长度 和 权值 乘积 之和 最小. 书上说的 静态最优 查找树的创建 时间 复杂度 较高,所以 用 次优 查找树(NOST) 代替. 下面 上代码: // Nost.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <cstd

看数据结构写代码(34) 树与回溯法(二)排序树(8皇后问题)

套用回溯 公式程序: void backtrack (int t) { if (t > n) { // 到达叶子结点,将结果输出 output (x); } else { // 遍历结点t的所有子结点 for (int i = f(n,t); i <= g(n,t); i ++ ) { x[t] = h[i]; // 如果不满足剪枝条件,则继续遍历 if (constraint (t) && bound (t)) backtrack (t + 1); } } } 写出 8皇后问

看数据结构写代码(33) 树与回溯法(一) 子集树

回溯法 是 一种 在 穷举 中,裁剪 不满足 条件 的 分支,已达到 提高 效率的 方法.其基本原型 是 树的 先序遍历,从 树根 到 树叶的路径 是 问题的 一个 解. 回溯法的基本框架 =  确定 解空间 + 深度优先遍历 + 裁剪函数 + 确定结果函数 其中 解空间,分为 子集树 和 排序树. 具体 概念 详解:参考 点击打开链接  和 点击打开链接 递归算法通用 模板如下: 回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法. // 针对N叉树的递归回溯方法 void