键树的基本概念
键树又称数字查找树(Digital Search Tree)。
它是一棵度大于等于2的树,树中的每个结点中不是包含一个或几个关键字,而是只含有组成关键字的符号。
例如,若关键字是数值,则结点中只包含一个数位;若关键字是单词,则结点中只包含一个字母字符。
这种树会给某种类型关键字的表的查找带来方便。
如下图所示为一棵键树:
从根到叶子结点路径中结点的字符组成的字符串表示一个关键字,叶子结点中的特殊符号$表示字符串的结束。
在叶子结点中还含有指向该关键字记录的指针。
为了查找和插入方便,我们约定键树是有序树,即同一层中兄弟结点之间依所含符号自左至右有序,并约定$小于任何字符。
键树中每个结点的最大度d和关键字的“基”有关,若关键字是单词,则d=27,若关键字是数值,则d=11。
键树的深度h则取决于关键字中字符或数位的个数。
通常,键树可有两种存储结构,分别称为双链树和Trie树。
双链树
以树的孩子兄弟链表来表示键树,则每个分支结点包括三个域:
symbol域:存储关键字的一个字符;
first域:存储指向第一棵子树根的指针;
next域:存储指向右兄弟的指针。
同时,叶子结点不含first域,它的infoptr域存储指向该关键字记录的指针。
此时的键树又称双链树。
在双链树中插入或删除一个关键字,相当于在树中某个结点上插入或删除一棵子树。
结点的结构中可以设置一个枚举变量表示结点的类型,叶子结点和分支结点。
叶子结点和分支结点都有symbol域和next域。不同的一个域可以用联合表示,叶子结点包含infoptr指向记录,而分支结点是first域指向其第一棵子树。
双链树如下图:
双链树的查找可如下进行:
假设给定值为K.ch(0..num-1), 其中K.ch[0]至 K.ch[num-2]表示待查关键字中num-1个字符, K.ch[num-1]为结束符$。
从双链树的根指针出发,顺first指针找到第一棵子树的根结点,以K.ch[0]和此结点的symbol域比较,若相等,则顺first域再比较下一字符,否则沿next域顺序查找。
若直至空仍比较不等,则查找不成功。
下面是算法表示的代码:
Trie树
若以树的多重链表表示键树,则树的每个结点中应含有d个指针域,此时的键树又称Trie树。
(Trie是从检索retrieve中取中间四个字符的,读音同try)。
若从键树中某个结点到叶子结点的路径上每个结点都只有一个孩子,则可将该路径上所有结点压缩成一个“叶子结点”,且在该叶子结点中存储关键字及指向记录的指针等信息。
在Trie树中有两种结点:
分支结点:含有d个指针域和一个指示该结点中非空指针域的个数的整数域。在分支结点中不设数据域,每个分支结点所表示的字符均有其父结点中指向该结点的指针所在位置决定。
叶子结点:含有关键字域和指向记录的指针域。
Trie树如下图:
在Trie树上进行查找的过程为:
从根结点出发,沿和给定值相应的指针逐层向下,直至叶子结点,若叶子结点中的关键字和给定值相等,则查找成功,若分支结点中和给定值相应的指针为空,或叶子结点中的关键字和给定值不相等,则查找不成功。
使用Trie树创建字典小程序:
声明:
Calss Mean 存放单词解释
Class Value 存放单词
Class Node 节点类
Class LinkList 链表类
Class Tree 树类
Class Main 主类
Class Test1 单词解析类
代码:
package 字典;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Test1 {
public int write1(Value[] V, Mean[] M){
String path = "./bin/字典/word.txt";
//String topath = "F:/IO/a.txt";
//String tpath = "F:/IO/a_str.txt";
int c1=0;
int c2=0;
//FileWriter w = null;
// FileReader r = null;
InputStreamReader r=null;
try{
//r = new FileReader(str);
r = new InputStreamReader(new FileInputStream(path),"utf-8");
int temp=0;
String ss = "";
String sss = "";
//String ssss = "";
boolean asd = true;
Value[] v = new Value[10000];
Mean[] m = new Mean[10000];
int count=0;
int count1=0;
while((temp = r.read())!=-1){
//System.out.println((char)temp);
char c = (char)temp;
if(c==' '){
Value v1 = new Value();
v1.value=ss;
ss="";
v[count]=v1;
count++;
asd=false;
}
if((c>='A'||c>='a')&&(c<='z'||c<='Z')&&asd==true){
ss+=String.valueOf(c);
}
if(asd==false){
sss+=String.valueOf(c);
}
if(c=='\n'){
Mean m1 = new Mean();
m1.mean=sss;
sss="";
m[count1]=m1;
count1++;
asd=true;
}
}
//Value[] V = new Value[10000];
//Mean[] M = new Mean[10000];
for(int i=0;i<count;i++){
if(v[i].value.equals("")){
continue;
}else{
//System.out.print(v[i].value);
Value v1 = new Value();
v1.value=v[i].value;
V[c1]=v1;
c1++;
}
}
for(int i=0;i<count1;i++){
if(m[i].mean.equals("")){
continue;
}else{
//System.out.println(m[i].mean);
Mean m1 = new Mean();
m1.mean=m[i].mean;
M[c2] = m1;
c2++;
}
}
/* for(int i=0;i<c1;i++){
System.out.print(V[i].value);
System.out.println(M[i].mean);
}*/ //用于测试
}catch(IOException e){
e.printStackTrace();
}finally{
if(r != null){
try{
r.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
return c1;
}
}
package 字典;
public class Node {
/*节点结构
* 链表上的每个节点有三个指针
* 左右指针指向其相连的左右节点
* 向下指针指向其下方的链表
*/
char key; //键值
int k; //标记键上的value是否为空
String value; //存放单词
String mean; //存放对应单词意思
Node leftChild;
Node rightChild;
LinkList down;
//LinkList lt;
public Node(char k){//, String v, String m
key = k;
//value = v;
//mean = m;
k=0;
leftChild=rightChild = null;
down = null;
}
//public void setLinkList(LinkList l){
// this.lt=l;
//}
//public LinkList getLinkList(){
// return lt;
//}
public void setK(int a){
k=a;
}
public int getK(){
return this.k;
}
public void setKey(char k){
this.key=k;
}
public char getKey(){
return key;
}
public void setValue(String v){
this.value=v;
}
public String getValue(){
return value;
}
public void setMean(String m){
this.mean=m;
}
public String getMean(){
return mean;
}
public void setRightChild(Node right){
this.rightChild = right;
}
public Node getRightChild(){
return rightChild;
}
public void setLeftChild(Node left){
this.leftChild = left;
}
public Node getLeftChild(){
return leftChild;
}
public void setDown(LinkList d){
down = d;
}
public LinkList getDown(){
return down;
}
}
package 字典;
public class LinkList {
Node head;
Node tail;
public LinkList(){
head=tail=null;
}
public boolean isEmpty(){
if(head==null){
return true;
}
return false;
}
public void addNode(char k){//, String v, String m
//Node node = new Node(k);//, v, m
Node node = new Node(k);
if(head==null){
head=node;
tail=node;
head.setLeftChild(null);
tail.setRightChild(null);
}else{
tail.setRightChild(node);
node.setLeftChild(tail);
tail=node;
tail.setRightChild(null);
}
}
public boolean find(char k){
Node node=head;
while(node!=null){
if(node.key==k){
return true;
}
node=node.getRightChild();
}
return false;
}
public Node findn(char k){
Node node=head;
while(node!=null){
if(node.key==k){
return node;
}
node=node.getRightChild();
}
return null;
}
}
package 字典;
public class Tree {
//Node root;
LinkList root;
char[] arr = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
public Tree(){
}
public boolean isEmpty(){
if(root==null){
return true;
}
return false;
}
public void creatTree(String V ,String M){ //根据传入单词创建树,这样创建的树最小
if(isEmpty()){ //添加根节点链表的键值
LinkList ll = new LinkList();
for(int i=0;i<arr.length;i++){
ll.addNode(arr[i]);
}
root=ll;
}
if(isEmpty()==false){
Node node = root.head;
boolean asd=false; //boolean 变量用于逻辑跳转
boolean asd1=false;
boolean asd2=false;
//boolean asd3=false;
for(int i=0; i<V.length();i++){
char c = V.charAt(i);
if(asd==true){
/*
* 若上一个(字符)字母下方没有链表,则创建新的链表,
* 并让上一个字符的向下指针指向创建的链表
* 并设置当前节点为新建链表的键值和当前字符相同的节点,
* 用于下次节点的创建
*/
LinkList l2 = new LinkList();
l2.addNode(c);
node.setDown(l2);
node=l2.findn(c);
if(i==V.length()-1){
node.setValue(V);
node.setMean(M);
node.setK(1);
}
}
if(asd2==true){
/*
*
*/
if(node.getDown()!=null){
if(node.getDown().find(c)==false){
node.getDown().addNode(c);
node=node.getDown().findn(c);
}else{
node=node.getDown().findn(c);
if(node.getDown()==null){
asd=true;
}
}
if(i==V.length()-1){
node.setValue(V);
node.setMean(M);
node.setK(1);
}
}else{
if(asd==false){
LinkList l2 = new LinkList();
l2.addNode(c);
node.setDown(l2);
node=l2.findn(c);
if(i==V.length()-1){
node.setValue(V);
node.setMean(M);
node.setK(1);
}
}
}
asd1=false;
}
if(node.down!=null&&asd1==true){
if(node.getDown().find(c)==false){
/*
* 若在当前节点指针指向的下方链表中 没有查找到和当前字符相同的键值节点
* 则在下方链表中添加新的键值节点;
* 若此新建节点键值 为 传入单词字符串的最后一个字符,则把对应的单词和解释添加到此节点上,
* 若此新建节点键值 不是 传入单词字符串的最后一个字符,则把当前节点置为与传入字符键值相同的的节点
*/
node.getDown().addNode(c);
node=node.getDown().findn(c);
if(i==V.length()-1){
/*为了处理单词词长较短的单词
* 处理单词长度为2的单词,例如: as
*/
node.setValue(V);
node.setMean(M);
node.setK(1);
}
//asd3=true;
/*
* 若在当前节点指针指向的下方链表中 没有查找到和当前字符相同的键值节点
* 则如果当前字符不是传入单词的最后一个单词,则此字符后面的字符都需要创建链表作为当前节点的子树
* 故:这里asd = true
*/
asd=true;
continue;
}else{
/*
* 如果当前节点所指向的下一个链表中存在需要传入的字符,
* 则需要把当前节点置为当前节点所指向的下方链表中键值与当前字符相同的节点
*
*/
node=node.getDown().findn(c);
/*
* 然后跳转到下一个链表继续需找 直到找到或新建的最后一个节点的键值为单词的最后一个字符时,
* 把单词和解释,赋给当前节点的value和mean即可:所以asd2=true
*/
asd2=true;
}
}
/* if(node.down==null&&asd3==true){
LinkList l2 = new LinkList();
l2.addNode(c);
node.setDown(l2);
node=l2.findn(c);
if(i==V.length()-1){
node.setValue(V);
node.setMean(M);
}
} */
while(node.rightChild!=null&&asd==false){ //从第一层根节点开始匹配第一个字符 直到找到和str第一个字符相同的为止,并记下当前节点的位置
if(node.key==c){ //若找到判断键值下方是否有链,若有则在此链进行查找,若此链没有此字符,则在此链添加节点
if(i==V.length()-1){
node.setValue(V);
node.setMean(M);
node.setK(1);
break;
}
if(node.down!=null){ //找到和str第一个字符相同之后,然后开始向下查找
asd1=true;
}else{
asd=true; //找到和str第一个字符相同之后,若下方没有链表, 则跳出循环,以下一个开头的字符创建新的链表
}
break;
}
node=node.getRightChild(); //一直向右查找
if(node.getRightChild()==null){ //处理最后的z开头的字母
if(i==V.length()-1){
node.setValue(V);
node.setMean(M);
node.setK(1);
break;
}
if(node.down!=null){
asd1=true;
}else{
asd=true;
}
break;
}
}
}
}
}
//打印字典树 查找单词
//根据单词长度进行查找,单词中的每一个字母都在一个链表上
public void findPrint(String str, LinkList rot){
Node node=rot.head;
boolean asd=true;
for(int i=0;i<str.length();i++){
char c = str.charAt(i);
while(node!=null){
if(node.key==c){
if(i==str.length()-1){
System.out.println("-查找成功!-"+node.value+"\t--"+node.mean);
asd=false;
}
break;
}
node=node.getRightChild();
}
if(asd==true){
if(node==null){
System.out.println("没找到----->"+str);
break;
}else{
if(node.getDown()!=null){
node=node.getDown().head;
}else{
break;
}
}
}
}
}
//递归主
String sss="";
int m=0;
public String finds(String str, LinkList rot){ //模糊查找
String ss="";
//Node node = find( str, rot);
Node node = fdown(str, rot);
if(node!=null){
if(node.k==1){
ss+=node.value+node.mean+"\n";
}
if(node.getDown()!=null){
findss( node.getDown());
}
}else{
ss="词库中没有!";
}
return ss+sss;
}
//递归次
public void findss( LinkList ll){
Node node = ll.head;
Node node1;
if(node!=null){
while(node!=null){
if(node.k==1){
//System.out.println(node.value+"--"+node.mean);
if(m<18){
sss+=node.value+node.mean+"";
m++;
}else{
break;
}
}
node1=node;
if(node1.getDown()!=null){
findss( node1.getDown());
}
node = node.getRightChild();
}
}
}
public Node fdown(String str, LinkList rot){//模糊查找找
Node node=rot.head;
boolean asd=true;
for(int i=0;i<str.length();i++){
char c = str.charAt(i);
while(node!=null){
if(node.key==c){
if(i==str.length()-1){
//System.out.println("-查找成功!-"+node.value+"\t--"+node.mean);
asd=false;
return node;
}
break;
}
node=node.getRightChild();
}
if(asd==true){
if(node==null){
//System.out.println("没找到----->"+str);
break;
}else{
if(node.getDown()!=null){
node=node.getDown().head;
}else{
break;
}
}
}
}
return null;
}
public Node find(String str, LinkList rot){ //完全查找
Node node=rot.head;
boolean asd=true;
for(int i=0;i<str.length();i++){
char c = str.charAt(i);
while(node!=null){
if(node.key==c){
if(i==str.length()-1){
//System.out.println("-查找成功!-"+node.value+"\t--"+node.mean);
asd=false;
return node;
}
break;
}
node=node.getRightChild();
}
if(asd==true){
if(node==null){
//System.out.println("没找到----->"+str);
break;
}else{
if(node.getDown()!=null){
node=node.getDown().head;
}else{
break;
}
}
}
}
return null;
}
}
package 字典;
public class Test {
Tree tree = null;
/* public static void main(String[] args){
int n=10000;
Test1 tt = new Test1();
Tree tree = new Tree();
Value[] v = new Value[n];
Mean[] m = new Mean[n];
int count = tt.write1(v, m);
// long startMili=System.currentTimeMillis();// 当前时间对应的毫秒数
// System.out.println("开始 "+startMili);
for(int i=0;i<count;i++){ //创建树
tree.creatTree(v[i].value, m[i].mean);
}
// long endMili=System.currentTimeMillis();
//System.out.println("结束 s"+endMili);
// System.out.println("总耗时为:"+(endMili-startMili)+"毫秒");
// for(int i=0;i<count;i++){
// tree.findPrint(v[i].value, tree.root);
// }
// long endMili1=System.currentTimeMillis();
// System.out.println("耗时为:"+(endMili1-endMili)+"毫秒");
//tree.findPrint("america", tree.root);
//tree.findPrintOther("america", tree.root);
System.out.println(tree.finds("art", tree.root));
}
*/
public Test(){
int n=10000;
Test1 tt = new Test1();//从文档中解析出单词和单词的解释
tree = new Tree();
Value[] v = new Value[n];//存放单词
Mean[] m = new Mean[n];//存放意思
int count = tt.write1(v, m);
for(int i=0;i<count;i++){ //创建树
tree.creatTree(v[i].value, m[i].mean);
}
}
public String creat(String str){
return tree.finds(str, tree.root);
}
}
package 字典;
public class Mean {
String mean;
}
package 字典;
public class Value {
String value;
}
package 字典;
import java.awt.BorderLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
@SuppressWarnings("serial")
public class Main extends JFrame implements DocumentListener {
private JTextField textField;
public String s;
static JTextArea area = new JTextArea(20,37);
public Main() {
super("动态实时监听TextField");
//init();
}
public JTextField init(int a) {
textField = new JTextField(a);
//获取与编辑器关联的模型
Document doc = textField.getDocument();
//添加DocumentListener监听器
doc.addDocumentListener(this);
return textField;
}
/**
* 实现DocumentListener接口中insertUpdate方法
* 该方法可以跟踪文本框中输入的内容
*/
Test test = new Test();
public void insertUpdate(DocumentEvent e) {
Document doc = e.getDocument();
try {
s = doc.getText(0, doc.getLength());
//System.out.println(s);
s=S_s(s);
area.setText("");
area.setText(test.creat(s));
} catch (BadLocationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} //返回文本框输入的内容
}
public String S_s(String str){
String s1="";
for(int i=0;i<str.length();i++){
if(str.charAt(i)>=97&&str.charAt(i)<=122){
s1+=String.valueOf(str.charAt(i));
}else{
char c = (char)(str.charAt(i)+32);
s1+=String.valueOf(c);
}
}
//System.out.println(s1); 用于测试
return s1;
}
/**
* 实现DocumentListener接口removeUpdate方法
* 该方法可以跟踪文本框中移除的内容,例如:在文本框中点击Backspace
*/
public void removeUpdate(DocumentEvent e) {
Document doc = e.getDocument();
try {
s = doc.getText(0, doc.getLength());
s=S_s(s);
area.setText("");
area.setText(test.creat(s));
//System.out.println(s);
} catch (BadLocationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} //返回文本框输入的内容
}
/**
* 实现DocumentListener接口changedUpdate方法
* 该方法可以跟踪当文本框中已存在的内容改变时,获取相应的值
*/
public void changedUpdate(DocumentEvent e) {
Document doc = e.getDocument();
try {
s = doc.getText(0, doc.getLength());
//System.out.println(s);
} catch (BadLocationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} //返回文本框输入的内容
}
public static void main(String[] args){
//S_s("ABCCcD");
JFrame frame = new JFrame("小词典");
JPanel jp = new JPanel();
JPanel jp1 = new JPanel();
JPanel jp2 = new JPanel();
JPanel jp3 = new JPanel();
JPanel jp4 = new JPanel();
area.setLineWrap(true); //设置自动换行
area.setEditable(false); //设置不可编辑
jp.add(area);
Main jt = new Main();
JTextField jtt=jt.init(10);
jp3.add(jtt);
frame.setLayout(new BorderLayout(35,40)); //设置整体布局
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置关闭窗体
frame.pack(); //设置窗口自适应
frame.setBounds(700,300, 500,500); //设置窗体位置
frame.setResizable(false); //设置窗体不可改变大小
frame.setVisible(true); //设置窗体可见
frame.add("North",jp3);
frame.add("Center",jp);
frame.add("East",jp1);
frame.add("West",jp2);
frame.add("South",jp4);
//System.out.println(jt.getS());
}
}
测试结果: