数据结构面试整理(3)

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

7.1快排的partition函数

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

 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函数

7.2归并的Merge函数

8. 对冒泡与快排的改进

8.1 对冒泡的改进

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

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值

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树的主要区别是,非叶子节点只用于检索,并且叶子节点也存在连接,便于顺序遍历。
这里写图片描述

10.4 AVL树

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

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 最小的二叉树。
参考:哈夫曼树的基本构建与操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值