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

杂谈; 打败自己的 往往不是敌人,而是自己。坚持不易,且行且珍惜。

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

键树的根节点不包含字符,除根节点外的非叶子节点都只包含一个字符,叶子节点存储具体信息。; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。并且 兄弟节点 按 按 字符大小 排序。

例如:

它的 键树形式如下:

键树的应用:(摘自百度)

串的快速检索

给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。

在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。

串”排序

给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出

用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。

最长公共前缀

对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为当时公共祖先问题。

键树 有 两种 表示 方法:1. 双链表 表示法 ,就是 树的 孩子链表 表示法   2.多重链表表示法(Trie树)

下面给出 键树的 双链表 表示法的 插入,删除,查找  等功能 代码:

完整源代码网盘地址http://yun.baidu.com/share/link?shareid=582619924&uk=1208029266

// DSTree.cpp : 定义控制台应用程序的入口点。
//键树的双链树表示法

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

#define MAX_KEY_LEN	16
#define LEAF_END_CHAR '$'//叶子节点结束符
struct KeyType{
	char key[MAX_KEY_LEN];
	int len;
};

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

enum E_Kind{
	E_Kind_Leaf,
	E_Kind_Branch,
};
typedef struct DLNode{
	char data;
	DLNode * nextSibling;//下一个兄弟节点
	E_Kind kind;;
	union
	{
		char * record;//叶子节点的信息
		DLNode * firstChild;//第一个孩子节点(分支节点)
	};
}*DLTree;

DLNode * makeNode(E_Kind kind){
	DLNode * node = (DLNode *)malloc(sizeof(DLNode));
	node->kind = kind;
	node->nextSibling = NULL;
	if (node->kind == E_Kind_Branch){
		node->firstChild = NULL;
	}
	else{
		node->record = (char *)malloc(sizeof(char)*MAX_KEY_LEN);
		node->data = LEAF_END_CHAR;
	}
	return node;
}

void initTree(DLTree * tree){
	*tree = NULL;
}

void destoryTree(DLTree * tree){
	DLTree p = *tree;
	if (p != NULL){
		if (p->kind == E_Kind_Leaf){
			free(p->record);
		}
		else{
			destoryTree(&p->firstChild);
		}
		destoryTree(&p->nextSibling);
		free(p);
		*tree = NULL;
	}
}

DLTree  search(DLTree t,char * k){
	if (t != NULL)
	{
		KeyType kt;
		initKey(&kt,k);
		int times = 0;
		t = t->firstChild;
		while (t && times < kt.len){
			while (t && t->data < kt.key[times]) t = t->nextSibling;
			if (t && t->data == kt.key[times]){
				t = t->firstChild;
				times++;
			}
			else{
				return NULL;
			}
		}
		if (times == kt.len && t->kind == E_Kind_Leaf){
			return t;
		}
	}
	return NULL;
}

//搜索结果.为插入 关键字 服务
//是第一个孩子节点:tree:返回 父亲节点 ,不是 返回 兄弟节点.
struct Result{
	bool isFind;//是不是找到了
	bool isFirst;//是不是第一个孩子节点
	DLTree tree;//插入位置
	int index;//需要插入的节点 的位置.
};

Result searchForInsert(DLTree tree,KeyType key){
	int times = 0;
	Result result;
	result.isFirst = false;
	result.isFind = false;
	while (tree && times < key.len){
		DLTree fatherNode = tree;
		result.index = times;
		tree = tree->firstChild;
		while (tree){
			if (tree->data > key.key[times]){
				if (fatherNode->firstChild == tree){
					result.isFirst = true;
					result.tree = fatherNode;
				}
				return result;
			}
			else if(tree->data == key.key[times]){
				result.tree = tree;
				times ++;
				break;
			}
			else{// $ 比 a ~ z 小
				result.tree = tree;
				tree = tree->nextSibling;
			}
		}
	}
	if ( times == key.len){//全部都相等
		if (tree && tree->firstChild && tree->firstChild->kind == E_Kind_Leaf){
			result.isFind = true;
			result.tree = tree->firstChild;
		}
		else{
			result.isFirst = true;
			result.index = times;
		}
	}
	return result;

}

void insert(DLTree t,KeyType kt,int index){
	for (int i = index; i < kt.len; i++){
		DLNode * node = makeNode(E_Kind_Branch);
		node->data = kt.key[i];
		t->firstChild = node;
		t = node;
	}
	//最后插入叶子节点
	DLNode * leaf = makeNode(E_Kind_Leaf);
	strncpy(leaf->record,kt.key,kt.len+1);
	t->firstChild = leaf;
}

void insertTree(DLTree * tree,char * key){
	KeyType kt;
	initKey(&kt,key);
	if (*tree == NULL){//空树,需要设置一个根节点
	    *tree = makeNode(E_Kind_Branch);
		insert(*tree,kt,0);
		return;
	}
	Result result = searchForInsert(*tree,kt);
	if (result.isFind == false){//没找到,再插入.
		if (result.isFirst){//插入在头部
			DLTree fatherNode = result.tree;
			DLTree oldFirst = fatherNode->firstChild;
			insert(fatherNode,kt,result.index);
			fatherNode->firstChild->nextSibling = oldFirst;
		}
		else{
			DLTree leftBrother = result.tree;
			DLNode * node = makeNode(E_Kind_Branch);
			node->data = kt.key[result.index];
			node->nextSibling = leftBrother->nextSibling;
			leftBrother->nextSibling = node;
			insert(node,kt,result.index+1);
		}
	}
}

void deleteTree(DLTree * t,char * k){
	if (*t != NULL)
	{
		KeyType kt;
		initKey(&kt,k);
		DLTree p = (*t)->firstChild;
		int times = 0;
		linkStack stack;
		stackInit(&stack);
		stackPush(&stack,*t);
		while (p && times < kt.len){
			while (p && p->data < kt.key[times]) p = p->nextSibling;
			if (p && p->data == kt.key[times]){
				stackPush(&stack,p);
				p = p->firstChild;
				times++;
			}
			else{
				return;//没找到
			}
		}
		if (p && p->kind == E_Kind_Leaf){
			DLTree f = NULL;
			while (!stackEmpty(stack)){
				stackPop(&stack,&f);
				if (f->firstChild == p && p->nextSibling == NULL){//父亲只有 一个孩子
					free(p);
					p = f;
					if (*t == f){//所有关键字都被删除了,空树..
						free(*t);
						*t = NULL;
						return;
					}
				}
				else{
					break;
				}
			}
			if (f->firstChild == p){
				f->firstChild = p->nextSibling;
			}
			else{
				DLTree pre = f->firstChild;
				while (pre != NULL && pre->nextSibling != p) pre = pre->nextSibling;
				pre->nextSibling = p->nextSibling;
			}
			free(p);
		}
		stackDestory(&stack);
	}
}

//层序遍历
void levelTraverse(DLTree tree){
	if (tree != NULL){
		if (tree->firstChild != NULL){
			printf("----------------层序遍历----------------\n");
			LinkQueue queue;
			queueInit(&queue);
			enqueue(&queue,tree->firstChild);
			int count = 1;
			int level = 1;
			int nextCount = 0;
			while (!queueEmpty(queue)){
				printf("%d 层数据:",level);
				for (int i = 0; i < count; i++){
					DLTree t;
					dequeue(&queue,&t);
					while (t){
						printf("%c   ",t->data);
						if (t->kind == E_Kind_Branch &&  t->firstChild != NULL){
							enqueue(&queue,t->firstChild);
							nextCount++;
						}
						t = t->nextSibling;
					}
				}
				printf("\n");
				count = nextCount;
				nextCount = 0;
				level++;
			}
			queueDestory(&queue);
			printf("\n");
		}
	}
}

static char testArray[][MAX_KEY_LEN] = {//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[])
{
	DLTree root;
	initTree(&root);
	for (int i = 0; i < 16; i++){
		insertTree(&root,testArray[i]);
	}
	levelTraverse(root);
	for (int i = 0; i < 18; i++){
		char  * s = testArray[i];
		DLTree t = search(root,s);
		if (t){
			printf("查找 %s后,记录为:%s\n",s,t->record);
		}
		else{
			printf("查找 %s,没找到\n",s);
		}
	}
	for (int i = 0; i < 18; i++){
		char * s = testArray[i];
		deleteTree(&root,s);
		printf("-------------删除%s后--------------\n",s);
		levelTraverse(root);
	}
	destoryTree(&root);
	return 0;
}

完整源代码网盘地址http://yun.baidu.com/share/link?shareid=582619924&uk=1208029266

时间: 2024-11-05 19:00:33

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

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

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

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

trie树,是用 树的 多重链表来表示 树的.每个节点 有 d 个指针域.若从键树中的某个节点到叶子节点的路径上每个节点都只有一个孩子,则可以把 路径上的所有节点压缩成一个叶子节点,且在叶子节点中 存储 关键字 以及 根关键字相关的信息. 当节点的度 比较大时,选择 Trie树,要比 双链表树更为合适. tire树的 数据 压缩 是 挺与众不同的. 下面 给出 具体的 代码: 源代码工程文件网盘地址:http://pan.baidu.com/s/1cyTg6 // TrieTree.cpp :

看数据结构写代码(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

看数据结构写代码(67) 置换 _ 选择排序(完结篇)

杂谈: 严蔚敏版<数据结构(C语言版)> 一书 终于看完了.这是 一个完结,也是 一个新的开端.<算法导论> 已到手. 置换选择排序的思想 是 将 归并段 尽量 变的 更大,而不是根据 内存 大小 限制在 固定的 大小. 这样 可以 利用赫夫曼树 来 进行 最优归并树,从而 使 外存 读写次数 最少. 下面给出 具体 代码:欢迎指出代码不足. // Replace_Selcetion.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环