数据挖掘,机器学习

1,html

start() 和 run()

调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,仍是在主线程里执行。node

2,python

HashMap和Hashtable的区别

HashMap和Hashtable都实现了Map接口,但决定用哪个以前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度算法

1) sychronized意味着在一次仅有一个线程可以更改Hashtable。就是说任何线程要更新Hashtable时要首先得到同步锁,其它线程要等到同步锁被释放以后才能再次得到同步锁更新Hashtable。编程

2) Fail-safe和iterator迭代器相关。若是某个集合对象建立了Iterator或者ListIterator,而后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程能够经过set()方法更改集合对象是容许的,由于这并无从“结构上”更改集合。可是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。bootstrap

3) 结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。api

三、Python如何定义一个私有变量数组

经常看到一些 python 变量或者方法 以__开头,其实这表示是私有方法和变量。
例如:安全

class Person:网络

    def __init__(self,name):

        self.__name =name

__name就是私有方法。

四、介绍RNN

递归神经网络(RNN)简介

DNN

http://blog.csdn.net/aws3217150/article/details/50768453

在此以前,咱们已经学习了前馈网络的两种结构——多层感知器卷积神经网络,这两种结构有一个特色,就是假设输入是一个独立的没有上下文联系的单位,好比输入是一张图片,网络识别是狗仍是猫。可是对于一些有明显的上下文特征的序列化输入,好比预测视频中下一帧的播放内容,那么很明显这样的输出必须依赖之前的输入, 也就是说网络必须拥有必定的”记忆能力”。为了赋予网络这样的记忆力,一种特殊结构的神经网络——递归神经网络(Recurrent Neural Network)便应运而生了。网上对于RNN的介绍多不胜数,这篇《Recurrent Neural Networks Tutorial》对于RNN的介绍很是直观,里面手把手地带领读者利用python实现一个RNN语言模型,强烈推荐。为了避免重复做者 Denny Britz的劳动,本篇将简要介绍RNN,并强调RNN训练的过程与多层感知器的训练差别不大(至少比CNN简单),但愿能给读者必定的信心——只要你理解了多层感知器,理解RNN便不是事儿:-)。

用途:

RNNs已经被在实践中证实对NLP是很是成功的。如词向量表达、语句合法性检查、词性标注等。在RNNs中,目前使用最普遍最成功的模型即是LSTMs(Long Short-Term Memory,长短时记忆模型)模型,该模型一般比vanilla RNNs可以更好地对长短时依赖进行表达,该模型相对于通常的RNNs,只是在隐藏层作了手脚。

语言模型与文本生成(Language Modeling and Generating Text)

机器翻译(Machine Translation)

语音识别(Speech Recognition)

图像描述生成 (Generating Image Descriptions)

  和卷积神经网络(convolutional Neural Networks, CNNs)同样,RNNs已经在对无标图像描述自动生成中获得应用。将CNNs与RNNs结合进行图像描述自动生成。

http://network.chinabyte.com/269/13936269.shtml

 

五、STL简介

STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称,

 

STL的代码从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),几乎全部的代码都采用了模板类和模版函数的方式,这相比于传统的由函数和类组成的库来讲提供了更好的代码重用机会。在C++标准中,STL被组织为下面的13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>和<utility>。 

六、Trie树

字典树(Trie)能够保存一些字符串->值的对应关系。基本上,它跟 Java 的 HashMap 功能相同,都是 key-value 映射,只不过 Trie 的 key 只能是字符串。
  Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,与 Trie 中保存了多少个元素无关。Hash 表号称是 O(1) 的,但在计算 hash 的时候就确定会是 O(k) ,并且还有碰撞之类的问题;Trie 的缺点是空间消耗很高。
  至于Trie树的实现,能够用数组,也能够用指针动态分配,我作题时为了方便就用了数组,静态分配空间。
      Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不只限于字符串),因此常常被搜索引擎系统用于文本词频统计。它的优势是:最大限度地减小无谓的字符串比较,查询效率比哈希表高。
      Trie的核心思想是空间换时间。利用字符串的公共前缀来下降查询时间的开销以达到提升效率的目的。
Trie树的基本性质能够概括为: 
(1)根节点不包含字符,除根节点意外每一个节点只包含一个字符。
(2)从根节点到某一个节点,路径上通过的字符链接起来,为该节点对应的字符串。 
(3)每一个节点的全部子节点包含的字符串不相同。
Trie树有一些特性:
1)根节点不包含字符,除根节点外每个节点都只包含一个字符。
2)从根节点到某一节点,路径上通过的字符链接起来,为该节点对应的字符串。
3)每一个节点的全部子节点包含的字符都不相同。
4)若是字符的种数为n,则每一个结点的出度为n,这也是空间换时间的体现,浪费了不少的空间。
5)插入查找的复杂度为O(n),n为字符串长度。

 

Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',所以根结点中数组next下标为'a'-97的值不为NULL,其余同理,构建的Trie树如图所示,红色结点表示在该处能够构成一个单词。

 

 七、红黑树

 

R-B Tree简介

    R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每一个节点上都有存储位表示节点的颜色,能够是红(Red)或黑(Black)。

红黑树的特性:
(1)每一个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每一个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)若是一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的全部路径上包含相同数目的黑节点。

注意
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
(02) 特性(5),确保没有一条路径会比其余路径长出俩倍。于是,红黑树是相对是接近平衡的二叉树。

红黑树示意图以下:

红黑树的应用

红黑树的应用比较普遍,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率很是之高。
例如,Java集合中的TreeSetTreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是经过红黑树去实现的。

 

红黑树的时间复杂度和相关证实

红黑树的时间复杂度为: O(lgn)

 八、快排的非递归实现

 其实就是用栈保存每个待排序子串的首尾元素下标,下一次while循环时取出这个范围,对这段子序列进行partition操做

 /**使用栈的非递归快速排序**/

template < typename  Comparable>
void  quicksort2(vector<Comparable> &vec, int  low, int  high){
     stack< int > st;
     if (low<high){
         int  mid=partition(vec,low,high);
         if (low<mid-1){
             st.push(low);
             st.push(mid-1);
         }
         if (mid+1<high){
             st.push(mid+1);
             st.push(high);
         }
         //其实就是用栈保存每个待排序子串的首尾元素下标,下一次while循环时取出这个范围,对这段子序列进行partition操做
         while (!st.empty()){
             int  q=st.top();
             st.pop();
             int  p=st.top();
             st.pop();
             mid=partition(vec,p,q);
             if (p<mid-1){
                 st.push(p);
                 st.push(mid-1);
             }
             if (mid+1<q){
                 st.push(mid+1);
                 st.push(q);
             }      
         }
     }
}
九、mergeSort(归并排序)

举例

无序数组[6 2 4 1 5 9]

先看一下每一个步骤下的状态,完了再看合并细节

第一步 [6 2 4 1 5 9]原始状态

第二步 [2 6] [1 4] [5 9]两两合并排序,排序细节后边介绍

第三步 [1 2 4 6] [5 9]继续两组两组合并

第四步 [1 2 4 5 6 9]合并完毕,排序完毕

输出结果[1 2 4 5 6 9]

 1 static void merge(int[] unsorted, int first, int mid, int last, int[] sorted)
 2         {
 3             int i = first, j = mid;
 4             int k = 0;
 5             while (i < mid && j < last)
 6                 if (unsorted[i] < unsorted[j])
 7                     sorted[k++] = unsorted[i++];
 8                 else
 9                     sorted[k++] = unsorted[j++];
10 
11             while (i < mid)
12                 sorted[k++] = unsorted[i++];
13             while (j < last)
14                 sorted[k++] = unsorted[j++];
15 
16             for (int v = 0; v < k; v++)
17                 unsorted[first + v] = sorted[v];
18         }
19 
20         static void merge_sort(int[] unsorted, int first, int last, int[] sorted)
21         {
22             if (first + 1 < last)
23             {
24                 int mid = (first + last) / 2;
25                 Console.WriteLine("{0}-{1}-{2}", first, mid, last);
26                 merge_sort(unsorted, first, mid, sorted);
27                 merge_sort(unsorted, mid, last, sorted);
28                 merge(unsorted, first, mid, last, sorted);
29             }
30         }
31 
32         static void Main(string[] args)
33         {
34             int[] x = { 6, 2, 4, 1, 5, 9 };
35             int[] sorted = new int[x.Length];
36             merge_sort(x, 0, x.Length, sorted);
37             for (int i = 0; i < sorted.Length; i++)
38             {
39                 if (x[i] > 0)
40                     Console.WriteLine(x[i]);
41             }
42             Console.ReadLine();
43         }

十、随机森林

http://www.cnblogs.com/maybe2030/p/4585705.html

  随机森林顾名思义,是用随机的方式创建一个森林,森林里面有不少的决策树组成,随机森林的每一棵决策树之间是没有关联的。在获得森林以后,当有一个新的输入样本进入的时候,就让森林中的每一棵决策树分别进行一下判断,看看这个样本应该属于哪一类(对于分类算法),而后看看哪一类被选择最多,就预测这个样本为那一类。

    在创建每一棵决策树的过程当中,有两点须要注意 - 采样与彻底分裂。首先是两个随机采样的过程,random forest对输入的数据要进行行、列的采样。

   随机森林是一个最近比较火的算法,它有不少的优势:

  •     在数据集上表现良好
  •     在当前的不少数据集上,相对其余算法有着很大的优点
  •     它可以处理很高维度(feature不少)的数据,而且不用作特征选择
  •     在训练完后,它可以给出哪些feature比较重要
  •     在建立随机森林的时候,对generlization error使用的是无偏估计
  •     训练速度快
  •     在训练过程当中,可以检测到feature间的互相影响
  •     容易作成并行化方法
  •     实现比较简单

  做为新兴起的、高度灵活的一种机器学习算法,随机森林(Random Forest,简称RF)拥有普遍的应用前景,从市场营销到医疗保健保险,既能够用来作市场营销模拟的建模,统计客户来源,保留和流失,也可用来预测疾病的风险和病患者的易感性。最初,我是在参加校外竞赛时接触到随机森林算法的。最近几年的国内外大赛,包括2013年百度校园电影推荐系统大赛、2014年阿里巴巴天池大数据竞赛以及Kaggle数据科学竞赛,参赛者对随机森林的使用占有至关高的比例。此外,据个人我的了解来看,一大部分红功进入答辩的队伍也都选择了Random Forest 或者 GBDT 算法。因此能够看出,Random Forest在准确率方面仍是至关有优点的。

  那说了这么多,那随机森林究竟是怎样的一种算法呢?

  若是读者接触过决策树(Decision Tree)的话,那么会很容易理解什么是随机森林。随机森林就是经过集成学习的思想将多棵树集成的一种算法,它的基本单元是决策树,而它的本质属于机器学习的一大分支——集成学习(Ensemble Learning)方法。随机森林的名称中有两个关键词,一个是“随机”,一个就是“森林”。“森林”咱们很好理解,一棵叫作树,那么成百上千棵就能够叫作森林了,这样的比喻仍是很贴切的,其实这也是随机森林的主要思想--集成思想的体现。“随机”的含义咱们会在下边部分讲到。

  其实从直观角度来解释,每棵决策树都是一个分类器(假设如今针对的是分类问题),那么对于一个输入样本,N棵树会有N个分类结果。而随机森林集成了全部的分类投票结果,将投票次数最多的类别指定为最终的输出,这就是一种最简单的 Bagging 思想。

 

十一、Bagging和Boosting 概念及区别

 

 Bagging和Boosting都是将已有的分类或回归算法经过必定方式组合起来,造成一个性能更增强大的分类器,更准确的说这是一种分类算法的组装方法。即将弱分类器组装成强分类器的方法。

首先介绍Bootstraping,即自助法:它是一种有放回的抽样方法(可能抽到重复的样本)。

一、Bagging (bootstrap aggregating)(引导程序汇集)

Bagging即套袋法,其算法过程以下:

A)从原始样本集中抽取训练集。每轮从原始样本集中使用Bootstraping的方法抽取n个训练样本(在训练集中,有些样本可能被屡次抽取到,而有些样本可能一次都没有被抽中)。共进行k轮抽取,获得k个训练集。(k个训练集之间是相互独立的)

B)每次使用一个训练集获得一个模型,k个训练集共获得k个模型。(注:这里并无具体的分类算法或回归方法,咱们能够根据具体问题采用不一样的分类或回归方法,如决策树、感知器等)

C)对分类问题:将上步获得的k个模型采用投票的方式获得分类结果;对回归问题,计算上述模型的均值做为最后的结果。(全部模型的重要性相同)

 

二、Boosting(推动)

其主要思想是将弱分类器组装成一个强分类器。在PAC(几率近似正确)学习框架下,则必定能够将弱分类器组装成一个强分类器。

关于Boosting的两个核心问题:

1)在每一轮如何改变训练数据的权值或几率分布?

经过提升那些在前一轮被弱分类器分错样例的权值,减少前一轮分对样例的权值,来使得分类器对误分的数据有较好的效果。

2)经过什么方式来组合弱分类器?

经过加法模型将弱分类器进行线性组合,好比AdaBoost经过加权多数表决的方式,即增大错误率小的分类器的权值,同时减少错误率较大的分类器的权值。

而提高树经过拟合残差的方式逐步减少残差,将每一步生成的模型叠加获得最终模型。

 

三、Bagging,Boosting两者之间的区别

Bagging和Boosting的区别:

1)样本选择上:

Bagging:训练集是在原始集中有放回选取的,从原始集中选出的各轮训练集之间是独立的。

Boosting:每一轮的训练集不变,只是训练集中每一个样例在分类器中的权重发生变化。而权值是根据上一轮的分类结果进行调整。

2)样例权重:

Bagging:使用均匀取样,每一个样例的权重相等

Boosting:根据错误率不断调整样例的权值,错误率越大则权重越大。

3)预测函数:

Bagging:全部预测函数的权重相等。

Boosting:每一个弱分类器都有相应的权重,对于分类偏差小的分类器会有更大的权重。

4)并行计算:

Bagging:各个预测函数能够并行生成

Boosting:各个预测函数只能顺序生成,由于后一个模型参数须要前一轮模型的结果。

 

四、总结

这两种方法都是把若干个分类器整合为一个分类器的方法,只是整合的方式不同,最终获得不同的效果,将不一样的分类算法套入到此类算法框架中必定程度上会提升了原单一分类器的分类效果,可是也增大了计算量。

下面是将决策树与这些算法框架进行结合所获得的新的算法:

1)Bagging + 决策树 = 随机森林

2)AdaBoost + 决策树 = 提高树

3)Gradient Boosting + 决策树 = GBDT

十二、SVM

SVM(支持向量机)主要用于分类问题,主要的应用场景有字符识别、面部识别、行人检测、文本分类等领域。

一般SVM用于二元分类问题,对于多元分类一般将其分解为多个二元分类问题,再进行分类。下面咱们首先讨论一下二元分类问题。

支持向量机三大理论要素:

最大化间距、核函数、对欧理论。

1三、word2vec

http://www.cnblogs.com/peghoty/p/3857839.html

word2vec 是 Google 于 2013 年开源推出的一个用于获取 word vector 的工具包,它简单、高效,所以引发了不少人的关注。因为 word2vec 的做者 Tomas Mikolov 在两篇相关的论文 [3,4] 中并无谈及太多算法细节,于是在必定程度上增长了这个工具包的神秘感。一些按捺不住的人因而选择了经过解剖源代码的方式来一窥究竟

有word2vec训练的词向量库,一个句子分词后,把词都换成对应的向量输入

文本情感分类项目,文本向量用tf-idf这种有什么问题没有?

有,不能捕获到上下文之间的联系。之后尝试用doc2vec这种。

1四、BPTT

循环神经网络(RNN)反向传播算法(BPTT)

LSTM(Long Short-Term Memory)是长短时间记忆网络,是一种时间递归神经网络,适合于处理和预测时间序列中间隔和延迟相对较长的重要事件。

http://www.cnblogs.com/yymn/articles/4969851.html

循环神经网络的反向传播算法其实只是BP算法的一个简单变体而已。

 

1五、问答系统倒排索引

问答系统,有200W个FAQ,如何用分类模型作分类(FAQ常见问题解答)

了解搜索引擎吗?

用倒排索引,把FAQ的问题分词,每一个词对应多个FAQ。新来的query分词,每一个词对应的FAQ拉出来。再在这个里面作分类。

1六、hadoop&spark

分布式文件系统。数据在哪里计算就在哪里,移动数据变成了移动计算。更高效

map、reduce分别分配资源,能够细粒度控制资源占用状况,有利于超大任务平稳正常运行。

http://www.cnblogs.com/tgzhu/p/5818374.html

与许多专有的大数据处理平台不一样,Spark创建在统一抽象的RDD之上,使得它能够以基本一致的方式应对不一样的大数据处理场景,包括MapReduce,Streaming,SQL,Machine Learning以及Graph等。这即Matei Zaharia所谓的“设计一个通用的编程抽象(Unified Programming Abstraction)。这正是Spark这朵小火花让人着迷的地方。

要理解Spark,就需得理解RDD。

RDD是什么?

RDD,全称为Resilient Distributed Datasets,是一个容错的、并行的数据结构,可让用户显式地将数据存储到磁盘和内存中,并能控制数据的分区。同时,RDD还提供了一组丰富的操做来操做这些数据。在这些操做中,诸如map、flatMap、filter等转换操做实现了monad模式,很好地契合了Scala的集合操做。除此以外,RDD还提供了诸如join、groupBy、reduceByKey等更为方便的操做(注意,reduceByKey是action,而非transformation),以支持常见的数据运算。

一般来说,针对数据处理有几种常见模型,包括:Iterative Algorithms,Relational Queries,MapReduce,Stream Processing。例如Hadoop MapReduce采用了MapReduces模型,Storm则采用了Stream Processing模型。RDD混合了这四种模型,使得Spark能够应用于各类大数据处理场景。

RDD做为数据结构,本质上是一个只读的分区记录集合。一个RDD能够包含多个分区,每一个分区就是一个dataset片断。RDD能够相互依赖。若是RDD的每一个分区最多只能被一个Child RDD的一个分区使用,则称之为narrow dependency;若多个Child RDD分区均可以依赖,则称之为wide dependency。不一样的操做依据其特性,可能会产生不一样的依赖。例如map操做会产生narrow dependency,而join操做则产生wide dependency。

(1)窄依赖与宽依赖

Spark之因此将依赖分为narrow与wide,基于两点缘由。

首先,narrow dependencies能够支持在同一个cluster node上以管道形式执行多条命令,例如在执行了map后,紧接着执行filter。相反,wide dependencies须要全部的父分区都是可用的,可能还须要调用相似MapReduce之类的操做进行跨节点传递。

其次,则是从失败恢复的角度考虑。narrow dependencies的失败恢复更有效,由于它只须要从新计算丢失的parent partition便可,并且能够并行地在不一样节点进行重计算。而wide dependencies牵涉到RDD各级的多个Parent Partitions。下图说明了narrow dependencies与wide dependencies之间的区别:

本图来自Matei Zaharia撰写的论文An Architecture for Fast and General Data Processing on Large Clusters。图中,一个box表明一个RDD,一个带阴影的矩形框表明一个partition。

RDD如何保障数据处理效率?

RDD提供了两方面的特性persistence和patitioning,用户能够经过persist与patitionBy函数来控制RDD的这两个方面。RDD的分区特性与并行计算能力(RDD定义了parallerize函数),使得Spark能够更好地利用可伸缩的硬件资源。若将分区与持久化两者结合起来,就能更加高效地处理海量数据。例如:

input.map(parseArticle _).partitionBy(partitioner).cache()

partitionBy函数须要接受一个Partitioner对象,如:

val partitioner = new HashPartitioner(sc.defaultParallelism)

RDD本质上是一个内存数据集,在访问RDD时,指针只会指向与操做相关的部分。例如存在一个面向列的数据结构,其中一个实现为Int的数组,另外一个实现为Float的数组。若是只须要访问Int字段,RDD的指针能够只访问Int数组,避免了对整个数据结构的扫描。

RDD将操做分为两类:transformation与action。不管执行了多少次transformation操做,RDD都不会真正执行运算,只有当action操做被执行时,运算才会触发。而在RDD的内部实现机制中,底层接口则是基于迭代器的,从而使得数据访问变得更高效,也避免了大量中间结果对内存的消耗。

在实现时,RDD针对transformation操做,都提供了对应的继承自RDD的类型,例如map操做会返回MappedRDD,而flatMap则返回FlatMappedRDD。当咱们执行map或flatMap操做时,不过是将当前RDD对象传递给对应的RDD对象而已。例如:

def map[U: ClassTag](f: T => U): RDD[U] = new MappedRDD(this, sc.clean(f))

这些继承自RDD的类都定义了compute函数。该函数会在action操做被调用时触发,在函数内部是经过迭代器进行对应的转换操做:

private[spark]
class MappedRDD[U: ClassTag, T: ClassTag](prev: RDD[T], f: T => U)
  extends RDD[U](prev) {

  override def getPartitions: Array[Partition] = firstParent[T].partitions

  override def compute(split: Partition, context: TaskContext) =
    firstParent[T].iterator(split, context).map(f)
}

RDD对容错的支持

支持容错一般采用两种方式:数据复制或日志记录。对于以数据为中心的系统而言,这两种方式都很是昂贵,由于它须要跨集群网络拷贝大量数据,毕竟带宽的数据远远低于内存。

RDD天生是支持容错的。首先,它自身是一个不变的(immutable)数据集,其次,它可以记住构建它的操做图(Graph of Operation),所以当执行任务的Worker失败时,彻底能够经过操做图得到以前执行的操做,进行从新计算。因为无需采用replication方式支持容错,很好地下降了跨网络的数据传输成本。

不过,在某些场景下,Spark也须要利用记录日志的方式来支持容错。例如,在Spark Streaming中,针对数据进行update操做,或者调用Streaming提供的window操做时,就须要恢复执行过程的中间状态。此时,须要经过Spark提供的checkpoint机制,以支持操做可以从checkpoint获得恢复。

针对RDD的wide dependency,最有效的容错方式一样仍是采用checkpoint机制。不过,彷佛Spark的最新版本仍然没有引入auto checkpointing机制。

总结

RDD是Spark的核心,也是整个Spark的架构基础。它的特性能够总结以下:

  • 它是不变的数据结构存储
  • 它是支持跨集群的分布式数据结构
  • 能够根据数据记录的key对结构进行分区
  • 提供了粗粒度的操做,且这些操做都支持分区
  • 它将数据存储在内存中,从而提供了低延迟性

 

1七、Kmeans优缺点

Kmeans:

优势:

简单易实现

缺点:

可能收敛于局部最小值(对初始k个聚类中心的选择敏感),在大规模数据集上收敛较慢

适用数据类型:数值型数据

度量聚类效果的指标:

SSE(sum of squared error, 偏差平方和),SSE值越小表示数据点越接近于他们的质心,聚类效果也越好

改进方法:簇划分

二分k均值算法:请参考博客:http://blog.csdn.net/u013593585/article/details/51263980

 

1八、kd-tree

k-d树[1]  (k-dimensional树的简称),是一种分割k维数据空间的数据结构。主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。K-D树是二进制空间分割树的特殊的状况。

相关文章
相关标签/搜索