文章目录
- 一、C语言能干大事
- 1. C语言下Huffman树的计算过程分析
- 2. C语言下Huffman树的编程
- 二、C#语言也不赖
- 1. C#下Huffman类的设计
- 2. C#中界面设计
- 3. 建立测试数据并显示Huffman树
- 4. 输入任意一组数据,完成构造Huffman树
- 三、JavaScript语言不爱听了
- 1. JavaScript下Huffman类的设计
- 2. 把权重数据显示在一个表格里
- 3. 为表格添加命令按钮
- 4. 编写添加一行的程序
- 5. 编写删除一行的程序
- 6. 用表格中的数据生成Huffman树表
- 7. 显示Huffman树表中的树
一、C语言能干大事
1. C语言下Huffman树的计算过程分析
例1 有权重集合分别是:5、29、7、8、14、23、3、11,计算Huffman树。
这个题目的计算过程如下:
(1)首先是把数据填写在以下表格里:
这个在编程中一定注意:空白格子里是NULL,这点不要搞错。
首先是寻找到两个最小权重的结点,找到的是第7、1号结点,权重合计是8,我们先标记这两个结点s=1(代表已经处理过了),并生成第9号结点,权重是8,并让第7、1号结点的父结点是9,第9号结点的左、右孩子分别是第7、1结点,就是如下表:
重复上面的过程,从头寻找s=0的结点里、权重最小的两个结点、就是第3、4号结点,权重合计是15,这样,标记这两个结点s=1,并生成第10号结点,权重是15,而第7、8的父结点是第10号结点,第10号结点的左右孩子是第3、4号结点,就是下表:
重复这个过程,处理到第15个结点,使其权重合计为100,就是:
最终这个树的计算到此结束,在这样的表中计算出的过程以及结果,就是我们下面编程的主要依据。
2. C语言下Huffman树的编程
针对前面介绍的表格,用C语言描述这样的表就是:
struct Huffman
{int W,Parent,lChild,rChild,S;
};
对Huffman树,由于它是正则二叉树,所以有n个权重数据则必然有2*n-1个树结点。
有了表的C语言定义,则首先是寻找未标记的、权重最小的结点,如果这个表是H,则全部代码就是:
int FindMinNode(struct Huffman *H,int N)
{int i,W,Node;W=100;Node=0;if(H==NULL) return -1;for(i=0;i<N;i++){if(H[i].W>0&&H[i].W<W&&H[i].S==0){W=H[i].W;Node=i;}}H[Node].S=1;return Node;
}
第6至第12行,是一个典型的数组中求最小值的算法,在第13行,则必须标记这个结点的S=1,说明该结点已经使用过,最后则返回这个结点的编号。
有了这个函数后,首先要对H表进行两次求最小值的操作,就是:
min1=FindMinNode(H,N); min2=FindMinNode(H,N);
如第i行是新增的一个结点,则让该表第i行的权重为:
H[i].W=H[min1].W+H[min2].W;
然后,就是设置这个结点的左右孩子结点为min1、min2,就是:
H[i].Parent=-1; H[i].lChild=min1; H[i].rChild=min2;
最后,就是设置编号min1、min2的结点的父结点是第i个结点。
H[min1].Parent=i; H[min2].Parent=i;
全部就是:
int ConstructHuffmanTree(struct Huffman *H,int n)
{int i;int min1,min2,N;if(n==0) return -1;if(H==NULL) return -2;N=2*n-1;for(i=n;i<N;i++){min1=FindMinNode(H,N);min2=FindMinNode(H,N);H[i].W=H[min1].W+H[min2].W;H[min1].Parent=i;H[min2].Parent=i;H[i].Parent=-1;H[i].lChild=min1;H[i].rChild=min2;}return 0;
}
注意这个函数第8行,它是从第n个结点开始循环的。
有了这两个函数后,用一个测试的main()来测试它们:
main()
{int n,N,i,*D;struct Huffman *H;n=8;//组织scanf()输入N=2*n-1;D=(int *)malloc(sizeof(int)*n);//组织scanf()输入。D[0]=5;D[1]=29;D[2]=7;D[3]=8;D[4]=14;D[5]=23;D[6]=3;D[7]=11;H=(struct Huffman *)malloc(sizeof(struct Huffman)*N);for(i=0;i<N;i++) {H[i].W=0;H[i].Parent=0;H[i].lChild=0;H[i].rChild=0;H[i].S =0;}for(i=0;i<n;i++)H[i].W =D[i];ConstructHuffmanTree(H,n);printf("ID\tW\tP\tL\tR\n");for(i=0;i<N;i++)printf("%d\t%d\t%d\t%d\t%d\n",i,H[i].W,H[i].Parent,H[i].lChild,H[i].rChild);
}
运行结果如下图所示:
Huffman树的C语言程序到此为止。
二、C#语言也不赖
1. C#下Huffman类的设计
仅仅针对前面C语言中的计算方法,设计一个类来完成计算过程,这个类就是:
class Huffman{public int W, pChild, lChild, rChild, s;public Huffman(){W = pChild = lChild = rChild = -1;s =0; }public Huffman(int Weight, int PChild, int LChild, int RChild, int Select){W = Weight; PChild = pChild; lChild = LChild; rChild = RChild; s = Select; }}
2. C#中界面设计
有了这个类以后,我们可以在界面设计上拖进两个命令按钮button1,button2,然后再拖进一个treeView1控件和imageList1控件,然后开始以下设置:
(1) 选择imageList1控件,找到属性images,加入文件夹MyIcon中的两个小图标;
(2) 选择treeView1控件,让imageList属性选中imageList1控件;
(3) 选择treeView1控件,让SelectImageIndex=1(打开书的图标);
(4) 选择treeView1控件,让ImageIndex=0(关闭书的图标);
(5) 选择button1控件,修改text属性为:”简单测试”;
(6) 选择button2控件,修改text属性为:”结束”
3. 建立测试数据并显示Huffman树
设有权重数据为:5,29,7,8,14.23.3.11,注意权重数据和必须是100。首先是编写从Huffman类数组中取得最小权重结点的函数,这个函数编写在Form1程序中,鼠标双击button1,注意在button1_click()前面补充这样的函数,就是:
在这个函数中,H是Huffman结点对象数组,而N是这个数组的数据个数,如果是上例,则N=15。
现在开始补充代码如下:
int FindMinNode(Huffman[] H,int N){ int i,W,Node;W=100;Node=0;if(H==null) return -1;for(i=0;i<N;i++){if(H[i].W>0&&H[i].W<W&&H[i].s==0){W=H[i].W;Node=i;}}H[Node].s=1;return Node;}
这个函数同C的几乎没什么差别,同样返回这个结点的下标。有这个函数后,就可以构造Huffman树,跟随着上面的函数,继续输入就是:
int ConstructHuffmanTree(Huffman[] H,int n){int i;int min1,min2,N;if(n==0) return -1;if(H==null) return -2;N=2*n-1;for(i=n;i<N;i++){min1=FindMinNode(H,N);min2=FindMinNode(H,N);H[i].W=H[min1].W+H[min2].W;H[min1].pChild=i;H[min2].pChild=i;H[i].pChild=-1;H[i].lChild=min1;H[i].rChild=min2;}return 0;}
其基本算法和C语言的也没什么差别。
但这个Huffman类的树是不能显示在treeView1中的,控件treeView1只能显示TreeNode类型的树,所以要按这个表格的内容构造TreeNode类对象的树。
在treeView1控件中,显示的结点个数同Huffman类的结点个数是一致的,而每个TreeNode类结点的Text内容则是权重,于是紧跟着上面的函数,写以下函数就是:
void dTree(Huffman[] H){int i,n,a,b;n = H.Count();TreeNode[] T = new TreeNode[n];for(i=0;i<n;i++)T[i]=new TreeNode(H[i].W.ToString());for (i = 0; i <n; i++){a = H[i].lChild;
b = H[i].rChild;if (a >= 0) T[i].Nodes.Add(T[a]);if (b >= 0) T[i].Nodes.Add(T[b]);}treeView1.Nodes.Add(T[n-1]); }
最后,就是补充button1下的程序,按实验数据有:
这组数据一共8个,所以Humman树一共将有2*8-1=15个结点,鼠标双击button1,写进以下程序:
private void button1_Click(object sender, EventArgs e){Huffman[] H = new Huffman[15];H[0] = new Huffman(5, -1, -1, -1, 0);H[1] = new Huffman(29, -1, -1, -1, 0);H[2] = new Huffman(7, -1, -1, -1, 0);H[3] = new Huffman(8, -1, -1, -1, 0);H[4] = new Huffman(14, -1, -1, -1, 0);H[5] = new Huffman(23, -1, -1, -1, 0);H[6] = new Huffman(3, -1, -1, -1, 0);H[7] = new Huffman(11, -1, -1, -1, 0);for (int i = 8; i < 15; i++) H[i] = new Huffman();ConstructHuffmanTree(H, 8);dTree(H);}
到此,简单的Huffman树的测试程序设计完成。最后在button2_click()中补充代码:this.close()
;让程序能正常结束。
4. 输入任意一组数据,完成构造Huffman树
为完成这个要求,首先要在界面设计中再补充控件,有:
(1) 补充listBox1控件;
(2) 补充button3控件,修改Text属性为:“输入确认”;
(3) 补充button4控件,修改Text属性为:”生成Huffman树”;
(4) 补充button5控件,修改Text属性为:”清除”;
(5) 补充textBox1控件;
有了上述控件后,我们首先设计操作过程是:
在textBox1控件中输入数据,按下button3按钮“输入确认”,则输入的数据显示在listBox1控件中,直到所有数据输入完成,于是这样的操作要求的程序就是:
private void button3_Click(object sender, EventArgs e){listBox1.Items.Add(textBox1.Text); }
listBox1控件是个数据容器,你可以不断追加进很多数据,这些数据全部可以保存在这个控件中。直到按下button4”生成Huffman树”,才开始计算。所以button4的程序就是:
private void button4_Click(object sender, EventArgs e){int n = listBox1.Items.Count;Huffman[] H = new Huffman[2*n-1];for (int i = 0; i < 2 * n - 1; i++)H[i] = new Huffman();for(int i=0;i<n;i++){int w=int.Parse( listBox1.Items[i].ToString()) ;H[i].W = w; }ConstructHuffmanTree(H, n);dTree(H);}
表7中第3行,相当于从listBox1控件中获得数据项的个数,在第9行,就是逐个取得每个数据项,并转换成int类型、赋值给Huffman类对象数组中每个对象的权重W。最后按同样的方式计算并显示在treeView1中。
注意这个程序实际很不理想,没判断输入的数据是否有负值、是否是非数字的字符串、是否和为100等等,这些详细的判断留给同学们自己去完成。
对于button5”清除”的编程非常简单,就是:
private void button5_Click(object sender, EventArgs e)
{listBox1.Items.Clear();treeView1.Nodes.Clear();textBox1.Text = "";
}
程序运行效果:
三、JavaScript语言不爱听了
1. JavaScript下Huffman类的设计
针对前面C语言中的计算方法,设计一个类来存储数据,如果你使用的Ext系统,则强烈建议直接使用Ext.data.ArrayStore类对象直接构造它,这样的好处是显示在表格里非常方便,于是说明对象tstore为:
var tstore = new Ext.data.ArrayStore({data:[[0,5 ,-1, -1,-1,0],[1,29,-1, -1,-1,0],[2,7 ,-1, -1,-1,0],[3,8 ,-1, -1,-1,0],[4,14,-1, -1,-1,0],[5,23,-1, -1,-1,0],[6,3 ,-1, -1,-1,0],[7,11,-1, -1,-1,0]],fields: [{name: 'id' ,type:'int'},{name: 'w' ,type:'int'},{name: 'pChild',type:'int'},{name: 'lChild',type:'int'},{name: 'rChild',type:'int'},{name: 's' ,type:'int'}]});
运行效果:
这个程序非常直观,它说明有一个表名称是tstore,其中右6列,data下说明了数据,在field下说明了各个列的名称、分别是id,w,pChild,lChild,rChild,s这样的6列,如同下表:
2. 把权重数据显示在一个表格里
但这个表用来显示在界面上则非常不好看,我们需要一个友好的界面,所谓友好的界面就是说用汉字显示、而不是奇怪符号显示的界面,如:
我们一般把表2成为逻辑表,表3称为显示表,它们两者之间的关系应该是一一对应的,唯独列名称不一样。造成这样的结果,主要原因是程序计算过程中,我们期望变量名都是英语字符,这样非常方便编程,但显示,则必须是汉字的表头。
把这两者统一起来:就是说让“结点编号”指向逻辑表的“id”列,要用到Ext.grid.ColumnModel类型的对象,如该对象的名称是colM,则定义如下:
var colM=new Ext.grid.ColumnModel([
{列对象1属性},
{列对象2属性},
…
{列对象n属性}
]);
注意JavaScript中、一旦出现[ ]则代表是数组,所以这些个列对象也就相当于数组中的各个元素。
一个典型的设置就是:如将表格第1列显示为“结点编号”、并和数据逻辑表tstore中的id关联起来、并在该列上提供排序、而且该列可以编辑,则就是:
{header:"结点编号",dataIndex:'id',sortable:true,editor:new Ext.form.TextField()
}
实际就是设置了列对象的4个属性。这仅仅是为第一列,如果是所有列,则:
var colM=new Ext.grid.ColumnModel([{header:"结点编号",dataIndex:'id',sortable:true,editor:new Ext.form.TextField()},{header:"结点权重",dataIndex:'w',sortable:true,editor:new Ext.form.TextField()},{header:"父结点编号",dataIndex:'pChild',sortable:true,editor:new Ext.form.TextField()},{header:"左结点编号",dataIndex:'lChild',sortable:true,editor:new Ext.form.TextField()},{header:"右孩子编号",dataIndex:'rChild',sortable:true,editor:new Ext.form.TextField()},{header:"选择状态",dataIndex:'s',sortable:true,editor:new Ext.form.TextField()}]);
注意在最后一个列对象定义完后、第37行后没有”,”,这点初学者一定要记着。
有了数据、有了列定义以后,就可以显示表格了,显示表格用的是Ext.grid.EditorGridPanel类对象,就是按下面的语句:
var grid=new Ext.grid.EditorGridPanel({表对象属性});
含义是定义了一个表对象grid,而该表的显示属性则由表对象属性中说明。如:
var grid=new Ext.grid.EditorGridPanel({renderTo:"hello",title:"Huffman树",height:400,width:620,cm:colM,store:tstore});
其中属性:
- renderTo:说明这个表格显示在哪里,在表5中是在”hello”中,这个hello实际是用HTML语言定义的一个分区,就是,有了这个分区,grid就将显示在该区域里;
- title:表示该表格的标题;
- height:表示该表格的高度;
- width:表示该表格的宽度;
- cm:表的列定义,需要一个列对象来说明各个列,如表4;
- store:表示该表显示的数据来自哪里,在我们的程序中,数据来自表1定义的tstore。
将上述程序代码合并到一个函数里就是:
function fun(){var tstore = new Ext.data.ArrayStore({data:[[0,5 ,-1, -1,-1,0],[1,29,-1, -1,-1,0],[2,7 ,-1, -1,-1,0],[3,8 ,-1, -1,-1,0],[4,14,-1, -1,-1,0],[5,23,-1, -1,-1,0],[6,3 ,-1, -1,-1,0],[7,11,-1, -1,-1,0]],fields: [{name: 'id' ,type:'int'},{name: 'w' ,type:'int'},{name: 'pChild',type:'int'},{name: 'lChild',type:'int'},{name: 'rChild',type:'int'},{name: 's' ,type:'int'}]});//表格上列名称显示、以及表格上数据处理方式。var colM=new Ext.grid.ColumnModel([{header:"结点编号",dataIndex:'id',sortable:true,editor:new Ext.form.TextField()},{header:"结点权重",dataIndex:'w',sortable:true,editor:new Ext.form.TextField()},{header:"父结点编号",dataIndex:'pChild',sortable:true,editor:new Ext.form.TextField()},{header:"左结点编号",dataIndex:'lChild',sortable:true,editor:new Ext.form.TextField()},{header:"右孩子编号",dataIndex:'rChild',sortable:true,editor:new Ext.form.TextField()},{header:"选择状态",dataIndex:'s',sortable:true,editor:new Ext.form.TextField()}]);//表格开始显示数据。var grid=new Ext.grid.EditorGridPanel({renderTo:"hello",title:"Huffman树",height:400,width:620,cm:colM,store:tstore});}
Ext.onReady(fun);
有了表7的程序,我们打开m.html,这个程序是一个调用Ext类库的模板程序,把表7的程序补充到这个程序里的fun()函数里即可执行,结果将显示一个表。
注意第72行不是这个函数中的语句,而是一条独立的JavaScript语句。现在就可以以m.html为模板文件,加入表7的程序。
3. 为表格添加命令按钮
如果一个程序仅仅能计算一组数据,那么这个程序实际没什么意义,为此,我们要给表格上加入三个命令按钮,就是:添加、删除、生成Huffman树,使其能添加一行、删除一行,并能根据新的数据生成Huffman树。
给表格上添加三个命令按钮,就是给grid对象上再说明一个工具条、并显示出三个命令按钮。也就是说给该对象的tbar属性说明命令按钮对象的属性说明,就是:
tbar:[{按钮对象1属性},{按钮对象2属性},…{按钮对象n属性} ]
对一个命令按钮属性,比如产生一个叫”新增”的命令按钮,则可以是这样来说明:
{ text: "新增", iconCls: "add", handler: function(){ //以下写进新增加一行的代码} }
text:表明这个按钮上显示的字符,如VB6中Command控件上的Caption属性;
iconCls:这个按钮上要显示的图标;
handler:function() { }:就是按下这个按钮后的响应程序;
同VB6的设计思路是一致的,但这里完全没有VB6那样的编程环境,所以逐个属性只能自己在键盘上输入。
现在,我们要在表格对象grid上的工具栏tbar中说明三个按钮,则就是一下的程序:
var grid=new Ext.grid.EditorGridPanel({renderTo:"hello",title:"Huffman树",height:400,width:620,cm:colM,store:tstore,tbar: [ { text: "新增", //iconCls: "add", handler: function(){ //以下写进新增加一行的代码} }, { text: "生成Huffman树", //iconCls: "refresh", handler: function(){ //以下写进生成新Huffman树的代码} }, { text: "删除一行", //iconCls: "delete", handler: function(){ //以下写进表格中删除一行的代码} } ]});
注意同表5的对比。新增加的三个命令按钮,在执行h1.html的时候则就会看到。当然,它们目前是什么都不会执行,仅仅是有三个命令按钮。
运行效果:
执行h0.html和h1.html,看看这两个网页的差异。
4. 编写添加一行的程序
编写这个程序,实际就是为表8的第15行处补充代码,补充这样的代码,首先要获得表格中当前有几行,这个数据要从tstore中去获得,就是:
var n=tstore.getCount();
然后构造一个默认的数据行,用的是:
var defData={id:n,pChild:-1,lChild:-1,rChild:-1,s:0};
就是说id的值是n,pChild=lChild=rChild=-1,s=0,让权重w为空白。
最后,构造一个第n行的记录,并插入到tstore中去,就是:
var r=new tstore.recordType(defData,n);
tstore.insert(tstore.getCount(),r);
一旦数据插入tstore,则在grid中会自动显示结果。完整的函数就是:
Handler: function(){ var n=tstore.getCount();var defData={id:n,pChild:-1,lChild:-1,rChild:-1,s:0};var r=new tstore.recordType(defData,n);tstore.insert(tstore.getCount(),r);}
运行效果:
5. 编写删除一行的程序
删除一行数据,首先要选中表中的一行,如果没有选中,则要给出提示。
var record = grid.getSelectionModel().selection.record;
如果正常获得了选中的行,则要找到这一行数据是在tstore中的哪一行,就是在对象tstore中按record来查找:
var index = tstore.indexOf(record);
这样,index就是表格中对应的行号,有了行号,就可以:
tstore.removeAt(index);
来删除这一行。
但一定要注意:假如你没有选中表格中的任何一行,执行上面的过程则会发生错误,这个是非常重要的事情,否则删除操作会让程序经常崩溃。为此,这段程序要加入到JavaScript语言的try过程中,就是
try
{
//这里加入要尝试着做的程序段落
}
catch(err)
{
//如果尝试着做的程序段无法正常执行,则在这里给出错误的提示, err对象中有
//错误的描述。
}
注意这个语句,在C#中也有同样的语句,这个语句非常重要,经常会出现在一些需要尝试着做的工作中,诸如在涉及到硬件访问、用户操作不确定的场合下,按这个语句的过程来执行程序,则是必须的。有了这个过程,则完整的删除程序就是:
handler: function(){ try{var record = grid.getSelectionModel().selection.record; var index = tstore.indexOf(record); tstore.removeAt(index); }catch(err){Ext.MessageBox.alert("删除错误提示","未选中任何行,不能删除.");return;}
}
注意表10的程序是添加在表8的第32行处,这个程序在这里是完整的,但它是对象grid说明的一个组成部分。
现在运行h2.html,则可以测试加入一行数据、并删除一行数据了。
6. 用表格中的数据生成Huffman树表
要生成一个Huffman树,则首先要从表格中计算各个结点的权重值,为此,又首先要获得表格中未用过结点的最小权重结点,为此,编写函数如下:
function FindMinNode(astore){var i,n,w=100,m=-1;n=astore.getCount();for(i=0;i<n;i++){if(astore.getAt(i).get('s')==0&&w>astore.getAt(i).get('w')){w=astore.getAt(i).get('w');m=i;}}astore.getAt(m).set('s',1);return m;}
这个函数的参数是astore,函数是逐行读这个对象中s=0的那些行,找到最小权重行的下标值、并存在m中,查找完后,在第12行,把该行的s列修改为1,然后返回这个下标值。这个算法的原理和C#是一致的。
有这个函数后,就可以在“生成Huffman树”按钮下的响应程序里补充这些代码,构造一个Huffman树,就是:
handler: function(){ var n=tstore.getCount();for(i=n;i<2*n-1;i++) {var min1=FindMinNode(tstore);var min2=FindMinNode(tstore);tstore.getAt(min1).set('pChild',i);tstore.getAt(min2).set('pChild',i);var nw=tstore.getAt(min1).get('w')+tstore.getAt(min2).get('w');var newNode={id:i,w:nw,pChild:-1,lChild:min1,rChild:min2,s:0};var r=new tstore.recordType(newNode,i);tstore.insert(tstore.getCount(),r);}dTree(tstore);}
在第6、7行,每次获得对象tstore中两个最小权重结点;
在第8、9行,修改tstore表中第min1、min2两行的pChild列,使这两个行的父结点等于新的这一行(第i行);
在第10行,则是读第min1、min2行的权重求和、结果在nw中;
在第11行,按新的权重nw构造一个结点,其左孩子是min1,右孩子是min2,s=0;
在第12、13行,则是把这一行新的记录插入到tstore中,同时也会显示在grid对象中;
最后,在第15行调用dTree(tstore)函数,显示在一个treeView对象中。
注意这个函数实际是在表9的第23行处补充的。
7. 显示Huffman树表中的树
显示一个树,在Ext系统中首先要构造Ext.tree.TreeNode类型的结点,这里和C#中的概念是一样的,Ext.tree.TreeNode类型的结点个数和Huffman表tstore中的行数是一致的,为此,就是这样的过程来构造这些Ext.tree.TreeNode类型的结点并显示出来:
function dTree(astore)
{var i,n;var TA=new Array();n=astore.getCount();for(i=0;i<n;i++)TA[i]=new Ext.tree.TreeNode({id:astore.getAt(i).get('id'),text:astore.getAt(i).get('w')}); for(i=0;i<n;i++){var a = astore.getAt(i).get('lChild'); var b = astore.getAt(i).get('rChild');if (a >= 0) TA[i].appendChild(TA[a]);if (b >= 0) TA[i].appendChild(TA[b]);}treeView1=new Ext.tree.TreePanel({renderTo:"Tree",root:TA[n-1],width:300,height:400});
}
这个函数的算法和C#中的完全一致,没有差别,只不过一些语句修改成JavaScript语句而已。
到这里,一个完整的Huffman树生成、显示程序完成了。
一定保留好这个程序的所有代码,这样的代码在随后的数据库课程中使用的非常广泛,它不仅仅是一个简单的树的问题,很多应用都需要类似的界面。而这个程序里最关键的数据对象tstore,在数据库中则要用Ajax技术来从数据库服务器中、通过一种叫WebServices的程序系统来获得,其中,相当多数的WebServices程序是由C#来编写的,由Ext执行结果也要通过C#的WebServices作用到数据库服务器中去。一个华丽的数据库应用系统,必须是在华丽的外观下实现的,而C#、Ext系统则是这个系统中最重要的组成部分。
随后,在数据库的课程中,我们将在VS2008中、使用C#、JavaScript Ext、SQLServer系统来完成这样的开发,所以这样的基础知识是非常关键的。