数据结构面试整理(3)

7. 快排的partition函数与归并的Merge函数。

7.1快排的partition函数

  在算法导论中咱们对Partition函数的定义是这样的:html

QUICKSORT(A, p, r)//快速排序算法
         if (p < r ){
             q = PARTITION(A, p, r)//分红左右两半,一半不大于A[r], 一半不小于A[r]
             QUICKSORT(A, p, q-1)//递归左半
             QUICKSORT(A, q+1, r) //递归右半
         }
PARTITION(A, p, r)
    x = A[r]//选择最后一个元素做为比较元素
    i = p – 1//这个慢速移动下标必须设定为比最小下表p小1,不然两个元素的序列好比2,1没法交换
    for j = p to r-1{//遍历每一个元素
       if (A[j] <= x){//比较
           i = i + 1//移动慢速下标
           Exchange A[i] with A[j ]//交换
       }
    }
    Exchange A[i+1] with A[r]//交换
    return i + 1//返回分割点

示例图:
这里写图片描述
  在这里,算法是进行单向扫描的,即当咱们选定一个基准的时候,全部比基准小的数据咱们都须要进行swap,这样算法的效率比较低。为此,对算法进行优化,提出双向扫描的Partition算法,其基本思想是对于比基准小的元素并非都执行了swap操做,只有数组右边的一部分的比基准小的元素咱们才执行了swap操做,大大减小了swap操做的次数从而下降时间复杂度。
参考:快排光芒下被忽视的Partition函数
参考:快排中的partition函数java

7.2归并的Merge函数

8. 对冒泡与快排的改进

8.1 对冒泡的改进

    改进1:设置一个标志位,标志位表明在某一个冒泡遍历时候是否发生位置数据的交换,若是没有交换,则代表序列已经排序完成,不然继续排序。减小没必要要的遍历。
    改进2:再设置一个标志位,标志位是序列的某个下标,下标以后的表明已经排序完成,下标以前未排序,则遍历大于标志位时,再也不遍历。减小一次遍历中已排完序的序列的遍历
    改进3:在一次遍历时,同时找出最大值和最小值,从而提升效率。
参考:排序算法(一)——冒泡排序及改进 web

8.2对快排的改进

基准的选取影响快排的效率,通常基准的选取有三种:
    1)固定位置。选序列第一位或者最后一位,算法的导论中提到的就是固定选择最后一位。
    2)随机选取。对于序列中部分有序的状况,若是选择固定位置做为基准,会致使全序列都须要交换位置,这会使得效率低下。所以会采用随机选取数据做为基准。
    3)三数取中。最佳划分是将序列划分红等长的两个子序列,所以提出三数取中的思想。取序列中,下标第一位,下标中间一位,下标最后一位的三个数进行排序,取排序结果中排中间的数据做为基准。(此外,也能够取5个数做为数据的基准。)
参考:三种快速排序以及快速排序的优化
    针对以上三种状况中,三数取中效果最优,可是依然没法解决序列中出现重复状况,对此进行再次优化:
    优化1:当待排序序列的长度分割到必定大小后,使用插入排序。对于很小和部分有序的数组,快排不如插排好。
    优化2与基准值相同的不加入分割。在每一次分割结束后,能够把与基准相等的元素聚在一块儿,继续下次分割时,不用再对与基准相等元素分割。减小重复序列的反复分割
    优化3优化递归操做,快排函数在函数尾部有两次递归操做,咱们能够对其使用尾递归优化。若是待排序的序列划分极端不平衡,递归的深度将趋近于n,而栈的大小是颇有限的,每次递归调用都会耗费必定的栈空间,函数的参数越多,每次递归耗费的空间也越多。优化后,能够缩减堆栈深度,由原来的O(n)缩减为O(logn),将会提升性能。
    这里提一下尾递归,若是一个函数中全部递归形式的调用都出现在函数的末尾,咱们称这个递归函数是尾递归。须要说明的是递归调用必须整个函数体中最后执行的语句且它的返回值不属于表达式的一部分。
尾递归的优势
    1)尾递归经过迭代的方式,不存在子问题被屡次计算的状况
    2)尾递归的调用发生在方法的末尾,在计算过程当中,彻底能够把上一次留在堆栈的状态擦掉,保证程序以O(1)的空间复杂度运行。
    惋惜的是,在jvm中第二点并无被优化。
参考:在Java中谈尾递归–尾递归和垃圾回收的比较算法

9. 二分查找,与变种二分查找

    二分查找的中间下标: mid=low+0.5(highlow)
    二分+插值
    若是序列长度为1000,查找的关键字在10位置上,则仍是须要从500中间开始二分查找,这样会产生屡次无效查询,所以优化的方式就是更改分割的比例,采用三分,四分,分割位置: mid=low+(highlow)(keya[low])/(a[high]key)
    插值查找是根据要查找的关键字的key与查找表中最大最小记录的关键字比较以后的查找算法。
    黄金分割比:用黄金分割比来做为mid值segmentfault

10. 二叉树、B+树、AVL树、红黑树、哈夫曼树。

10.1 二叉树

二叉树的数据结构就很少说了,这里列举一些常见题目
1)求解二叉树的节点
    递归求解:
        a) 树为空,节点数为0
        b) 二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1
2)求二叉树的深度
    递归解法:
        a)若是二叉树为空,二叉树的深度为0
        b)若是二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
3) 先根遍历,中序遍历,后序遍历
    依然递归求解
4)广度优先
    借助队列。
5)将二叉查找树变为有序的双向链表
    要求不能建立新节点,只调整指针。
    递归解法:
        a)若是二叉树查找树为空,对应双向链表的第一个节点和最后一个节点是NULL
        b)若是二叉查找树不为空:
        设置参数flag,表明父节点与子节点的关系。若是修正的是左子树与父节点的关系,则递归返回的是序列最后的节点。
6)求二叉树第K层的节点个数
    递归解法:
        a)若是二叉树为空或者k<1返回0
        b)若是二叉树不为空而且k==1,返回1
        c)若是二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和
7)求二叉树中叶子节点的个数
    递归解法:
        a)若是二叉树为空,返回0
        b)若是二叉树不为空且左右子树为空,返回1
        c)若是二叉树不为空,且左右子树不一样时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数
8)判断二叉树是否是平衡二叉树(AVL树)
    递归解法:
        a)若是二叉树为空,返回真
        b)若是二叉树不为空,若是左子树和右子树都是AVL树而且左子树和右子树高度相差不大于1,返回真,其余返回假
9)由前序遍历序列和中序遍历序列重建二叉树
    二叉树前序遍历序列中,第一个元素老是树的根节点的值。中序遍历序列中,左子树的节点的值位于根节点的值的左边,右子树的节点的值位于根节点的值的右边。
    递归解法:
        a)若是前序遍历为空或中序遍历为空或节点个数小于等于0,返回NULL;
        b)建立根节点。前序遍历的第一个数据就是根节点的数据,在中序遍历中找到根节点的位置,可分别得知左子树和右子树的前序和中序遍历序列,重建左右子树
10)判断是否是彻底二叉树数组

10.2 B-树

参考:浅谈算法和数据结构: 十 平衡查找树之B树
b树定义
3阶B树
数据结构定义:
定义
插入分裂:原则是要知足B树的性质,因此插入的时候要注意关键字的个数在ceil(m/2)-1到m-1个之间。
       若是插入的节点关键字树<m-1说明尚未满,则直接插入。若是插入的关键字数=m-1则说明满了,要进行分裂。取节点中关键字的中间值,以中间值为界一分为二,产生两个新的节点,将中间值做为关键字插入到父节点中(h-1层),因为插入到父节点时可能父节点也是满的,因此要重复上述操做直至根节点,创建一个新的根节点,整个B树增长一层。分裂是B树长高的惟一途径!数据结构

10.3 B+树

相比于B树的主要区别是,非叶子节点只用于检索,而且叶子节点也存在链接,便于顺序遍历。
这里写图片描述jvm

10.4 AVL树

二叉搜索树,在进行构建的时候,树的高度和序列构建时插入的顺序有关,会使得树发生倾斜,最矮的时候是lgN,最高的时候是N,所以平衡二叉搜索树是来解决二叉搜索树不平衡的问题。svg

typedef int Type;

typedef struct AVLTreeNode{
    Type key;                    // 关键字(键值)
    int height;                  //深度
    struct AVLTreeNode *left;    // 左孩子
    struct AVLTreeNode *right;    // 右孩子
}Node, *AVLTree;

AVL树的旋转:若是在AVL树中进行插入或删除节点后,可能致使AVL树失去平衡。这种失去平衡的能够归纳为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)。下面给出它们的示意图:
失去平衡的avl树
失去平衡的avl树2
       1) LL:LeftLeft,也称为”左左”。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,致使”根的左子树的高度”比”根的右子树的高度”大2,致使AVL树失去了平衡。
LL的旋转
       2) LR:LeftRight,也称为”左右”。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,致使”根的左子树的高度”比”根的右子树的高度”大2,致使AVL树失去了平衡。
LR的旋转
       3) RL:RightLeft,称为”右左”。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,致使”根的右子树的高度”比”根的左子树的高度”大2,致使AVL树失去了平衡。
RL的旋转
       4) RR:RightRight,称为”右右”。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,致使”根的右子树的高度”比”根的左子树的高度”大2,致使AVL树失去了平衡。
RR旋转函数

参考:AVL树(三)之 图文解析 和 JAVA语言的实现

10.5 红黑树

       平衡二叉树实现复杂,插入删除效率低,不利于实践,所以提出红黑树。红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。知足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。红黑树的每一个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。
       红黑树的特性:1) 每一个节点或者是黑色,或者是红;2) 根节点是黑色;3) 每一个叶子节点是黑色; [注意:这里叶子节点,是指为空的叶子节点!] 4) 若是一个节点是红色的,则它的子节点必须是黑色的;5) 从一个节点到该节点的子孙节点的全部路径上包含相同数目的黑节点。()确保没有一条路径会比其余路径长出俩倍。于是,红黑树是相对接近平衡的二叉树
参考:红黑树代码实现java
        红黑树的实际应用很是普遍,好比Linux内核中的彻底公平调度器、高精度计时器、ext3文件系统等等,各类语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等。红黑树也是函数式语言中最经常使用的持久数据结构之一,在计算几何中也有重要做用。值得一提的是,Java 8中HashMap的实现也由于用RBTree取代链表,性能有所提高。
红黑树的旋转参考:教你透彻了解红黑树
一些问题
Q1:为何TreeMap采用红黑树而不是二叉查找树?
        其实这个问题就是在问红黑树相对于排序二叉树的优势。咱们都知道排序二叉树虽然能够快速检索,但在最坏的状况下:若是插入的节点集自己就是有序的,要么是由小到大排列,要么是由大到小排列,那么最后获得的排序二叉树将变成链表:全部节点只有左节点(若是插入节点集自己是大到小排列);或全部节点只有右节点(若是插入节点集自己是小到大排列)。在这种状况下,排序二叉树就变成了普通链表,其检索效率就会不好。
Q2:TreeMap、TreeSet 对比 HashMap、HashSet的优缺点?
缺点:
       对于 TreeMap 而言,因为它底层采用一棵“红黑树”来保存集合中的 Entry,这意味这 TreeMap 添加元素、取出元素的性能都比 HashMap (O(1))低:
       当 TreeMap 添加元素时,须要经过循环找到新增 Entry 的插入位置,所以比较耗性能(O(logN))
       当从 TreeMap 中取出元素时,须要经过循环才能找到合适的 Entry,也比较耗性能(O(logN))
优势:
       TreeMap 中的全部 Entry 老是按 key 根据指定排序规则保持有序状态,TreeSet 中全部元素老是根据指定排序规则保持有序状态。

10.6 哈夫曼树

a)路径和路径长度
       若在一棵树中存在着一个结点序列 k1,k2,……,kj, 使得 ki是ki+1 的双亲 1<=i<j ,则称此结点序列是从 k1 到 kj 的路径。从 k1 到 kj 所通过的分支数称为这两点之间的路径长度,它等于路径上的结点数减1.
b)结点的权和带权路径长度
       在许多应用中,经常将树中的结点赋予一个有着某种意义的实数,咱们称此实数为该结点的权,结点的带权路径长度规定为从树根结点到该结点之间的路径长度与该结点上权的乘积。
c)树的带权路径长度
       树的带权路径长度定义为树中全部叶子结点的带权路径长度之和,公式为:
               WPL=wili,  i=1,2,...n
       其中,n表示叶子结点的数目,wi 和 li 分别表示叶子结点 ki 的权值和树根结点到 ki 之间的路径长度。
d)哈夫曼树
       哈夫曼树又称最优二叉树。它是 n 个带权叶子结点构成的全部二叉树中,带权路径长度 WPL 最小的二叉树。
参考:哈夫曼树的基本构建与操做