如今安卓面试,对于数据结构的问题也愈来愈多了,也常常看到别人发的面试题都是问什么红黑树,二叉树查找等,因此咱们虽然不会立刻就会各类难的面试题,但起码树的基础知识仍是要会的,这样才能去进一步学。面试
贴上最近看到的一个介绍图片: 算法
Android技能书系列:数组
Android基础知识bash
Android技能树 — Android存储路径及IO操做小结spa
数据结构基础知识
算法基础知识
本文主要讲关于树的基础知识。
树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其他结点可分为m(m>O)个互不相交的有限集T一、T二、……、 Tm,其中每个集合自己又是一棵树,而且称为根的子树(SubTree)
根据上面的基础知识我画了一个归总的图(这样我就不须要写文字介绍了,啊哈哈):
仍是用本身画的图来讲明:
在Android技能树 — 数组,链表,散列表基础小结中,咱们介绍了线性存储,链式存储,咱们的树能够充分用两者来结合表示。
咱们统一来用上面各类方式来表示下面这个树的存储结构:
在每一个结点中,附设一个指示器指示其双亲结点在数组中的位置。
由于有十一个结点,因此咱们的index为0-10,而后index位置中存储(data + parent的index值),结果以下图所示:
这里咱们发现根据index里面的parent指针,很容易知道父结点,可是好比我问知道了E结点,想知道它的子结点是哪几个,就不知道了,只能经过整个结构的遍历。
固然咱们能够变相的 多加一个指针:
若是咱们又比较关注兄弟结点之间的关系,能够增长一个右兄弟域来体现兄弟关系:
把每一个结点的孩子结点排列起来,以单链表做存储结构,则n 个结点有n个孩子链表,若是是叶子结点,则此单链表为空。而后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
用孩子表示法表示咱们上面的树,结构以下:
因此咱们的结点结构有二种: 1.表头数组的表头结点:
可是这样子对于查找某个结点的孩子,或者找某个结点的兄弟都方便,但彷佛若是要找某个结点的双亲结点就麻烦了。因此咱们能够结合上面讲过的《双亲表示法》
把上面二个方法结合就能够了。
任意一棵树,它的结点的第一个孩子若是存在就是惟一的,它的右兄弟若是存在也是惟一的。 因此设置二个指针,分别指向该结点的第一个孩子和此结点的右兄弟
因此和上面相似,只是咱们存了不一样的二个指针(从方法名字就知道,<孩子><兄弟>法,一个孩子,一个兄弟,二个指针)。
咱们把上面的树按照这种方式实现:
继续画个图来讲明,免得打字了,嘿嘿:
树也是会根据不一样条件,作了分类,咱们来了解一下。
那有序树和无序数的区别在于哪里呢?
若是将树中结点的各子树当作从左至右是有次序的,不能互换,则成为有序树,不然就是无序树
好比咱们只是单纯的表示一个家族的关系:
好比只是说明A的孩子有B跟C,这时候B和C换了位置叶 不要紧,这时候就是无序树。
可是若是咱们这个家族谱是按照年龄来排序(长子,次子),那这时候B和C就不能换位置了,这时候就是有序树。
可是咱们日常说的树一般都是指的有序树,而有序树有不少分类,比较多的就是二叉树。
其实这个相似与咱们之前数学课上要背的数学公式,你们能够本身画个二叉树,而后试着上面的公式比对下,是否是正确。
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中全部结点,使得每一个结点被访问依次且仅被访问一次。
单单看这个图,其实换成我,我也看不懂规律,可是咱们只须要懂得其中的规则就行。
伪代码:
遍历(结点对象 t){
if( t == null){
return;
}
//第一步,实现某个业务操做,好比咱们是打印结点字符串。
print(t)
//第二步,递归方式继续调用该方法遍历左孩子
遍历(t.左孩子)
//第三步,递归方式继续调用该方法遍历右孩子
遍历(t.右孩子)
}
复制代码
咱们看到由于print在其余方法的前面,因此叫前序遍历。咱们如今传入根结点到这个方法,而后依次打印而且递归,就会发现,就是图上的顺序。
伪代码:
遍历(结点对象 t){
if( t == null){
return;
}
//第一步,递归方式继续调用该方法遍历左孩子
遍历(t.左孩子)
//第二步,实现某个业务操做,好比咱们是打印结点字符串。
print(t)
//第三步,递归方式继续调用该方法遍历右孩子
遍历(t.右孩子)
}
复制代码
咱们发现只要把咱们的打印语句放在中间,就是中序遍历了。
伪代码:
遍历(结点对象 t){
if( t == null){
return;
}
//第一步,递归方式继续调用该方法遍历左孩子
遍历(t.左孩子)
//第二步,递归方式继续调用该方法遍历右孩子
遍历(t.右孩子)
//第三步,实现某个业务操做,好比咱们是打印结点字符串。
print(t)
}
复制代码
咱们发现只要把咱们的打印语句放在最后,就是后序遍历了。
一棵深度为k,且有 2^(k+1) - 1 个节点的二叉树称为满二叉树,这种树的特色是每一层上的节点数都是最大节点数。
而在一棵二叉树中,除最后一层外,若其他层都是满的,而且最后一层或者是满的,或者是在右边缺乏连续若干节点,则此二叉树为彻底二叉树。
这块知识不少,后期补上。
这块知识不少,后期补上。
n个结点的二叉链表中含有n+1(2n-(n-1)=n+1)个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")。
这里必定要说明一个知识点:什么是前驱和后继。
网上不少人都是对这个解释太过于简单以致于不少人理解错误,好比:
假设咱们如今有这个一个二叉树:
好比双向链表就是有前驱和后继。那咱们单纯看这棵树是看不出来的,咱们要先把树按照某个遍历方式的时候,把它用这种链表形式摆列,而后才能知道某个结点的前驱和后继是什么,好比上面的图咱们用中序遍历,咱们获得的是:HDIBJEAFCG,这时候 I 的前继是D,后继是B。
咱们在二叉树存储结构中,有二个指针指向它的二个子结点。
可是可能只有一个子结点,或者没有子结点,这样这个空的指针存储就浪费了,咱们能够在这个指针里面存这个结点的前驱或者后继结点的指针。
可是这时候又有一个问题,就是咱们不知道这个点目前到底放的是前驱的仍是左子结点的指针,因此咱们还须要一个参数来讲明当前这个位置放的是哪一个的指针。
咱们把二叉树补充为一个满二叉树,而后相应的填入一个一维数组便可。
二叉树每一个结点最多又二个孩子,因此为它设计一个数据域和二个指针域。
改进于二叉链表,增长父节点的指引,能更好地实现节点间的访问
本文并无写完,内容太多,后面再陆续补上去。哪里写错了,欢迎指出。。。谢谢。
参考:
《大话数据结构》
《维基百科》