ps:详情可见《啊哈!算法》第7章
ps:从自己的印象笔记里复制过来的,某些小标号和格式有一点问题,想要看的清晰的可以留言印象笔记分享给你
树
-
定义:树是指任意两个结点间有且只有一条路径的无向图。(或者说树其实就是不包含回路的连通无向图)
-
树的特性:
-
一棵树中的任意两个结点有且仅有唯一的一条路径连通。
-
一棵树中如果有n个结点,那么他一定恰好有n-1条边。
-
在一棵树中加一条边将会构成一个回路。
3.每个结点都有深度,深度是指从根到这个结点的层数(根为第一层)。
二叉树
-
特点:每个结点最多有两个儿子(子树)。
-
满二叉树:每个内部结点都有两个儿子;或者说所有的叶结点都有同样的深度;深度为h且有2h-1个结点的树。
-
完全二叉树:若二叉树的高度为h,除第h层外,其他各层结点数都达到最大个数,第h层从右向左连续缺若干结点,则这个二叉树就是完全二叉树。(如果一个结点有右子结点,那么它一定也有左子结点)
-
完全二叉树中父亲与儿子之间有着神奇的规律,只需要用一个一维数组就可以存储完全二叉树。将完全二叉树从上到下,从左到右编号(如下图),如果一个父结点的编号是k,那么它的左儿子的编号就是2*k,右儿子的编号就是2*k+1。如果已知儿子(左或右)的编号是x,那么它父结点的编号就是x/2。
-
如果一棵完全二叉树有N个结点,那么这个完全二叉树的高度为log2N,简写为logN,即最多有logN层结点。(实际上是logN去掉小数部分并加上1层)
-
完全二叉树最典型的的应用就是堆。
堆——神奇的优先队列
-
最小堆:所有父结点都比子结点小。
-
最大堆:所有父结点都比子结点大。
ps:以下只讨论最小的情况,最大时情况类似。
-
找最小的数的几种情况:
-
每次删除最小的数,并增加一个数:删除根结点(最小的数),将新增的数放在根结点,然后向下调整。
-
每次新增一个数,依旧求最小的数,但不删除:在末尾添加上一个新的结点,将该节点向上调整。插入第N个数的时间复杂度为O(logN)。
-
每次删除最小的数:
(我的想法)可以维护一个队列,计算的时候减去head的下标,这样子计算N次后多占用N单位的空间;或者每次将末尾的结点放到根结点去维护,这样子计算N次后多用了N*logN的时间。(分号前的想法是错误的,因为通过队列维护的时候,本质上改变了树的所有节点的排列顺序,而分号后的想法正是堆排序的一种方法,时间复杂度就为 O(N*logN ) )
-
建立一个最小堆的方法:
-
从空的堆开始,每一次插入一个结点,然后向上调整,插入第 i 个元素的时间是log i,所以插入所有元素的整体时间复杂度是O(N*logN)
-
还有一种更为巧妙的方法,将所有数依次输入进堆,确保每个节点的子节点满足最小堆的特性,所有的叶结点因为没有子节点,所有肯定满足特性,因此,从第 n/2(n/2是最后面一个非叶节点,可以思考一下)个节点开始处理,每次和子节点比较,确保子节点满足最小堆特性,即向下调整。(注意,向下调整是具有延续性的,不是只比较一次,而是比较到确保子节点都满足最小堆特性为止)不太理解为什么该方法的时间复杂度是O(N)?
-
该问题可以看下面的解释:(层数从0开始到h)
-
https://blog.csdn.net/hupenghui1224/article/details/57427045
-
-
堆排序的两种方法:
-
事实上堆排序就是每次删除最小的元素,然后将删除的元素都输出的排序方法。
-
每次删掉一个,然后将末尾的节点放到根结点再向下调整,O(N*logN)。
-
还有一种方法,维护一个最大堆,然后每次将根结点与末尾节点的数交换,将堆的长度减1,并将根结点向下调整,这样最后树就是按从小到大的顺序排列的。这种方法事实上只是节省了空间,时间方面并没有改进。
并查集
-
并查集也称为不相交集数据结构。
-
并查集通过一个一维数组来实现,其本质是维护一个森林。刚开始时,森林里每个点都是孤立的,也可以理解为每个点就是一棵只有一个结点的树,之后通过一些条件,将这些点合成一棵大树。合并的过程就是“认爹”的过程,在“认爹”的过程中,要遵循“靠左”原则和“擒贼先擒王”原则。在每次判断两个结点是否已经在同一棵树中的时候,也要注意必须求其根源,中间父亲结点是不能说明问题的,必须找到其祖宗(根结点),判断两个根结点的祖宗是否是同一个根结点才行。