牛客网算法与数据结构试题总结

排序

 

 

快速排序最坏情况:

最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为 ,最终其时间复杂度为O(n^2)。

 

快排的阶段性排序结果的特点是,第i趟完成时,会有i个以上的数出现在它最终将要出现的位置,即它左边的数都比它小,它右边的数都比它大。

 

为实现快速排序算法,待排序序列宜采用的存储方式是顺序存储

快速排序中查询操作用的较多,而顺序存储适用于频繁查询时使用; 链式存储适用于频繁地插入、删除、更新元素时使用。

1、顺序存储方式:顺序存储方式就是在一块连续的存储区域一个接着一个的存放数据。一般采用数组或结构数组来描述。 

2、链接存储方式:链接存储方式比较灵活,不要求逻辑上相邻的节点在物理位置上相邻,一个节点的引用字段往往指向下一个节点的存放位置,比如链表;

3、索引存储方式:索引存储方式是采用附加的索引表的方式来存储节点信息的一种存储方式。索引表由若干索引项组成。索引存储方式中索引项的一般形式为(关键字、地址); 

4、散列存储方式:散列存储方式是根据节点0的关键字直接计算出该节点的存储地址的一种存储方式。​

 

选择排序法:

每一次选出未比较的序列中最小值,与前面的元素互换位置;记得是互换!!

 

基数排序是一种借助于多关键字排序的思想对单逻辑关键字进行排序的方法,不需要进行记录关键字间的比较。

 

基数排序分配和回收的趟数只与元素长度有关,与元素个数无关,通过对元素各位数进行比较进行排序。

基数排序是通过“分配”和“收集”过程来实现排序。

1)首先根据个位数值(只看个位)来排序:253 674 924 345 627

2)再看十位(只看十位数值大小)来排序:924 627 345 253 674

3)最后看百位:253 345 627 674 924。最后的结果排序为 253 345 627 674 924,需要3趟。

 

常见的线性时间非比较类排序:

基数排序桶排序(用运算来确定排序顺序)

 

关于与数组初始状态无关的排序算法:

1、算法复杂度与初始状态无关;

2、元素总比较次数与初始状态无关;

3、元素总移动次数与初始状态无关。

4、排序趟数与初始状态无关

 

元素的移动次数与关键字的初始排列次序无关:基数排序

元素的比较次数与初始序列无关:选择排序

算法的时间复杂度与初始序列无关:堆排序,归并排序,选择排序

 

折半插入排序

折半插入排序是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。 所以,很明显比较的次数减少了。

折半插入排序基本思想和直接插入排序一样,区别在于寻找插入位置的方法不同,折半插入排序采用折半查找法来寻找插入位置。

 

待排序元素规模较小时,选取冒泡排序算法效率最高:

递归本身需要处理的时间就比普通长。数据量越大,时间复杂度的优势就越明显。如果数据量小,时间复杂度的优势就不明显。

 

插入排序特殊情况:

升序排序时,当最后一个元素是最小的,要把前面所有元素都往后移动一个位置,再把最后一个元素放到第一个位置。即所有元素都不在正确的位置上。

 

堆排序

当从一个最小堆中删除一个元素时,需要把堆尾元素填补到堆顶位置,然后再按条件把它逐层向下调整到合适位置。

​为了便于比较同一问题的不同算法,通常以基本操作重复执行「次数」作为算法的时间量度。

当待排序的元素数目很多时,比较的次数是影响时间复杂度的主要因素。 

 

快速排序

递归次数与初始数据的排列顺序有关,与每次划分后得到的分区处理顺序无关

 

在含有n个关键字的小根堆(堆顶元素最小)中,关键字最大的记录有可能存储在大于[n/2]位置上(下标从1开始,):

小根堆中最大的数一定是放在叶子节点上,堆本身是个完全二叉树,完全二叉树的叶子节点的位置大于n/2。

下标是从1开始的。那么

                                          1

                                     2       3

                                4     5   6    7

                              ...............n

n不论是左子树还是右子树,n的父结点一定是 [n/2],(中括号的取整规则:正数向下取整),从 [n/2] + 1 开始一直到 n 都是叶子结点,叶子结点就可能会是最大值。

 

插入排序、选择排序、起泡排序都是相邻元素之间的操作,顺序比较元素,不存在跨越式比较,与存储方式无关。​比如插入排序,每插入一个元素,顺序与已排序元素比较;比如选择排序,每选择一个元素,顺序与剩下的元素比较。

​而希尔排序和堆排序存在元素的跨越式比较,​涉及到非相邻元素的交换,利用了顺序存储的随机访问特性。​由于链表不支持随机访问,因而非相邻元素之间交换困难,导致算法复杂度上升。

 

希尔排序的思想:

先将待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成),分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。

 

直接插入排序

 

外部排序:

待排序文件较大,内存一次性放不下,需存放在外部介质中。

10TB的数据不可能一次全部载入内存,传统的排序方法就不适用了,需要用到外部排序。外排序采用分治思想,即先对数据分块,对块内数据进行排序,然后采用归并排序的思想进行排序,得到数据的一个有序序列。

 

外部存储上有3110400个记录,做6路平衡归并排序,计算机内存工作区能容纳400个记录,排序好所有记录,每次计算机内存填满400,则3110400个记录要填 3110400/400 =  7776次,n路归并m 次的次数为n^m ,6^m = 7776,m=5。即一共需要作5趟归并排序。

 

某一趟结束后未必能选出一个元素放在其最终位置上的是直接插入排序

  1. 堆排序每趟总能选出一个最大值或者最小值位于根节点。
  2. 冒泡排序总是两两比较选出一个最大值位于最终位置。
  3. 快排选出的枢轴在一趟排序中就位于了它最终的位置
  4. 直接插入排序不一定会位于最终的位置,因为不确定后面插入的元素对于前面的元素是否产生影响。

 

改良的冒泡排序:当一轮循环中没有交换就结束排序。

 

CPU的大端模式(big endian)和小端(little endian)模式:

假设从内存地址0x0000 开始有以下数据:0x12 0x34 0xab 0xcd,读取一个地址为0x0000 的四个字节变量,若字节序为big-endian,则读出结果为0x1234abcd;若字节序位little-endian,则读出结果为 0xcdab3412。如果我们将0x1234abcd 写入到以0x0000 开始的内存中,则Little endian 和Big endian 模式的存放结果如下:
地址               0x0000 0x0001 0x0002 0x0003
big-endian         0x12   0x34   0xab   0xcd
little-endian      0xcd   0xab 0x34   0x12

 

唯一确定一颗二叉树

必须知道这一颗二叉树的中序遍历顺序。知道前序+中序或者知道后序+中序,可以唯一确定这颗二叉树。

 

大顶堆:每个节点的值都不小于自己两个左右子节的完全二叉树。
若要求原地排序,即不产生额外的空间,堆顶元素与最后一个元素交换。每轮输出堆顶元素后,以堆中最后一个元素代替之,再将新的顶点元素不断与其子节点中大于该元素的较大者交换,直到该元素大于其左右两个子节点,或成为叶子节点。此时将剩余元素调整成一个新的大顶推。

将整数数组(7-6-3-5-4-1-2)按照堆排序的方式原地进行升序排列,第一轮排序过程:

                7                         2                          6                        6
          /  \                      /  \                      /  \                     /  \
            6     3     ==>    6     3     ==>      2     3    ==>      5    3
       /   \    /  \             /  \    /               /  \    /             /  \    / 
       5   4  1   2           5   4  1    7       5   4  1            2   4  1      7
由此得出第一轮结束后的顺序是:6,5,3,2,4,1,7。

完整排序过程:

 

 

哈夫曼树

当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优(最佳)二叉树”,也叫“赫夫曼树”或“哈夫曼树”。

对于给定的有各自权值的 n 个结点,构建哈夫曼树:

  1. 在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的
  2. 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
  3. 重复 1 和 2 ,直到所有结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。

 

表分为顺序表和链表

A、顺序表的特点就是随机存取(直接存取),所以访问节点的时间复杂度为O(1)。

B、插入一个节点,那么这个节点之后的所有节点都分别要向后移动一个,所以时间复杂度为O(n)。

C、删除一个节点,那么后面的所有节点都需要向前移动一个,所以时间复杂度为O(n)。

D、常见的排序方法中最快的也是O(n),即使是桶排序,也不可能达到O(1)。

 

排序方法按照排序过程中所涉及的存储器可分为内排序和外排序两种,其中,待排序记录全部存放在计算机内存中进行排序的过程,称为内排序;而由于待排序记录的数量很大使得排序过程中也需要对外存设备进行访问的排序过程,称为外排序

内、外排序强调的是所涉及的存储器的不同,内排序又可以分为非线性时间比较类排序和线性时间非比较类排序。非线性时间比较类排序是基于关键字比较的,而线性时间非比较类排序是不基于关键字排序的。

 

非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。

线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。

 

常见的非线性时间比较类排序:

  • (直接)插入排序:将待排序数字逐渐插入到已排好序的元素序列中,需要比较关键字
  • 快速排序:通过一趟排序将待排序数列分割为两部分,一部分比基准值大,另一部分比基准值小,需要关键字的比较。
  • 选择排序(简单选择排序):从待排序序列中选择一个最大(或最小)的元素放在序列的首位置,直到所有的序列排列完成。需要关键字比较。
  • 归并排序:将序列先拆分为多个子序列,使子序列有序,再逐步合并,直至整个序列有序。需要关键字比较。
  • 冒泡排序:相邻元素逐个比较做交换。
  • 希尔排序:缩小增量排序,属于插入排序的一种。
  • 堆排序:大根堆的要求是每个节点的值都不大于其父节点的值,小根堆相反。

 

一趟排序结束后不一定能够选出一个元素放在其最终位置上的是希尔排序:

A、堆排序可以把最大的或者最小的放在堆顶,所以是可以在一趟排序之后将其中一个放在最终位置的。

B、冒泡排序在一趟排序之后把最大的放在了最右边

C、快速排序的过程是选出一个作为基准,大的放在基准的右边,小的放在基准的左边,然后递归实现,所以基准可以放在最终位置

D、希尔排序属于插入排序,而插入排序是不能保证在第一次排序后放在最终位置。

 

每个结点的值都大于或等于其左右孩子结点的值称为大顶堆是具有以下性质的完全二叉树

l  将无序序列构建成一个堆,根据升序需求选择大顶堆;

l  将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

l  重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

希尔排序每次是对划分的子表进行排序,得到局部有序的结果,所以不能保证每一趟排序结束都能确定一个元素的最终位置。

二路归并排序每趟对子表进行两两归并从而得到若干个局部有序的结果,但无法确定最终位置。

设一组初始记录关键字序列为(25,50,15,35,80,85,20,40,36,70),其中含有5个长度为2的有序子表,则用归并排序的方法对该记录关键字序列进行一趟归并后的结果:

原序列共分成5段有序,第1段:25,50.第2段:15,35.第3段:80,85.第4段:20,40.第5段:36,70.第1段与第2段归并,结果为:15,25,35,50.第3段与第4段归并结果为:20,40,80,85.第5段落单了,则原样照抄.

 

二叉查找树的查找效率与二叉树的树型有关:

在单枝树时其查找效率最低:当二叉查询树变成一条链表效率最差,此时深度为n。

所以有AVL平衡树:限制节点深度差不超过1,避免产生链表一般的树。

 

快速排序挖坑法实现:

 

首先选定基准元素Pivot,并记住这个位置index,这个位置相当于一个“坑”,并且设置两个指针 left 和 right ,指向数列的最左和最有两个元素。

 

接下来从right指针开始,把指针所指向的元素和基准元素做比较。如果比pivot大,则right指针向左移动;如果比pivot小,则把right所指向的元素填入坑中。

4>2,所以4的位置不变,将right指针左移继续比较,right右边黄色的区域代表着大于基准元素的区域。1<2,所以把1填入基准元素所在位置,也就是坑的位置。这时候,元素1本来所在的位置成为了新的坑。同时,left向右移动一位。


此时,left左边绿色的区域代表着小于基准元素的区域,接下来切换到left指针进行比较。如果left指向的元素小于pivot,则left指针向右移动;如果元素大于pivot,则把left指向的元素填入坑中。

 


下面按照这个思路继续排序:

 

这时候,把之前的pivot元素,也就是2放到index的位置。此时数列左边的元素都小于2,数列右边的元素都大于2,这一轮交换终告结束。


 

查找

二叉排序树查找算法的平均查找长度,主要取决于树的高度,即与二叉树的形态有关。

相同结点数深度最小的是平衡二叉树,​平衡二叉树的平均查找长度O(logN)。

如果二叉排序树是一个只有右(左)孩子的单支树,其平均查找长度和单链表相同为O(n);

 

当有序表是静态查找表时,宜用顺序表作为其存储结构,而采用二分查找实现其查找操作;

当有序表是动态查找表时,应选择二叉排序树作为其逻辑结构

 

常把第一个最后一个元素作为哨兵,表头设置监视哨,就是将空出来的下标为0的这个元素的值设为Key, 这样我们就不用多次判断 i 是否越界,因为就算静态表中找不到,也会在0位置上配对成功,返回0,从第n个元素往开始前查找该数据元素,监视哨是最后需要比较的元素,减少了越界判断。

 

一次查找的长度指需要比较的关键字次数。平均查找长度=总的查找次数/元素数。使查找成功时的平均查找长度达到最小,所以将经常需要将查找概率大的数据放在顺序表中较前的位置。

 

二分查找:

也称折半查找(Binary Search),它是一种效率较高的查找方法。折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

 

二分查找每次的下标并不是直接取原来,而是会减一;当mid为奇数时,mid/2是向下取整

二分查找可以用二叉判定树:查找不成功的次数不超过判定树的深度(log2n)+1,向下取整。

 

二分查找:

也称折半查找(Binary Search),它是一种效率较高的查找方法。折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

为什么采取顺序存储结构:折半查找需要先对查找的数据集合排序,并且每次要获得数据列表的中间位置,通过数组这种顺序存储结构,只要一次索引就能获得中间值,如果是链式结构,就每次都要从头遍历到中间位置,耗费大量时间。

 

有个长度为12的无重复有序表,按折半查找法进行查找,在表内各元素等概率情况下,查找成功所需的平均比较(三元比较)的次数为:

查找1次成功的结点为:6。查找2次成功的结点为:3,9。查找3次成功的结点为:1,4,7,11。查找4次成功的结点为:2,5,8,10,12。成功查找所有结点的总的比较次数为:1×1+2×2+3×4+4×5=37平均比较次数为37/12。

 

一个长度为32的有序表,若采用二分查找一个不存在的元素,则比较次数最多是6次:

比较次数也就是完全二叉树深度,只含有一个节点的时候深度为1,假设深度为k,则有2^k-1>=n>2^(k-1)-1,其中2^k-1对应深度为k的满二叉树对应节点数,那么等价于2^k>=(n+1)>2^(k-1),不等式两边同时取log,k>=log(n+1)>(k-1),此时k等于log(n+1)向上取整数,因此log(32+1)向上取整为6。

查找不存在的比存在的多比较一次,存在的次数是树的深度5因此不存在的是6。

 

具有12个关键字的有序表,求折半查找平均查找长度

第一层 1个元素 1次

第二层 2个元素 2次

第三层 4个元素 3次

第四层 5个元素 4次

总共查询次数: 1×1+2×2+4×3+5×4=37

平均查询次数为:37/12=3.1

 

在有序线性表A[1, …, 30]上进行二分查找

查找一次成功的节点数为1, 值为15

查找二次成功的节点数为2, 值为7,,23

查找三次成功的节点数为4, 值为3,11,19,27

查找四次成功的节点数为8, 值为1,5,9,13,17,21,25,29

查找五次成功的节点数为15, 值为2,3,4,6,8,10,12,14,16,18,20,22,24,26,28,30

 

 

12个元素的折半查找,所以中间位置30为树的根节点,分出左右子树。其左右子树按照中间为根节点的方式继续递归构造判定树。 遵守左<根<右的规则。 查找成功的平均长度需要统计每个元素的查找次数:

树第一层次数为1,第二层为2*2,第三层为3*4,第四层为5*4,一共为37次 所以平均查找长度为37/12

 

 

斐波纳切查找:

l  平均性能是斐波纳切黄金分割查找更好

l  有序表长度不一定是一个斐波纳切数,可以补齐最大的数目直到长度是斐波纳切数

l  最坏情况下斐波纳切查找性能比折半差 

采用二分查找方法查找长度为n的线性表时,每个元素的平均查找长度为O(log2n):

二分查找方法的思想:将数列按有序化(递增或递减)排列,查找过程中采用跳跃式方式查找,即先以有序数列的中点位置为比较对象,如果要找的元素值小 于该中点元素,则将待查序列缩小为左半部分,否则为右半部分。通过一次比较,将查找区间缩小一半。

  • 最好的情况是:待查关键字刚好位于序列中间,第一次即查到。
  • 最坏的情况是:第一次查找,还剩下n/2个元素需要比较;第二次,还剩n/4……第i次查找,还剩下n/2i个元素需要比较,直到剩余最后一个元素,查找结束。

最后n/2i=1,即n=2i,i为查找的次数(长度),i=log2n,

 

就平均查找速度而言,下列几种查找速度从慢至快的关系:

顺序查找的时间复杂度为o(n)

分块查找的时间复杂度为o(log2n)到o(n)之间

二分查找的时间复杂度为o(log2n)

哈希查找的时间复杂度为o(1)

 

索引分块查找:

类似于图书馆的索引,我们先找到第几楼,再找第几个书架,然后再找书。

分块查找就是先找到目标所在的数据块,然后块内查找。因此跟表中元素个数和块中元素个数都有关。

分块查找时,首先在索引表中进行查找,确定要找的节点所在的块。由于索引表是排序的,因此,对索引表的查找可以采用顺序查找或折半查找;然后,在相应的块中采用顺序查找,即可找到对应的节点。

分块查找是顺序查找和折半查找的折中方案。​​

 

红黑树AVL树

都属于自平衡二叉树

两者查找、插入、删除的时间复杂度相同;

包含n个内部结点的红黑树的高度是o(logn);

TreeMap是一个红黑树的实现,能保证插入的值保证排序

将12个数画成完全二叉树

第一层需要比较1次

第二两个数,每个比较2次

第三层四个数,每个比较3次

第四层五个数,每个比较4次

 

 

顺序存储的线性表不但逻辑上连续,物理上也连续,可以使用顺序查找法。

链式存储的线性表虽然物理上不一定连续,但逻辑上连续(通过额外的指针表示逻辑相邻关系),也可以使用顺序查找法。

二叉搜索树:

若左子树不为空则左子树上所有的值均小于根节点,右子树反之均大于根节点的值。(前一个元素要么大于后面每一个元素要么小于后面每一个元素)

l  满二叉树中,叶子节点个数 = 2 ^ (深度 - 1),即2 ^ (5 - 1) = 16;

l  队列、栈属于线性结构,二叉树属于非线性结构中的树型结构;

 

顺序查找:

目标数据可能在序列的任意位置,平均位置是n/2,所以顺序查找平均时间是n/2

注意平均时间和时间复杂度的区别,时间复杂度只取数量级,略去常数为O(n)

 

顺序查找:

是在一个已知无序(或有序)队列中找出与给定关键字相同的数的具体的位置。原理是让关键字与队列中的数从最后一个开始逐个比较,直到找出与给定关键字相同的数为止,它的缺点是效率低下。顺序查找就是从头到尾一个一个比较,表中内容是否有序无所谓,反正都会浏览一遍直到找到。

 

长度为n的线性顺序查找x,则查找次数可能是1,2,3,...,n次,则和sum为n(n+1)/2
顺序查找时,若第一个即为待查找元素,则查找次数为1,若第二个是待查找元素,则查找次数为2,……

所以查找次数是1+2+3+4+5+....+n=(1+n)*n/2次,平均查找次数为(n+1)/2次。

 

对长度为N的线性表进行顺序查找,在最坏情况下所需要的比较次数为n次

最坏情况是要查找的元素不在线性表内或在线性表尾部。

 

改进顺序查找:

多次查找后,出现次数越多的当然概率越大,查找次数多的数往前放,且元素查找速度快。

 

给定一个整数sum,从有N个有序元素的数组中寻找元素a,b,使得a+b的结果最接近sum:

最快的平均时间复杂度是:O(n)

1、设定两个指针P1、P2,分别指向数组开始和结尾,即P1指向最小值,P2指向最大值;

2、计算 *P1+*P2 的值为 SUM,与 sum 比较,记录它们的差值 DIF 和 SUM,若 SUM<sum,P1++,若SUM>sum,P2--;

3、重复以上过程直到DIF最小

 

 

顺序线性表的长度为30,分成5块,每块6个元素,如果采用分块查找,则其平均查找长度为6.5:

找块:(1+2+3+4+5)/5=3
在块内找元素:   (1+2+3+4+5+6)/6=3.5 
合计:3+3.5=6.5

判断数字能否构成折半查找关键字比较序列

折半查找的判定树是一棵二叉排序树,看其是否满足二叉排序树的要求。关键字比较序列不是要查找的数据的完整序列,只是在查找过程中比较过的关键字的序列。

 

随机给出数组的n个数据,找最大元素需要n-1次比较,找第二大元素需要logn-1次比较(向下取整)

 

使用KMP算法在文本串S中找模式串P,假设S=P={xyxyyxxyx},亦即将S对自己进行匹配:

1、 首先求最大相同前缀后缀长度

 

2、通过“最长相同前缀后缀长度值右移一位,然后初值赋为 -1 ”得到的 next 数组:

模式串

X

Y

X

Y

Y

X

X

Y

X

前缀最大公共元素

0

0

1

2

0

1

1

2

3

next

-1

0

0

1

2

0

1

1

2

求next 数组:计算字符串前缀和后缀最长匹配相同的长度+1

根据前缀最大公共元素表求next 数组:

若第一位的next值为-1,则next值为前一位的前缀最大公共元素;

若第一位的next值为0,则next值为前一位的前缀最大公共元素加1

第一位的next值为0(有的时候为-1),第二位的next值为1,后面求解每一位的next值时,根据前一位进行比较。首先将前一位与其next值对应的内容进行比较,如果相等,则该位的next值就是前一位的next值加上1;如果不等,向前继续寻找next值对应的内容来与前一位进行比较,直到找到某个位上内容的next值对应的内容与前一位相等为止,则这个位对应的值加上1即为需求的next值;如果找到第一位都没有找到与前一位相等的内容,那么需求的位上的next值即为1。


 

算法

贪心算法(贪婪算法):在对问题求解时,总是做出在当前看来是最好的选择。也就是不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解。

 

单源最短路径中的Dijkstra算法

Dijkstra提出按各顶点与源点v间的路径长度的递增次序,生成到各顶点的最短路径的算法。既先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从源点v 到其它各顶点的最短路径全部求出为止。

 

最小生成树的Prim算法:Prim算法基于贪心算法设计,其从一个顶点出发,选择这个顶点发出的边中权重最小的一条加入最小生成树中,然后又从当前的树中的所有顶点发出的边中选出权重最小的一条加入树中,以此类推,直到所有顶点都在树中,算法结束。

 

最小生成树的Kruskal算法:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。

 

广度优先遍历需要借助队列实现。

 

邻接表的结构包括:顶点表;边表(有向图为出边表)。

当采用邻接表存储方式时,在对图进行广度优先遍历时每个顶点均需入队一次(顶点表遍历),故时间复杂度为O(n),

在搜索所有顶点的邻接点的过程中,每条边至少访问一次(出边表遍历),故时间复杂度为O(e),算法总的时间复杂度为O(n+e)。

 

算法的五大特性
输入: 算法具有0个或多个输入
输出: 算法至少有1个或多个输出
有穷性: 算法在有限的步骤之后会自动结束而不会无限循环,每个步骤在可接受的时间内完成
确定性:算法中的每一步都有确定的含义,不会出现二义性
可行性:算法的每一步都是可行的

 

排序时,若不采用计数排序的等空间换时间的方法,则合并m个长度为n的已排序数组的最好时间复杂度为O(mn(logm)):的大小为m,每次调整的时间复杂度为logm,一共需要调整mn次。具体过程:

1.最小堆的初始化:分别将m个数组的第一个数(都是各自数组的最小值)取出建立一个最小堆。

2.取出堆顶元素(目前最小值),放入最终的数组中。并将取出的那个元素原先的数组的下一个数放在堆顶,然后进行一次由上至下(下沉)的堆有序操作。注意:如果有一个数组遍历完,则将堆大小减1。

3.重复执行步骤2直至所有的数组都遍历完。

4.最后将堆中还剩下的m个数据按照堆排序的规则依次放入最终数组中。

整个过程中最耗时的是步骤2的重复。堆的有序化为O(logm),我们需要将所有的元素(m * n个)都遍历出来让堆过滤一遍(就是找到最小值),所以有m * n次步骤2的循环。最终的复杂度为m * n * log(m)。

用常规的非递归方法遍历一个平衡二叉树,所需的时间复杂度和空间复杂度都是O(n)

非递归的访问遍历二叉树的算法中的基本操作是访问结点,则不论按哪一种次序进行遍历,对n个结点的二叉树,其时间复杂度均为O(n)。所需辅助空间为遍历过程中栈的最大容量,即树的深度,最坏情况下为n,则空间复杂度也为O(n)。

 

递归算法复杂度(主定理):

 

 

用邻接表表示图(包含n个定点e条边)时,拓扑排序算法时间复杂度为O(n+e)

对有n个顶点和e条弧的有向图而言,建立求各顶点的入度的时间复杂度为O(e);建零入度顶点栈的时间复杂度为O(n);在拓扑排序过程中,若有向图无环,则每个顶点进一次栈、出一次栈,入度减1的操作在while语句中总共执行e次,所以总的时间复杂度为O(n+e)。

拓扑排序初始参数只有邻接表,所以第一步建立入度数组,因为每1入度对应一条弧,总共e条弧,建立入度数组的复杂度为O(e)。每个节点输出一次,n个节点遍历一次,时间复杂度为O(n)。(使用零入度节点栈的原因是,如果不把零入度节点入栈,每次输出时都要遍历节点。建立此栈,只需遍历一次。)然后节点入度减1的操作,也是一条弧对应一次,e条弧总共O(e)。以上总计O(n+2e)即O(n+e)。

即对每条弧要建立入度数组操作和删除操作,每个顶点要遍历一次并删除。故时间复杂度为O(n+e)

 

采用分治法计算最大子段和时间复杂度为O(log(n)):

假设问题规模即数组长度为n,那么:

第1次:[0, n/2], [n/2+1, n], 每一部分是n/2

第2次:[0, n/4], [n/4+1, n/2], [n/2+1, 3n/4], [3n/4+1, n], 每一部分是n/4

...

第i次:假设第i次每个问题都不可再分,则每一部分为 n / n = 1,共有n项,2^i = n,解得i = logn,每一次分治的时间复杂度是O(n),二者相乘得时间复杂度O(nlogn),动态规划的时间复杂度是O(n)

 

程序段的 McCabe 环路复杂性

程序的环路复杂性给出了程序基本路径集中的独立路径条数,这是确保程序中每个可执行语句至少执行一次所必需的测试用例数目的上界。

McCabe环路复杂性,简单的定义为控制流图的区域数。从程序的环路复杂性可导出程序基本路径集合中的独立路径条数,这是确保程序中每个可执行语句至少执行一次所必需的最少测试用例数。

 

判定节点法:

通过控制流图中判定节点数计算。若P为控制流图中的判定节点数,则V(G)=P+1

 

递归算法的时间复杂度通常为指数 O(2^N)

 

 

算法一般可以用3种控制结构组合而成:

顺序结构,分支结构,循环结构。递归不属于基本控制结构。

 

串行算法和并行算法

发掘和利用现有串行算法中的并行性,可以将串行算法改造为并行算法。

由串行算法直接并行化的方法是并行算法设计的最常用方法之一;

不是所有的串行算法都可以直接并行化的;

一个好的串行算法并不一定能并行化为一个好的并行算法。

容器按key查找的复杂度:

STL库中,map和multimap底层都是红黑树实现的,两者的不同在于multimap允许重复的可以,而map中不行。

红黑树的查找复杂度为O(log(n))。

unodered_map/_set底层是哈希表实现的,查找复杂度为O(1)。

 

算法的五大特性

1.有穷性;算法必须在执行有限次之后停止;

2.确切性,算法的每一步必须有确切的意义;

3.输入项:0项或多项;

4.输出项:至少一项,没有输出的算法是没有意义的;

5.可行性。

 

Floyd-Warshall算法(Floyd-Warshall algorithm):

解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。算法的时间复杂度为O(N^3),空间复杂度为O(N^2)。

 

BFPRT算法

解决TOP-K问题最有效的算法是BFPRT算法,又称中位数的中位数算法,该算法由Blum、Floyd、Pratt、Rivest、Tarjan提出。算法步骤:

1. 将n个元素每5个一组,分成n/5(上界)组。

2. 取出每一组的中位数,任意排序方法,比如插入排序。

3. 递归的调用selection算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。

4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。

5. 若i==k,返回x;若i<k,在小于x的元素中递归查找第i小的元素;若i>k,在大于x的元素中递归查找第i-k小的元素。

终止条件:n=1时,返回的即是i小元素。

​​

回溯法和分支限界法

回溯法:

1)(求解目标)回溯法的求解目标是找出解空间中满足约束条件的一个解或所有解
2)(搜索方式:深度优先)回溯法搜索整个解空间,当不满条件时,丢弃,继续搜索下一个子结点,如果所有子结点都不满足,向上回溯到它的父节点。

分支限界法:

1)(求解目标)分支限界法的目标一般是在满足约束条件的解中找出在某种意义下的最优解,也有找出满足约束条件的一个解。
2)(搜索方式:广度优先)分支限界法以广度优先或以最小损耗优先的方式搜索解空间。

 

线性表 以链表方式存储时,访问第i位置元素的时间复杂性为O(n):

时间复杂度指的是一个数量级的概念,i只是单纯的一个位置

在这里可以计算访问某一节点的平均时间,访问第一个时间为1,第二个时间为2、、、以此类推,访问第n个的时间为n,求得平均时间,(1+n)*n/2n,这个时间的数量级为O(n)

 

求程序时间复杂度:(一般情况)加是开根号,乘是取对数

 

对于一个含n个元素的无序数组,构建一个大顶堆(Max-Heap)的时间复杂度为O(n)

排序重建堆时间复杂度为nlog(n)

 

逆波兰表达式:

    a*(b-c*d)+e-f/g*(h+i*j-k)

=  a * (b - (c * d)) + e - (f / g) * (h + (i * j) - k)

=  a * (b - (cd*)) + e - (fg/) * (h + (ij*) - k)

=  a * (bcd*-) + e - (fg/) * ((hij*+) - k)

=  (abcd*-*) + e - (fg/) * (hij*+k-)

=  (abcd*-*e+) - (fg/hij*+k-*)

=  (abcd*-*e+fg/hij*+k-*-)

%是两数相除取余数//是两数相除,对商取整数(向下取整)。

 

建立一个长度为n的有序单链表的时间复杂度为O(n^2):

将n个元素依次插入到空链表中,每一个元素插入需要遍历之前已经有序的链表,找到合适的位置,复杂度是O(n)。一共有n个元素,所以就是O(n*n)

 

移动n个盘子的汉诺塔问题需要g(n)次移动操作来完成,g(n)是三部分之和:

(1) 将n个盘上面的n-1个盘子借助C桩从A桩移到B桩上,需g(n-1)次移动;

(2) 然后将A桩上第n个盘子移到C桩上(1次);

(3) 最后,将B桩上的n-1个盘子借助A桩移到C桩上,需g(n-1)次。

递归关系:g(n)=2*g(n-1)+1 初始条件(递归出口):g(1)=1,即1、3、7、15…,g(n) = 2^n -1

 

多边形三角剖分公式:D(n+1)\Dn =(4n-6)\n  (Dn表示凸n边形的三角剖分数)

 

邻接矩阵存储有n个结点(0,1,...,n)和e条边的有向图 。在邻接矩阵中删除结点 的时间复杂度是O(n):

邻接矩阵用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据。由顶点表查找i,复杂度为O(1),然后查找二维数组i行i列,置i行i列均为0(即删除i节点),复杂度为2*O(n)。

 

 

一个优化的程序可以生成一n个元素集合的所有子集,那么该程序的时间复杂度是O(2^n):

每个元素分出现和不出现的两种情况,就是2的n次方。

 

记号O的定义:O(g(n))={f(n)|存在正常数c和n0使得所有n>=n0有: 0<=f(n)<=cg(n)}

假设包含t个非零元素的稀疏矩阵A含有m行n列,并采用三元组顺序表压缩存储,其快速转置算法的时间复杂度为O(n+t):

1.初始化所有列的非零元素的个数统计为0(n)

2.统计每一列的非零元素个数(t)

3.接着求每一列第一个非零元素的首位置(n)

4.最后对每一个非零个数转置(t)。

总共时间:2*(n+t),时间复杂度:O(n+t)

 

2n 个数中的最大值和最小值,最少的比较次数是3n-2

前两个数比较,大的为最大值, 小的为最小值, 用掉一次比较;后面2*(n-1)个数, 每两个比较, 共比较n-1次,大的同最大值比较, 共比较n-1次,小的同最小值比较,共比较n-1次,一共3*(n-1)次比较, 所以总次数 3*(n - 1) + 1 = 3n - 2次比较。

 

在一长度为 N 的有序数列中寻找两个数,使得两数之和等于某指定值的最快的算法的平均时间复杂度是O(N)

双指针法,*p,*q分别从0和最后一位开始,如果*p+*q<S,p++;如果大于,--q;如果p=q了还没找到就是不存在。p和q就遍历了一遍数组,所以是O(n)

 

递归过程分为两步“递”和“归”,对应着栈的两种操作“进栈”和“出栈”。在满足递归条件之前都是进栈,一旦不满足递归条件,则依次开始执行出栈操作,顺序为先进后出。

 

P问题:  能在多项式时间内解决的问题

NP问题: 不能在多项式时间内解决或不确定能不能在多项式时间内解决,但 能在多项式时间验证的问题

NPC问题: NP完全问题, 所有NPC问题(C代表complete)在多项式时间内都能约化(Reducibility)到它的NP问题 ,即解决了此NPC问题,所有NP问题也都得到解决。

NP hard问题:NP难问题, 所有NP问题在多项式时间内都能约化(Reducibility)到它的问题(不一定是NP问题)。

 

该程序最后输出结果是9:

遇到递归问题画解决:

以8为根节点画出分支树 8个分支表明调用8次 再加上自身调用x(8)一次 共9次

 

 

 

有一段楼梯台阶有15级台阶,一步最多只能跨3级,登上这段楼梯有5768种不同的走法:

斐波那契数列:f(n)=f(n-1)+f(n-2)+f(n-3)

 

递归函数中的形参自动变量

递归是借助栈来实现的,自动变量是存储在栈里面的,随着递归的进行,自动创建和销毁。

外部变量和静态变量存放在静态存储区。外部变量和静态变量不能作为递归函数的参数。递归的过程中,会创建变量存放在栈顶,如果是静态变量,递归结束后不会被销毁。自动变量不加说明就是局部变量。如果想要说明就用auto说明符(c++11),可以根据初始值来推断变量类型。auto修饰的变量,必须有初始值

 

递归程序的优化的一般的手段为尾递归优化

尾递归指在每一次递归的过程中保持了上一次计算的状态,即“线性迭代过程”。尾递归在函数返回的时候调用自身,并且return语句不能包含表达式。这样编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出。尾递归和一般的递归不同在对内存的占用,普通递归创建栈累积后计算收缩,尾递归调用时如果做了优化,栈不会增长,无论多少次调用也不会导致栈溢出,尾递归只会占用恒量的内存(和迭代一样)。

散列函数有共同的性质,则函数值应当以同等概率取其值域的每一个值。

 

有关hash冲突时候的解决方法:

通常有两类方法处理冲突:开放定址(Open Addressing)法和拉链(Chaining)法。

拉链(Chaining)法各链表上的结点空间是动态申请的,更适合于造表前无法确定表长的情况

在用拉链法构造的散列表中,删除结点的操作易于实现

拉链法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间

其他解决方法:线性探测;二次探测;双重散列;多重散列

某散列表:

该散列表的负载因子约为0.37:结点数目为7,基本区域能容纳的结点数为19, a=7/19≈0.37。

 

设某散列表的长度为1000,散列函数为除留余数法,H(K)=K%P,则P通常情况下最好选择997:

除留余数法选择小于等于表长的最大质数

 

设哈希表长为14,哈希函数是H(key)=key%11,表中已有数据的关键字为15,38,61,84共四个,现要将关键字为49的结点加到表中,用二次探测再散列法解决冲突,则放入的位置是9:

哈希

15

38

61

84

...

49

..

%11

4

5

6

7

 

5

...

49%11=5,此时发生冲突,按二次探测法:

1.(5+1^2)%14=6   冲突

2.(5-1^2)%14=4  冲突

3.(5+2^2)%14=9  不冲突

4.(5-2^2)%14=1  (如有必要)

 

为了保证代码的异常安全性,应该避免在析构函数中抛异常

 

Hash函数构造方法:

平方取中法:先通过求关键字的平方值扩大相近数的差别,然后根据表长度取中间的几位数作为散列函数值。

除余法:它是以表长m来除关键字,取其余数作为散列地址,即 h(key)=key%m

相乘取整法:首先用关键字key乘上某个常数A(0<A<1),并抽取出key.A的小数部分;然后用m乘以该小数后取整

随机数法:选择一个随机函数,取关键字的随机函数值为它的散列地址

 

线程安全的map在JDK 1.5及其更高版本环境的实现方法:

Map map = new ConcurrentHashMap();

Map map = Collections.synchronizedMap(new HashMap());

 

产生哈希冲突的影响因素:装填因子、哈希函数、处理冲突的方法,不包括哈希表长。

Hashtable  HashMap 的区别:

HashMap 是内部基于哈希表实现,该类继承AbstractMap,实现Map接口

Hashtable 线程安全的,而 HashMap 是线程不安全的

Properties 类 继承了 Hashtable 类,而 Hashtable 类则继承Dictionary 类

HashMap允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。

 

runtime会把weak对象存放到hash表中,对象的内存地址作为键,当该对象的引用计数为0的时候就会被回收。

 

对于内存中数据,查找性能较好的数据结构是Hash_Map,对于磁盘中数据,查找性能较好的数据结构是B+Tree

 

单向哈希表的特征:把任意长度的信息转换成固定的长度输出。  

 

对包含n个元素的散列表进行检索,平均检索长度不直接依赖于n

 

哈希函数的构造方法:①数字分析法 ②平方取中法 ③除留取余法  ④直接定址法 ⑤折叠法

 

现有一完全的P2P共享协议,每次两个节点通讯后都能获取对方已经获取的全部信息,现在使得系统中每个节点都知道所有节点的文件信息,共17个节点,假设只能通过多次两个对等节点之间通讯的方式,则最少需要30次通讯:

第一步:两两配对,8次

第二步:从第一步中的抽出8个,在进行两两配对,4次

第三步:从第二步中的四个两两配对,2次

第四步:从第三步中的两个两两配对,1次,这一步执行完毕后,有两个节点已经拥有全部信息

第五步:第四步的两个和其他两个相互配对,2次,此时,4个完成

第六步:这四个和剩下的4个配对,4此,此时,8个完成

第七部:这8个和剩下的8个配对,此时16个完成

第八步:和最后一个配对,此时17个完成

总计:8+4+2+1+2+4+8+1=15


 

如图1所示,假设有5个节点,按连线1、2、3、4通讯之后,节点4和5就掌握了所有节点的信息,之后,1、2、3节点只需跟4或5任一节点通讯一次即连线5、6、7就可保证每个节点都知道所有节点的信息,总的通讯次数是(n-1)+(n-2)=2n-3次。


如果将所有节点分成两组,如图2所示,两组中的节点分别按连线1-8顺序通讯之后,节点4和5就掌握了1-5所有节点的信息,节点9和0就掌握了6-0所有节点的信息,再按连线9、10通讯之后,节点4、5、9、0就掌握了1-0所有节点的信息,剩下的节点只需跟4、5、9、0任一节点通讯一次就可保证每个节点知道所有节点信息,和图1相比,多了9和10两次通讯,总的通讯次数是(2n1-3)+(2n2-3)+2=2n-4次(n1和n2分别表示分组中元素个数)。


数组

下面哪个Java语句声明了一个适合于创建50个字符串对象数组的变量?

String a[]; String[] a; Object a[];

在java 中,声明一个数组时,不能直接限定数组长度,只有在创建实例化对象时,才能对给定数组长度.。数组是一个引用类型变量 ,因此使用它定义一个变量时,仅仅定义了一个变量 ,这个引用变量还未指向任何有效的内存 ,定义数组不能指定数组的长度。

 

已知定义数组 char a[3];,&a[0] ++不能代表数组元素a[1]的地址():

地址不能自加,可以用一个指针指向它,对指针自加。

 

循环队列中元素个数计算方法是固定的,即(尾-头)%长度,但是由于是循环队列所以尾可能会小于头,所以要加上长度,使尾-头保持是正整数,然后再对长度求余,即元素个数。循环队列的相关条件和公式: 
队尾指针是rear,队头是front,size为循环队列的最大长度 
1.队空条件:rear == front 
2.队满条件:(rear + 1) % size == front 
3.计算队列长度:(rear - front+ size)% size 
4.入队:(rear + 1)% size 
5.出队:(front + 1)% size

 

已知 10*12 的二维数组 A ,以行序为主序进行存储,每个元素占 1 个存储单元,已知 A[1][1] 的存储地址为 420 ,则 A[5][5] 的存储地址为472:

A[5][5]与A[1][1]之间有4*12+4=52个元素,而A[1][1]地址为420,那么A[5][5]的地址就为420+52=472。

 

二维数组在内存中是连续存储的,而且是行优先存储。给出总元素个数,行数等于总元素对列数取整。编辑器会根据列数去计算行数,但不会根据行数去计算列数。假如有N列,数组寻址的时候编译器会自动得到 *(arr+(j*N)+i)。所以传参数的时候列数必须指定。

 

区分int *p[n]; 和int (*p)[n]; 看运算符的优先级:
int *p[n]; 运算符[ ]优先级高,先与p结合成为一个数组,再由int*说明是一个整型指针数组。
int (*p)[n]; ( )优先级高,首先说明p是一个指针,指向一个整型的一维数组。

int *s[8]; //定义一个指针数组,该数组中每个元素是一个指针,每个指针指向哪里就需要程序中后续再定义了。
int (*s)[8]; //定义一个数组指针,该指针指向含8个元素的一维数组(数组中每个元素是int型)。(*s)等价于s[]

代码执行后,array的结果为[3,4,1,6,-1,10]:

 

sort()方法用于对数组的元素进行排序,参数决定排序的顺序。箭头函数表示:当Math.abs(a-3)>Math.abs(b-3)时,a放在b后面,Math.abs(a-3)<Math.abs(b-3)时,不交换位置。原数组减3后的绝对值[4,2,0,1,3,7],将其作为参数决定排序。

定义char s[20]="programming",*ps=s ;则不能表示字符‘o’的是ps+2:

ps+2:指针向后移动2个单位指向字符'o'的地址,并不是地址中的值

s[2]:表示数组名称加下标引用元素

ps[2]:表示指针名称加下标引用元素

*(ps+2):对ps+2的地址进行取存放的值

 

关联数组:关联数组和数组类似,它包含着类似于(键,值)的有序对,是一种具有特殊索引方式的数组,关联数组的元素没有特定的顺序。和数组不同的是, 关联数组的索引值不是非负的整数而是任意的标量。这些标量称为Keys,可以在以后用于检索数组中的数值。

 

设m*n 矩阵中有t个非零元素且t<<m*n,这样的矩阵称为稀疏矩阵

对于阶数很高的大型稀疏矩阵,如果按常规分配方法顺序分配在计算机内相当浪费内存。为此提出另外一种存储方法,仅仅存放非零元素。但对于这类矩阵,通常零元素分布没有规律,为了能找到相应的元素,所以仅存储非零元素的值是不够的,还要记下它所在的行和列。

于是将非零元素所在的行、列以及它的值构成一个三元组(i,j,v),然后再按某种规律存储。


三元组表示稀疏矩阵可大大节省空间,但是当涉及到矩阵运算时,要大量移动元素。


 

十字链表表示法可以避免大量移动元素。

节点结构如下:  

down指向同列下一个节点,

right指向同行下一个节点

 

 

 

矩阵部分表示如下:

 


数组要么在静态存储区被创建(全局数组),要么在上被创建(动态数组)。

二维以上的数组其实是一种特殊的广义表

l  数组一旦建立,结构的元素个数和元素间的物理存储关系就不再变化。

l  数组采用顺序存储方式表示。

 

线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。线性表主要由顺序表链式表表示。在实际应用中,常以数组、栈、队列、字符串等特殊形式使用。

线性表长度的定义是它所包含的元素的个数,元素的类型决定了元素所占用存储空间的大小

 

有序表中任意一个位置插入元素,插入位置之后的元素依次挪动一个位置,假设元素插入的位置坐标为k,则时间复杂度为O(k),渐进时间复杂度为O(n)

二维数组是每个元素都为顺序表的顺序表。

 

哈希表是随机存储,所以是离散分布,无法实现顺序遍历。

 

GetHead 【 GetTail 【 GetHead 【 ((a,b),(c,d)) 】】】 = b:

GeiTail返回除第一个元素之外的元素构成的“子表” ,GeiHead返回第一个元素”​。

GetHead 【 ((a,b),(c,d)) 】返回第一个元素(a,b)

GetTail (a,b) 返回除第一个元素外的子表(b)
GetHead (b)返回第一个元素 b

 

数据结构的基本运算中,搜索不是必须的,比如中就没有搜索。

 

的表示方法有两种,分别是邻接矩阵邻接表

 

JDK8之前版本,HashMap的数据结构由数组+链表组成,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。JDK8及其以后版本,HashMap的数据结构是数组+链表+红黑树

 

关于 Array 数组对象:

l  push( ):向数组末尾添加一个或者多个元素,并返回新的长度。

l  pop( ):删除数组的最后一个元素,把数组的长度减1,并返回被删除元素的值。

l  shift( ):删除数组的第一个元素,并返回被删除的值。

l  unshift( ):向数组的开头添加一个或多个元素,并返回新的长度。

l  join( ):把数组中的所有元素放入一个字符串,元素通过指定的分隔符进行分隔。

 

将10阶对称矩阵压缩存储到一维数组A中,则数组A的长度最少为55:

10阶矩阵,主对角元素10个,剩余100-10=90个元素,根据对称矩阵的性质,保留这90个元素的一半即45个,所有总共10+45=55个。

 

在定义 int a[3][4][2]; 后,第 20 个元素是a[2][1][1]:

三维数组可以看成是一本书,int a[3][4][2]; 就是有3页每页4行2列。

总共有(0~2)3层,每层可以看成是一个二维数组(如b[4][2]),有4*2=8个元素。

前两层总共有16个元素,所以第20个元素应该在第三层(下标为2).

20-14=4还差4个元素,所以第三层中(例如二维数组b[4][2])第四个元素的位置为b[1][1]

所以第20个元素是a[2][1][1]. 

 

sizeof统计分配了的空间,如果没显式分配空间(显式分配空间是指定义数组并指定数组长度),那么就遍历统计,直到遇到\0,会把\0也统计上。

strlen统计\0前面所有字符,不包括\0; 

 

c/c++,Java等强类型语言定义数组时要指定类型,只能存储同类项元素。Python,JavaScript等弱类型语言可以存储不同类型元素。

 

数组元素的地址计算与数组的存储方式有关:二维数组存储方式可以分为行优先和列优先两种。

设有一个10阶对称矩阵A[10][10],采用压缩存储方式按行将矩阵中的下三角部分的元素存入一维数组B[ ]中,A[0][0]存入B[0]中,则A[8][6]在B[42]的位置:

由于是对称矩阵,因此压缩存储只要存储以下三角矩阵:

(0,0)                                             1

(1,0)(1,1)                                        2

(2,0)(2,1) (2,2)                                   3

(3,0)(3,1) (3,2) (3,3)                            4

(4,0)(4,1) (4,2) (4,3) (4,4)                         5

(5,0)(5,1) (5,2) (5,3) (5,4) (5,5)                   6 

(6,0)(6,1) (6,2) (6,3) (6,4) (6,5) (6,6)            7

(7,0)(7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7)      8 

(8,0)(8,1) (8,2) (8,3) (8,4) (8,5) (8,6)            6

求A[8][6]中有多少个元素:分为完整的等腰三角形和最后一行两部分。

等腰三角形是等差数列:8*(8+1)/2=36

最后一行索引为6,有6+1个元素

故总共有36+7=43个元素,索引从0开始,故占位减1: 43-1=42。

 

逻辑结构可以采用两种方法来描述:二元组、图形。

二元组表示形式: DS = ( D, S )【Data Structure】其中 D 是数据元素的集合; S是D中数据元素之间的关系集合,并且数据元素之间的关系是使用序偶来表示的。序偶是由两个元素 x 和 y 按一定顺序排列而成的二元组,记作<x , y>, x是它的第一元 y是它的第二元素。

l  如果 D != null,而S == null,则该数据结构为集合结构

l  如果 D = {01, 02, 03, 04, 05},S = {<02,04>, <03,05>, <05,02>, <01,03>},则该数据结构是线性结构

在这些数据元素中有一个可以被称为“第一个”的数据元素;还有一个可以被称为“最后一个”的数据元素;除第一个元素以外每个数据元素有且仅有一个直接前驱元素,除最后一个元素以外每个数据元素有且仅有一个直接后续元素。这种数据结构的特点是数据元素之间是 1对 1 的联系,即线性关系。

l  如果 D = {01, 02, 03, 04, 05, 06},S = {<01,02>, <01,03>, <02,04>, <02,05>, <03,06>},则该数据结构是树结构

除了一个数据元素(元素 01)以外每个数据元素有且仅有一个直接前驱元素,但是可以有多个直接后续元素。这种数据结构的特点是数据元素之间是 1 对 N 的联系。

l  如果 D = {01, 02, 03, 04, 05},S = {<01,02>, <01,05>, <02,01>, <02,03>, <02,04>, <03,02>,<04,02>, <04,05>, <05,01>, <05,04>}:则该数据结构是图结构

每个数据元素可以有多个直接前驱元素,也可以有多个直接后续元素。这种数据结构的特点是数据元素之间是 M 对 N 的联系。

 

在 C 语言中一维数组的定义方式为:元素类型 数组名[E] ;E 为整型常量表达式

 

有一个100*90的稀疏矩阵,非0元素有10个,设每个整型数占2字节,则用三元组表示该矩阵时,所需的字节数是66:

将非零元素所在、非零元素的构成一个三元组(i,j,v),每个非零元素占3*2=6个字节,共10个非零元素,需6*10 = 60 个字节;

此外还要用三个整数来存储矩阵的行数列数总元素个数,又需要3*2 = 6个字节;

随机存取、顺序存取、随机存储和顺序存储概念辨析:

存取结构:分为随机存取和非随机存取(又称顺序存取)。

1、随机存取:就是直接存取,可以通过下标直接访问的那种数据结构,与存储位置无关,例如数组。存取第N个数据时,不需要访问前(N-1)个数据,直接对第N个数据操作 (array)。

2、顺序存取:即非随机存取。存取第N个数据时,必须先访问前(N-1)个数据 (list),不能通过下标访问,只能按照存储顺序存取,与存储位置有关,例如链表

存储结构:分为顺序存储和随机存储。

1、顺序存储:在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素,称作线性表的顺序存储结构。顺序存储结构是把逻辑上相邻的节点存储在物理位置上相邻的存储单元中,结点之间的逻辑关系由存储单元的邻接关系来体现。通常顺序存储结构是借助于计算机程序设计语言(例如c/c++)的数组来描述的。顺序存储结构特点:

节省存储空间,因为分配给数据的存储单元全用存放结点的数据(不考虑c/c++中数组需指定大小的情况),结点之间的逻辑关系没有占用额外的存储空间。

l  可实现对结点的随机存取,即每一个结点对应一个序号,由该序号可以直接计算出来结点的存储地址。

l  不便于修改,对结点的插入、删除运算时,可能要移动一系列的结点。

2、随机存储:在计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。它不要求逻辑上相邻的元素在物理位置上也相邻,但也同时失去了顺序表可随机存取的优点。随机存储最典型的代表为链式存储。链式存储结构特点:

l  比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。

l  逻辑上相邻的节点物理上不必相邻。

l  插入、删除灵活 (不必移动节点,只要改变节点中的指针)。

l  查找结点时链式存储要比顺序存储慢。

l  每个结点是由数据域和指针域组成。

 

当使用图形来表示数据结构时,是用图形中的点来表示数据元素,用图形中的弧来表示数据元素之间的关系。如果数据元素 x 与 y 之间有关系<x , y>,则在图形中有从表示 x 的点出发到达表示 y 的点的一条弧。

 

 

执行以下代码段:char a[2][3] = {{'a', 'b', 'c'}, {'1', '\0', '2'}};  printf("%s", a[0]); 输出abc1:

a[0]代表了以a[0][0]为首的一维数组,也就是将这个char a[2][3]摊成一维数组了。%s:a[0]就等于输出a b c 1 \0 2,但是字符输出遇到 \0直接结束。

C语言中定义:int a[4][10] (其中0<=i<4,0<=j<10); 则对数组元素a[i][j]引用可以有如下表示:

①*(&a[0][0]+10*i+j)  ②*(a[i]+j)  ③*(*(a+i)+j)

 

若数组S[1..n]作为两个栈S1和S2的存储空间,对任何一个栈,只有当[1..n]全满时才不能进行进栈操作。为这两个栈分配空间的最佳方案是S1的栈底位置为1,S2的栈底位置为n

栈空间共享同一段数组内存空间,他们是在数组的两端,分别向中间靠拢,只要栈的栈顶不碰面,这段空间就没满,可以一直用。想象成两只试管口对上,绑在一个横坐标上。两只试管分别加满了就加不进去了。

 

设A是n*n的对称矩阵,将A的对角线及对角线上方的元素以列为主的次序存放在一维数组B[1..n(n+1)/2]中,对上述任一元素aij (1≤i,j≤n,且i≤j)在B中的位置为j(j-1)/2+i:

(1+j-1)*(j-1) / 2 + i = j(j-1)/2 + i

 

稀疏矩阵采用三元组表形式进行压缩存储,完成对三元组表进行转置

  • 矩阵的行数 n 和列数 m 的值交换;
  • 将三元组中的i和j调换;
  • 转换之后的表同样按照行序(置换前的列序)为主序,进行排序

 

 

Array对象常用方法中:

不改变原数组:

l  concat():连接两个或多个数组,返回被连接数组的一个副本

l  join():把数组中所有元素放入一个字符串,返回字符串

l  slice():从已有的数组中返回选定的元素,返回一个新数组

l  toString():把数组转为字符串,返回数组的字符串形式。

改变原数组:

l  shift():把数组的第一个元素删除,返回第一个元素的值。

l  pop():删除数组最后一个元素,返回最后一个元素的值。

l  unshift():向数组的开头添加一个或多个元素,返回新数组的长度

l  push():向数组末尾添加一个或多个元素,返回新数组的长度

l  reverse():颠倒数组中元素的顺序,返回该数组

l  sort():对数组元素进行排序(ascii),返回该数组

l  splice():从数组中添加/删除项目,返回被删除的元素。

 

对于长度为n的线性表,建立其对应的单链表的时间复杂度为O(n)

采用头插式尾插式建立单链表,都需要扫描这n个元素,边扫描边创建单链表中的结点并链接起来,其时间复杂度为O(n)。

char *gets(char *str) 从标准输入 stdin 读取一行,并把它存储在str所指向的字符串中,当读取到换行符或到达文件末尾时停止。gets能够接受空格、制表符Tab和回车等。scanf()函数里输入空格,会被系统当作输入字符串之间的分隔符,认为当前字符串已经结束。gets和sacnf函数在字符串接受结束后自动加'\0'

 

右左法则:首先从未定义的标识符开始阅读,然后从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

对于int (*(*p)[10])(int *):

先看未定义标识符p,p的左边是*,*p表示一个指针,跳出括号,由于[]的结合性大于*,所以*p指向一个大小为10的数组,即(*p)[10]。左边又有一个*号,修饰数组的元素,*(*p)[10]表示*p指向一个大小为10的数组,且每个数组的元素为一个指针。跳出括号,根据右边(int *)可以判断(*(*p)[10])是一个函数指针。则该函数为:声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int*。

 

在声明时可以使用 char s[5]="good";赋值,接下来赋值不可以使用s = "good";只能有:

1.用循环遍历数组单个赋值;

2.字符数组和字符串用strcpy或memcpy;

3.数值数组用memset。

 

用数组r存储静态链表,结点的next域指向后继,工作指针j指向链中结点,使j沿链移动的操作为j=r[j].next:

静态链表:用一个数组来存储链表,也就是链表的线性存储,这个数组得足够大。

1

2

3

4

typedef struct{

    int data;

    int next;

}component;

 

题目中的指针只是指向数组下标的一个整数。上图中的每一个数组元素都是存放component结构体类型的数据。比如A[0].data=1,A[0].next=3,即数组第1个元素中的数据为1,它的直接后继在数组下标为3的元素里。

 

定义数组char a[6] 用来接收输入5个字符,可以用gets(&a[0]); gets(a); scanf("%s", a + 1);

getsscanf输入都是需要取地址,数组名a等价于首元素地址&a[0],a+1相当于&a[1]

 

二维数组声明char b[2][3]={"a","b","c"}; 错误:

char b[2][3] 表示能存2个字符串,每个字符串的长度不超过3,但是A中存了3个字符串

 

已知代码段定义int a[] = {1, 2, 3, 4, 5, 6, 7, 8}; char s = 'a', e, i; 则a['e' - s] 结果为5:

'e'-s == 'e'-'a' == 4 而数组a[4] == 5。

Java中数组初始化有三种方式:

  1. 动态初始化:数组定义与为数组分配空间赋值的操作分开进行;
  2. 静态初始化:在定义数字的同时就为数组元素分配空间赋值
    1. 默认初始化:数组是引用类型,它的元素相当于类的成员变量,因此数组分配空间后,每个元素也被按照成员变量的规则被隐式初始化

     //动态初始化 int[] a;//int a[];两者都可以 a = new int[10];

a[0] = 0; a[1] = 1; a[2] = 2;

     //静态初始化 int[] b = {0, 1, 2};//int b[] = {0, 1, 2};两者都可以

     //默认初始化 int[] c = new int[10];//int c[] = new int[10];两者都可以

int A[2][3]={1,2,3,4,5,6}; 则A[1][0] 的值为3,*(*(A+1)+1)的值为4:

二维数组可以理解为一维的一维。在一维中,*表示取数值,在二维中*表示取第几行地址**表示取值

A+1为指向第二个元素的常量指针,*(A+1)为第二个元素,(第二个元素为int [3],即{4,5,6})

同时对象名也是指向第一个元素的常量指针。所以*(A+1)也是指向元素4的指针,那么*(A+1)+1为指向元素5的指针。

A是二维数组名, 在参与运算时候会退化成指针。A这个指针的值和二维数组中第00个元素的地址一样,即A==&A[0][0] (注意这里很重要是在数值上),&A[0][0]表示二位数组下标为[0][0]元素的地址,*A表示第0行的行首地址, 第0行首地址跟A[0][0]的地址也一样, 所以在数值上A==&A[0][0] = *A ,但是表示的含义不一样。

设A == &A[0][0] == *A == 0x0028F724,那么**A就是二维数组的下标为00的, **A == 1,因为二维数组是按行存储,所以表示增加一个行的字节数, 因此需要在A的基础上增加12个字节,所以A+1为0x0028F730,*(A+1)表示下标为1的行首地址,因此也是0x0028F730, 即A+1 = *(A+1) = 0x0028F730,*(A+1) + 1表示往后面增加一个元素的字节数,即实际为[1][1]的元素的地址, 所以 *(*(A+1) + 1) 就是5。

 

定义int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, *p = a; 则表示a数组元素:*p,*a,a[p-a]:

*p=a[0],*a=a[0],p与a都是指向a[0]的地址,相同地址相减为0,a[p-a]=a[0]。

 

C/C++ 中定义 char a [10], *p=a; 赋值语句正确的是p="abcdefg "; 错误的是a [] ="abcdefg"; a="abedefg"; *p="abcdefg";

双引号做了3件事: 1.申请了空间(在常量区),存放了字符串 2. 在字符串尾加上了'/0'  3.返回地址,将返回的地址赋值给了p

1. 定义时直接用字符串赋值。 char a[10]="hello"; 但是不能先定义再赋值,即以下非法:char a[10];a[10]="hello";

2. 利用strcpy。 char a[10]; strcpy(a,"hello");

3. 利用指针。 char *p; p="hello"; 这里字符串返回首字母地址赋值给指针p。以下非法:char a[10]; a="hello"; a已经指向在堆栈中分配的10个字符空间,不能再指向数据区中的"hello"常量。可以理解为a是一个地址常量,不可变,p是一个地址变量

4. 数组中的字符逐个赋值。


 

字符串

假设二维字符数组s为char s[][10] = {"mango" ,"apple","orange","banana","pineapple" },那么sizeof(s)的结果为50:sizeof是计算分配了的空间。

 

对字符串 "mabnmnm" 的二进制进行哈夫曼编码有13位:

频率 m:3/7 a:1/7 b:1/7 n:2/7(频率越高离根越近

建树(o只占位,|表示0 ,\表示1):

                   o

                   |\ 

                  m  o
                       |\
                      n  o
                       |\
                    a  b
由|,\转为0,1得:m->0,n->10,a->110,b->111 ,mabnmnm:0 110 111 10 0 10 0(共13位)

 

C 语言中char mark = ‘#’; 则’#’ 占1字节内存、”#” 占2字节内存:

单引号引起的一个字符大小就是一个字节。 而用双引号引起的字符串大小是字符的总大小+1,因为用双引号引起的字符串会在字符串末尾添加一个二进制为0的字符'\0'

 

关于「字符串」:

字符串是特殊的线性表;字符串可以连续存储也可以链式存储;空串与空白串不是同一含义:

l  串是特殊的线性表,故其存储结构与线性表的存储结构类似,只不过组成串的结点是单个字符。字符串的值中每个字符可以是字母、数字或其他字符组成的序列,组成线性表的每个元素就是一个单字符,所以是一种特殊的线性表

l  连续存储包括静态定长存储和动态堆分配存储;串值也可用单链表存储,简称为链串。所以实际应用中为了提高空间利用率,可使每个结点存放多个字符(这是顺序串和链串的综合 (折衷) ),称为块链结构。

l  字符串通常采用顺序存储,但是字符串较长而没有那么大的连续空间时,可以把一个字符串分成多个小串,串与串之间采用链式存储

l  空串是零个字符的串,它的长度为零。而空白串是指由一个或多个空格组成的串,它的长度为串中空格字符的个数。

 

strcpy(s1,s2)这个函数是把s2字符串拷贝到s1这个字符串,同时也把s2的 '\0' 拷过去。

 

由 4 个 "1" 和 4 个 "0" 组成的 8 位二进制补码,能表示的最小整数是-121:

正数: 原码 = 反码 = 补码。负数:反码 = 原码除符号位之外的各位求反,补码 = 反码 +1 (如果 +1 之后有进位的,要一直往前进位,包括符号位。)

原码绝对值最大,反码绝对值应该最小,相应的补码的绝对值也最小,所以要求的补码(除符号位外)应该是 #000 0111,加上符号位1得到1000 0111(补码)代表的原码是最小的-121。

 

数据结构的实质就是相互存在各种特定关系的数据元素集合

存储数据时,通常不仅要存储数据元素的,还要存储元素之间的关系

KMP算法:时间复杂度为O(m+n),空间复杂度为O(m)。 KMP算法所需的附加空间,即next数组所需的存储空间,等于模式串的长度O(m)​。

BF算法(普通匹配算法):时间复杂度O(m*n);空间复杂度O(1)。

 

串又称为字符串,是一种特殊的线性表,其特殊性体现在数据元素是一个字符,也就是说串是一种内容受限的线性表。

栈和队列是操作受限的线性表。

 

StringBuffer线程安全;StringBuilder线程不安全

 

求字符串子串时需要注意:空串也是一个子串。

 

对字符使用哈夫曼(Huffman)算法进行编码:

l  求Huffman树可以有多套方案,但最终每套方案生成的编码长度相同且都是最优解

l  可以将左子树定为1右子树定为0也可以反之,不同的方案获得的编码值可以不同

l  不同方案影响的只是通向节点的路径为0还是1,不会影响Huffman树的层次结构。

l  生成Huffman树之后,出现频率越高的节点越靠近根,深度越小即编码值位数越短

 

定义 char s1[] = "12345", *s2 = "1234";

printf("%d\n", strlen(strcpy(s1, s2)));则输出结果是4:strcpy(s1,s2)这个函数是把s2字符串拷贝到s1这个字符串,同时也把s2的 '\0' 拷过去,所以覆盖了s1的所有字符。

 


以下程序段的输出结果是12:

#include<stdio.h>

void main() { 

    char s[] = "\\123456\123456\t";

    printf("%d\n", strlen(s));

}

 

\\、\123、\t是转义字符。所以长度是 12。
\\ 表示单个字符\
\123 表示八进制的ASCII码值为123对应的字符

\t 表示制表符


 

cin>> 操作符是根据后面变量的类型读取数据。输入结束条件:遇到EnterSpaceTab键。所以一般用getline作为字符串的输入(含空格)。​

 

设栈的初始状态为空,当字符序列 "a3_" 作为栈的输入时,输出长度为 3 的且可以用作 C 语言标识符的字符串序列有3个:

栈的顺序是先进后出,字符序列为a3_  

1)a入栈,再出栈,然后3入栈,再出栈,—入栈,再出栈   序列是a3_

2)a入栈,再出栈,然后3,—入栈,再出栈,序列是a_3

3)a入栈,3入栈,再出栈,a出栈, —入栈,再出栈   序列是3a_

4) a入栈,3入栈,再出栈, —入栈,序列是3_a

5) a入栈,3入栈,_入栈,序列是_3a

或者利用卡特兰数公式求递推关系:

 

C语言的标识符不能以数字开头,去除3a_和3_a   答案为3。

​链表

文件分配表FAT是管理磁盘空间的一种数据结构,用在以链接方式存储文件的系统中记录磁盘分配和跟踪空白磁盘块。假设磁盘物理块大小为1KB,并且FAT序号以4bits为单位向上扩充空间。1)一块540MB的硬盘的FAT最少需要占用1.35M的空间。2)一块1.2GB的硬盘的FAT最少需要占用3.6M的空间。

(1) 硬盘大小为540MB且磁盘物理块大小为1KB时,该硬盘共有盘块540MB/1KB = 540K个。2^19<540K<2^20,所以540K个盘块号需要用20位二进制数表示,也即文件分配表FAT的每个表项为20/8=2.5B。所以540MB磁盘的FAT需占用存储空间容量为2.5B×540K=1350KB。

(2)当硬盘容量大小为1.2GB时,硬盘共有盘块1.2M个。2^20<1.2M <2^21,由于FAT序号以4bits为单位向上扩充空间,所以1.2M个盘块号需要用24位二进制数表示,文件分配表FAT的每个表项为24/8=3B。所以1.2GB磁盘的FAT需占用存储空间容量为3B×1.2M=3.6MB。

在有N个结点的二叉链表中,必定有2N个链域。除根结点外,其余N-1个结点都有一个父结点。所以一共有N-1非空链域,其余2N-(N-1)=N+1个为链域。

 

单链表的每个节点中包括一个指针next,它指向该节点的后继节点。将指针q指向的新节点插入到指针p指向的单链表节点之后的操作:q->next = p->next;  p->next = q;

 

在表头和表尾都可能有元素被插入的情况下,在单循环链表中设置尾指针比设置头指针好:

使用指向终端节点的尾指针,找开始节点:(rear->next)->next;找终端节点:rear。查找开始节点和终端节点时间复杂度都是O(1):如果用头指针,找开始节点的时间是O(1),找终端节点要从头指针开始遍历整个循环链表,时间是O(n)

 

在双链表中搜索给定元素,从两个方向搜索双链表,比从一个方向搜索双链表的方差要小:

如果链表数据是无序的,则单向搜索与双向搜索平均速度相同;如果链表是有序的,而要搜索的数据距离最小最大较近,则双向搜索平均速度更快。因此双向搜索更稳定,方差更小。

 

将两个长度为 len1 和 len2 的升序链表,合并为一个长度为 len1+len2 的降序列表,采用归并算法,在最坏情况下,比较操作的次数与O(len1+len2)最接近:

len2的最大序列和len1的最大序列相互比较,较大的一个放到新链表中(假设len2的元素),

然后对比len2序列剩余元素的最大值继续和len1的最大值做比较,将较大的放入新链表中。

最坏情况:当两个链表交替进行的时候,连接两个链表的时间最长。len2中的每个元素都要和len1做比较,而且len1相互之间再做比较。

最优情况:当两个链表一个先走完,另一个没有动的时候结果最优,最优时间为min(len1,len2);

 

F=(a,F)是一个递归广义表,它的长度是2,深度是无穷:

广义表是由n个元素组成的序列,长度为n。广义表中括号的最大层数叫广义表的深度

F=(a,F)的长度为2,由于属于递归表,所以深度为无穷,F相当于一个无限的表(a,(a,(a,(...))))。

 

将长度为n的单链表链接在长度为m的单链表后面,其算法的时间复杂度为O(m):

先遍历长度为m的链表,找到链表尾部,这个时间复杂度为O(m),再将链表尾部的next指针指向长度为n的链表的头结点

 

双循环链表中,尾节点的后继指向头结点,而头结点不是尾节点的逻辑后继

如果某个有向图邻接表中第i条单链表为空,则第i个顶点出度

顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。

边表的结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

如V3指向null,所以出度为0。

 

 

设单循环链表中节点的结构为(data,next), rear是指向非空的带头节点的单循环链表的尾节点的指针。删除链表第一个数据元素:s=rear->next->next;rear->next->next=s->next;free(s);

错误答案:

s=rear;rear=rear->next;free(s)   rear=rear->next;free(rear)  rear=rear->next->next;free(rear)

 

 

构造链表的结构类型:struct bb{ int a;bb * b;};构造链表需要一个指向此结构体指针

 

广义表K=(m,n,(p,(q,s)),(h,f)),则head[tail[head[tail[tail(K)]]]]的值为q:

head() 返回列表的第一个元素;tail() 返回列表的删去第一个元素之后的剩余列表。

 

链表只能顺序访问任一结点,双向链表也不例外。

设数据结构 B=(D, R) ,其中D={ a, b, c, d, e, f } R={ (a, b ) , (b, c ) , (c, d ) , (d, e), (e, f), (f, a) }

则该数据结构为非线性结构

 

关于单向链表

l  如果两个单向链表相交,那他们的尾结点一定相同:单链表的每个节点都具有唯一的前驱节点和唯一的后继节点,所以当两个单链表存在相交的节点时,这两个链表则同时拥有这个节点,以及这个节点的所有后继节点,当这个公共节点是尾节点时,他们则只含有一个公共节点尾节点。

有环的单向链表跟无环的单向链表不可能相交:当相交的时候,无环的单向链表也会被迫存在一个环,只不过这个环的”起点“可能不是原来单向链表的头结点。

l  如果两个单向链表相交,这两个链表可以存在环。

 

 

广义表

简称表,是线性表的推广。一个广义表是n个元素的一个序列,n=0时称为空表。广义表的表示与线性表相同:设ai为广义表的第i个元素,GL=(a1,a2,…,ai,…,an)。n表示广义表的长度,即广义表中所含元素的个数。如果ai是单个数据元素,则ai是广义表GL的原子;如果ai是一个广义表,则ai是广义表GL的子表。 用小写字母表示原子,大写字母表示广义表的表名。

广义表的特性:

l  广义表中的数据元素有相对次序;

l  广义表的长度定义为最外层包含元素个数;

l  广义表的深度定义为所含括弧的重数。其中原子的深度为0,空表的深度为1;

l  广义表可以共享;一个广义表可以为其他广义表共享;这种共享广义表称为再入表

l  一个广义表可以是自已的子表,这种广义表称为递归表,深度无穷值,长度有限值;

l  head 返回列表第一个元素(不带括号),tail返回删除第一个元素后剩余的列表(带括号)。

 

邻接矩阵顺序存储结构,邻接表链式存储结构。

邻接表:为的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点Vi的边(对于有向图是以Vi为尾的弧)。

 

单链表插入操作:

核心是新旧地址互换,先处理新节点的后驱关系,再处理新节点的前驱关系:1、将旧结点的指针域(即pPre->Link,它存放着接下来的那个结点的地址​)赋值给新结点的指针域(pNew->Link),为了完成插入,新结点应指向旧结点原来指向的元素。2、将指向新结点的指针(pNew,即新结点的地址)赋值给旧结点的指针域(pPre->Link),以让旧结点指向新结点。

 


 

横线处填入程序:

node_t* m=n;

n=n->next;

m->next=head;

head=m;

 


在执行while(n)循环之前,node_t* n=head; 用一个临时变量n来保存结点①,​head=NULL; 把head修改为NULL。​然后开始遍历,while循环里面:

node_t* m=n; n=n->next;  用m来保存n,然后让n指向n的下一个结点,之所以复制 n 给 m ,是因为 n 的作用是控制while循环次数,每循环一次它就要被修改为指向下一个结点。

m->next=head; head=m; ​ 变量head像一个临时变量,m 指向 head,然后 head 等于 m。

  1. m被赋值为n(结点①),n由指向结点①变成了指向结点②,m(结点①)指向head(NULL),head接着被赋值为结点①。(①指向NULL)
  2. m被赋值为n(结点②),n指向由结点②变成结点③,m(结点②)指向head(结点①),head接着被赋值为结点②。(②指向①)
  3. m被赋值为n(结点③),n指向由结点③变成结点④,m(结点③)指向head(结点②),head接着被赋值为结点③。(③指向②)
  4. m被赋值为n(结点④),n指向由结点④变成结点⑤,m(结点④)指向head(结点⑤),head接着被赋值为结点④。(④指向③)
  5. m被赋值为n(结点⑤),n指向由结点⑤变成结点⑥(结点⑥是NULL,意味着这是最后一次循环),m(结点⑤)指向head(结点⑥),head接着被赋值为结点⑤。(⑤指向④)

 

头结点head的单向循环链表L为空的判断条件是head->next==head:

循环链表带头结点,头结点不算链表内容。

单向不带头结点链表判空:head==NULL;单向带头结点判空:head->next == NULL

单向循环不带头结点判空:head==NULL;单向循环带头结点判空:head->next == head

 

线性表是具有 n 个数据元素的有限序列(n>0)

数据元素数据的基本单位,通常作为一个整体进行考虑和处理。一个数据元素可由若干个数据项组成,数据项是构成数据元素的不可分割的最小单位。

 

一次性加载N(N在1000左右)个随机数,后续只对该集合进行遍历。最宜采用链表结构存放:

链表适合遍历不适合随机访问。​随机数未经排序,二叉树不适合;需要遍历,hash表不适合;不强调数据之间的关系,图不适合;随机数数据类型不一致,数组不适合。

 

单链表的存储密度小于1:

存储密度=数据项所占空间/结点所占空间,结点所占空间由数据项所占空间和存放后继结点地址的链域组成。

 

双向循环链表结点 p 后插入s:s->prior=p;s->next=p->next;p->next->prior=s;p->next=s

 

带链队列初始状态为 front=rear=NULL 。经过一系列正常的入队与退队操作后, front=rear=10 。该队列中的元素个数为1:

带链的队列每个元素都包含一个指针域指向下一个元素,当带链队列为空时 front=rear=Null ,插入第 1 个元素时 rear+1 和front+1 都指向该元素,插入第 2 个元素时rear+1和front 不变,删除 1 个元素时front+1。即front=rear不为空时带链的队列中只有一个元素。

 

设链式栈中结点的结构为(data ,link),且top是指向栈顶的指针,若想在链式栈的栈顶插入一个由指针s所指的结点,则应执行s->link=top; top=s;:

先修改s指向原栈顶,然后修改栈顶指针指向s

 

对于双向循环链表,每个结点有两个指针域next和prior,分别指向前驱和后继。在p指针所指向的结点之后插入s指针所指结点的操作:

 

 

线性表可以顺序存储也可以链式存储,所以不只有顺序表,还包括链表

 

在具有 顶点和 无向图邻接表存储中,邻接表中结点的总数为2N

邻接表是图的一种主要存储结构,用来描述图上的每一个点。对图的每个顶点建立一个容器(n个顶点建立n个容器),第i个容器中的结点包含顶点Vi的所有邻接顶点

无向图邻接表中,第i个边表的结点是表示关联于顶点i的边。同一条无向边关联于两个顶点,因此同一条边在邻接表中用了两个边表结点表示。

对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。

 

用邻接表表示图进行深度优先遍历时,通常采用来实现算法。

深度优先遍历使用递归实现,故用到了栈。广度优先遍历,每次需要确保当前层的所有结点被访问到,要用队列存储。

 

在一个具有n个结点的有序单链表中插入一个新结点并仍然保持有序的时间复杂度是O(n):

二分查找的条件:1.顺序存储结构.  2.元素保持有序。有序链表不满足二分查找的条件,需要遍历找到插入的位置,复杂度为O(n)。

 

在一个长度为 n ( n>1 )的单链表上,设有头和尾两个指针。

l  当删除单链表中的第一个元素时只需要将头指针指向第二个元素,然后释放第一个元素储存空间申请的内存。与链表长度无关

1

2

3

ListNode *temp = head->next;

head->next = temp->next;

free(temp);

l  当删除单链表中的最后一个元素时,要从头指针开始,一直遍历直到倒数第二个元素,将倒数第二个元素(结点)指向NULL,释放原末端元素(结点)空间后,将尾指针等于新的末端元素(结点)。与链表长度有关

1

2

3

4

5

ListNode *p = head;

while(p->next != rear) p = p->next;

p->next = NULL;

free(r);

r = p;

l  在单链表第一个元素前插入一个新元素时,只需要把新的元素(结点)指向原来的第一个元素(结点),然后使头指针指向新的元素(结点)。与链表长度无关

1

2

new_point->next = head->next;

head->next = new_point;

l  在单链表最后一个元素后插入一个新元素时,只需先将新结点指向NULL,然后将尾指针指向的原末端结点指向新的元素(结点),最后将尾指针指向新的元素(结点)。与链表长度无关

1

2

3

new_point->next = NULL;

rear->next = new_point;

rear = new_point;

 

n个结点的二叉链表共有2n链域,除了根节点以外,其他每个节点都被一个链域所指向,因此用到的链域为n-1个,即空链域个数为:2n - ( n-1) = n+1

一个二叉树通过如下的方法穿起来:所有原本为的右(孩子)指针改为指向该节点在中序序列中的后继,所有原本为空的左(孩子)指针改为指向该节点的中序序列的前驱。

左孩子域:0:指示左孩子1:指示节点前驱。右孩子域:0:指示右孩子,1:指示节点后继。

 

 

 

 


 

递归先序遍历一个节点为n,深度为d二叉树,需要空间的大小为O(d)

二叉树并不一定是平衡的,也就是深度d!=logn,有可能d>>logn。从第1层到第d-1层,每次都要压一个有孩子结点入栈,所以要d-1次,栈大小应该是O(d)。

 


如下C++程序:

int i=0x22222222; 

char szTest[]="aaaa"; 

//a的ascii码为0x61 

func(I, szTest);    

//函数原型void func(int a,char *sz);

 

刚进入func函数时,参数在栈中的形式可能为(左侧为地址,右侧为数据):

0x0013FCF0     0x0013FCF8

0x0013FCF4     0x22222222

0x0013FCF8     0x61616161


 

函数参数入栈顺序为从右到左;栈的存储从高地址到低地址。

 

给出一个中缀表达式:a+b*c-(d+e),求前缀和后缀表达式:

按照运算符的优先级对所有的运算单位加括号:式子变成:((a+(b*c))-(d+e))

前缀表达式:把运算符号移动到对应的括号前面:-( +(a *(bc)) +(de)),把括号去掉:-+a*bc+de

后缀表达式:把运算符号移动到对应的括号后面:((a(bc)* )+ (de)+ )- ,把括号去掉:abc*+de+-

 

表达式的前缀形式为"+-*^ABCD/E/F+GH",它的中缀形式为A^B*C-D+E/(F/(G+H)):

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 op 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。

题目中的前缀形式为:+-*^ABCD/E/F+GH,根据从右至左扫描计算过程如下:

1) 首先扫描H为数字,入栈 ,栈中为: H

2) 扫描G为数字,入栈 ,栈中为:G,H

3) 扫描+为运算符,依次弹出G ,H ,得到 G+H 的结果 入栈,栈中为:G+H

4) 扫描 F 为数字,入栈,栈中为F, G+H

5) 扫描 / 为运算符, 依次弹出F, G+H,计算F/(G+H) 的结果入栈 ,栈中为 F/(G+H)

6) 扫描 E 为数字,入栈,栈中为E,  F/(G+H)

7) 扫描 / 为运算符, 依次弹出E, F/(G+H) ,计算 E/(F/(G+H))

8) 扫描 D 为数字,入栈 栈中为:D, E/(F/(G+H))

9) 扫描 C 为数字,入栈 栈中为:C,D, E/(F/(G+H))

10) 扫描 B 为数字,入栈 栈中为:B,C,D, E/(F/(G+H))

11) 扫描 A 为数字,入栈 栈中为:A,B,C,D, E/(F/(G+H))

12) 扫描^ 为数字,依次弹出 A,B 计算A^B的结果入栈, 栈中为:A^B ,C,D, E/(F/(G+H))

13) 扫描*为数字,依次弹出 A^B,C 计算A^B*C的结果入栈, 栈中为:A^B* C,D, E/(F/(G+H))

14) 扫描-为数字,依次弹出A^B*C,D,计算A^B*C-D的结果入栈, 栈中为A^B* C-D, E/(F/(G+H))

15) 扫描+为数字,依次弹出 A^B*C-D, E/(F/(G+H))   计算 A^B*C-D+  E/(F/(G+H)) ,最后得到的表达式为:A^B* C-D+ E/(F/(G+H))

 

给出入栈序列,判断出栈序列:依次判断每个元素,在原序列中排在它前面的,必须是逆序

入栈序列为A B C D E  F,则不可能的输出序列为BCEAFD:E后面不可以是AD必须是DA。

一个空栈,有顺序输入序列:a1,a2,a3...an(个数大于3),而且输出第一个元素为 a(n-1), 那么所有元素都出栈后,a(n-2) 一定比 a(n-3) 先出:

a1~an必须按顺序入栈,但是第一个出栈的是an-1,则可以推测a1-an-1都是顺序入栈,而an可以在a1~an-1任意一个元素出栈的过程入栈,所以an输出的顺序不能确定,但是可以确定已经入栈的元素出栈的先后顺序。

 

以下对象是分配在上的:函数内局部变量、函数内局部指针变量、函数内指向动态申请的对象的局部指针变量。new出来的对象是分配在堆上,函数内动态申请的对象分配在上。

 

关于堆和栈的区别:

堆:自己做菜自己吃,什么时候收盘子自己知道,但是可能会浪费(产生碎片),因为可能自己一个人吃不完。 

桟:吃饭由食堂工作人员帮你打饭和分配位置,吃完了工作人员帮你收盘子。不可能浪费粮食(碎片)。

堆内存实际上指满足堆内存性质的优先队列的一种数据结构,第1个元素有最高的优先权 ;

栈内存实际上就是满足先进后出的性质的数学或数据结构。

堆内存由程序员自己来分配,栈内存由操作系统自动分配,只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

栈的大小是固定的,堆的大小受限于系统中有效的虚拟内存.

l  申请方式不同:栈由系统自动分配,而堆是人为申请开辟的;

l  申请大小不同:栈获得的空间较小,而堆获得的空间较大;

l  申请效率的不同:栈由系统自动分配,速度较快,而堆一般速度比较慢;

l  存储内容的不同:栈在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数各个参数进栈,其中静态变量是不进栈的,而堆中一般是在头部用一个字节存放堆的大小,堆中的具体内容是人为安排的;

l  底层不同,连续的空间,而不连续的空间。

生长方向是向上的,即向着内存地址增大的方向增长;生长方向是向下的,即向着内存地址减小的方向增长。

 

在栈中,栈底保持不变:堆栈初始化时设置的堆栈的底不会改变。栈底固定,而栈顶浮动;栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。

 

设指针变量top指向当前链式栈的栈顶,则删除栈顶元素的操作为top=top->next; 若顺序栈则为top=top-1;

 

a - (b * c + d) / e的后缀表达式是abc*d+e/- 分析顺序如下:

bc*

bc*d+

bc*d+e/

abc*d+e/-

 

高级语言通过编译或者即时编译 (JIT) 后成为汇编语言被机器装载执行?错误:机器可以执行的是二进制的机器语言。

根据栈的定义可知,栈只可以访问栈顶元素,不可以访问栈底元素。

中缀表达式转换为等价后缀表达式:

操作数的顺序不会发生变化,只有运算符的顺序可能发生变化。同时后缀表达式没有括号。所以在转换的过程中,只要碰到操作数可以直接输出,而遇到运算符和括号进行相应的处理即可。

从左到右读取中序表达式。若读取的是操作数则直接输出。若读取的是运算符,分三种情况:

l  该运算符为左括号( ,则直接存入堆栈。

l  该运算符为右括号),则输出堆栈中的运算符,直到取出左括号为止。

l  该运算符为非括号运算符,则与堆栈顶端的运算符做优先权比较,若较堆栈顶端运算符高或者相等,则直接存入堆栈;若较堆栈顶端运算符低,则输出堆栈中的运算符。

当表达式已经读取完成,而堆栈中尚有运算符时,则依次序取出运算符,直到堆栈为空。

 

中缀表达式 3*2^(4+2*2-6*3)-5 进行求值,求值过程中当扫描到6时,对象栈和算符栈分别为3,2,8; *^(-

 

 

LRU的过程如下(访问的频率越高越不该丢弃):

1. 新数据插入到链表头部;2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;3. 当链表满的时候,将链表尾部的数据丢弃。

 

已知操作符包括‘ +’、‘ -’、‘ *’、‘ /’、‘ (’和‘ )’。将中缀表达式 a+b-a*((c+d)/e-f)+g 转换为后缀表达式 ab+acd+e/f-*-g+时,用栈来存放暂时还不能确定运算次序的操作符。若栈初始时为空,则转换过程中同时保存在栈中的操作符的最大个数是5:

优先级从低至高:(、+-、*/、^
遇+,入栈,1个
遇-,-的优先级不大于+,弹出+,压入-,1个
遇*,*的优先级大于-,入栈,2个
遇(,入栈,3个
遇(,入栈,4个
遇+,+的优先级大于(,入栈,5个
遇),弹出+,弹出(,3个
遇/,/的优先级大于(,入栈,4个
遇-,-的优先级不大于/,弹出/,压入-,4个
遇),弹出-,弹出(,2个
遇+,+的优先级不大于*,弹出*,压入+,2个
操作符遍历完毕,栈中最后自底而上为-和+,依次弹出。

队列

最大容量为n的循环队列,队尾指针rear,队头指针front,则队满的条件是:(rear+1) %n==front

注意rear在+1后与front比较,且%取模运算不是除法。

 

输入受限的双端队列是指只能从队列一端输入,可以从两端输出;输出受限是指只能从队列一端输出,可以从两端输入。

 

单向队列在允许删除的一端叫队头,在允许插入的一端叫队尾

 

开源软件中经常被用作队列的是Redis和kafka

 

对序列(12 , 13 , 11 , 18 , 60 , 15 , 7 , 19 , 25 , 100)用筛选法,从60开始建初始堆

有n个元素的序列,若使用筛选法建堆,则从位置为n/2下整的元素开始建堆。

 

最适合创建一个优先级队列的数据结构:

 


循环队列存储在一维数组A[0..n-1]中,队列非空时 front 和 rear 分别指向队头和队尾。若初始时队列为空,且第 1 个进入队列的元素存储在A[0]处,则初始时 front的值是0rear 的值是n-1

最初只有一个元素时:

反推初始值,front不用动,但是rear要退一个位置。按照循环,第一个元素位置可以直接到最后一个位置上,因此在数组中初态就是0,n-1.

 

为什么front初态不在最后一个位置:为了操作的统一性,进队时并不更新front,所以front初态在第一个元素的位置,即下标为0处。


 

与栈不同,队列进出的顺序不会因为进出的交替进行而有所改变,只会先进先出。

 

顺序循环队列 Q 空的条件是: Q.front==Q.rear:

为了方便起见,约定初始化建空队时 front=rear=0, 只凭front=rear无法判断队空还是队满。有两种方法处理:1)另设一个标志位以区别队列是空还是满。2)少用一个元素空间,以队列头指针front在队尾指针rear的下一个位置上作为队列“满”状态的标志。队空时 front=rear, 

队满时:(rear+1)%maxsize=front,front指向队首元素,rear指向队尾元素的下一个元素。

 

循环队列:最后一个单元的后继是第一个单元的队列。

入队:将新元素插入rear所指的位置,然后rear加1。
出队:删去front所指的元素,然后加1并返回被删元素
◆ 取队首元素:返回fornt指向的元素值

 

用链接方式存储的队列,在进行删除运算时头、尾指针可能都要修改:

l  当有多于一个节点时,链表表示的队列的删除操作只需要修改头指针即可,将头指针定义为head=head.next  此时不需要修改尾指针;

l  当队列只有一个节点时,该节点既是头又是尾,如果head==tail需要修改尾指针将队列置空。

 

循环队列的存储空间为 Q(1:40) ,初始状态 front=rear=40 。经过一系列入队与退队操作后,

 front=rear=15 ,此后又退出一个元素。则循环队列中元素个数为39,或0且产生下溢错误

front==rear代表队列或者。又退出一个数据,当为空的时候会产生下溢为满的时候就是总个数-1也就是39个。

 

在循环队列中, 若尾指针 rear 大于头指针 front, 其元素个数为 rear- front:

rear大于front时rear-front必为正数不需要加上m再求余数。

 

一个虚拟存储系统,进程在内存中占3页(开始时内存为空),采用先进先出页面淘汰算法,当执行如下访问页号序列后1,2,3,4,5, 1,2,5,1,2,3,4,5,会发生10次缺页:

FIFO,发生缺页时的调入顺序即为淘汰顺序

1、访问1,缺页,调入1,内存中为    1, ,;

2、访问2,缺页,调入2,内存中为   1,2,;

3、 访问3,缺页,调入3,内存中为 1,2,3;

4、 访问4,缺页,调入4,淘汰1,内存中为 4,2,3;

5、 访问5,缺页,调入5,淘汰2,内存中为 4,5,3;
6、 访问1,缺页,调入1,淘汰3,内存中为 4,5,1;

7、 访问2,缺页,调入2,淘汰4,内存中为 2,5,1;

8、 访问5,不缺页,内存中为 2,5,1;

9、 访问1,不缺页,内存中为 2,5,1;

10、 访问2,不缺页,内存中为 2,5,1;

11、访问3,缺页,调入3,淘汰5,内存中为 2,3,1;

12、访问4,缺页,调入4,淘汰1,内存中为 2,3,4;

13、访问5,缺页,调入5,淘汰2,内存中为 5,3,4;

 

大小为MAX的循环队列中,f为当前对头元素位置,r为当前队尾元素位置(最后一个元素的位置),则任意时刻,队列中的元素个数为(r-f+MAX+1)%MAX:

求队列公式(rear-front+Max)% Max 中的 rear指向队尾元素的下一个位置 ,而本题中 r 指向队尾元素位置

 


 

某二叉树的前序序列为 ABCDEFG ,中序序列为 DCBAEFG ,则该二叉树的深度(根结点在第 1 层)为 4:

二叉树的前序序列为 ABCDEFG , A 为根节点。中序序列为 DCBAEFG ,可知 DCB 为左子树节点, EFG 为右子树节点。同理 B 为 C 父节点 ,C 为 D 父节点。同理 E 为 F 根节点, F 为 G 根节点。故二叉树深度为 4 层。

 

二叉排序树的构造:

如果根节点为空,则首先序列的第一个作为根节点,然后第二个与根节点比较:小的放到左边,大的放到右边;

如果​根节点不为空,待插入的节点跟根节点比较:小的往左子树去比较,大的往右子树去比较。比较过程中,待插入节点只作为某节点的左或右节点插入,而不会变动之前的节点位置。

 

在任意一棵二叉树前序序列和后序序列中,各叶子之间的相对次序关系都相同

 

 

B树(或B-树、B_树)是一种适用于外查找平衡多叉树

一棵m阶B树是一棵平衡的m路搜索树。它或者是空树,或者是满足下列性质的树:

1、根结点至少有两个子结点(唯一例外的是根结点就是叶子结点);

2、每个非根节点所包含的关键字个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;

3、除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 满足:┌m/2┐ <= k <= m ;

4、所有的叶子结点都位于同一层

(备注:底函数指派给实数x的是小于或等于x的最大整数。底函数在x的值用x表示。顶函数指派给实数x的是大于或等于x的最小整数。顶函数在x的值用x表示。)

 

策划一个FBI项目(Fast Binary Indexing),其中用到的词汇有6200条,词汇长度在10-15之间,词汇字符是英文字母,区分大小写。下面几个数据结构中TRIE树是检索速度最快的:

二叉搜索树,比较函数开销:1次运算/每字符

哈希表,hash算法开销:10次运算/每字符

链表,比较函数开销:1次运算/每字符

TRIE树,寻找子节点开销:1次运算/每字符

Trie树又称单词查找树,是哈希树的变种。典型应用是统计排序保存大量的字符串(但不仅限于字符串),经常被搜索引擎系统用于文本词频统计。优点是利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。

 

二叉树采用二叉链表表示,若要将其所有节点的左右子树交换位置,则采用后序遍历的方法:

交换左右子树,然后回溯到它们的根,“左右根”正好是后序遍历的顺序。

 

一个具有1025个节点的二叉树,其高度范围在11~1025之间:

最低考虑完全二叉树,最高就成链表了. 刚好是长度.。

 

二叉树中有两个孩子的节点数一定等于叶子节点减一

n个关键字中已知有k个关键字哈希值相同,若用现象探测法将它们存入散列表,至少需要进行k(k-1)/2次探测。

 

C++ STL中unordered_map查找一个数据的复杂度是O(1),map复杂度是O(logn)。

 

由权值分别为3,8,6,2的叶子生成一颗哈夫曼树,它的带权路径长度为35:

先将上列节点按从小到大排序,2, 3, 6, 8,首先选取两个最小的节点2,3构成一个二叉树;然后选取剩下小的一个节点6,和之前的二叉树组成新二叉树;带权路径和2*3 +3*3+6*2+ 8 = 35

 

若有一个叶子结点是二叉树中某个子树中序遍历结果序列的最后一个结点,则它一定是该子树的前序遍历结果序列的最后一个结点:

中序遍历的顺序是左根右前序遍历的顺序是根左右,若中序遍历的最后一个结点是叶子节点的话说明根结点一定存在右子树。而前序遍历的最后一个是叶子结点的话不能保证根节点有右子树。(该叶子结点可能是根结点左子树的)。

 

一个4叉树,度为4的结点个数为6,度为3的节点个数是10,度为2的节点个数是5,叶子节点个数为44:

设度为1的节点个数为x,度为0的节点(叶子节点)为y,则该树的分叉数为4*6+3*10+2*5+x*1。又因为节点数=分叉数+1,则有:6+10+5+x+y= 4*6+3*10+2*5+x*1+1,解得:y=44

 

求下面带权图的最小(代价)生成树时,可能是克鲁斯卡(Kruskal)算法第 2 次选中但不是普里姆(Prim)算法(从 V4 开始)第 2 次选中的边是(V2,V3):

 

Kruskal算法是按权值选边,若选边后不形成回路,则保留作为一条边,若形成回路则除去。

Prim算法是每次从当前的二叉树节点向外延伸的,选择权值最小的边。

Kruskal算法和Prim算法(从V4开始)第1次选中的边都是(v4,v1) 。Kruskal算法第二次可选择(v1,v3), (v2,v3), (v3,v4); Prim算法第二次可选择(v1,v3), (v3,v4)。

 

平衡二叉树一定是二叉排序树

 

最小二叉平衡树节点公式: F(n)=F(n-1)+F(n-2)+1

n个结点的线索二叉树上含有的线索数为 n+l

线索二叉树的线索数为二叉链表中的空链域的值。N个结点的二叉链表共有2n个链域,非空链域为n-1个,空链域有n+1个。

 

二叉树的先序遍历序列和后序遍历序列正好相反,则该二叉树满足的条件是高度等于其结点数:

先序遍历:根左右;后续遍历:左右根。要满足题意,则只有根左<->左根,根右<-->右根,所以高度一定等于节点数。

 

在二叉排序树中插入一个结点最坏情况下的时间复杂度为O(n)

由10个数构造出的Huffman树一共有19个节点:

由10个数构造出的Huffman树,叶子节点有10个,度为2的节点有9个,没有度为1的节点,所以总结点数为19

 

现有一棵无重复关键字的平衡二叉树(AVL  树),对其进行中序遍历可得到一个降序序列,树中最大元素一定是无左子树

 

在一棵高度为2 的 5 阶 B 树中, 所含关键字的个数最少是5:

1.根节点至少有两个孩子节点,那么根节点的关键字至少为1

2.第二层节点(至少2个),每个节点至少有ceil(m/2)=3个孩子节点,那么其关键字至少为2

综上,高度为2的5阶B树,关键字个数至少为1+2+2=5

 

有以下5个叶子节点1,1,3,2,5构成的哈夫曼树带权路径长度为25:

哈夫曼树的带权路径长度只需要计算叶子节点的带权路径长度之和​

先从数组中选出两个最小的数1,1作为两个路径的权值,然后将这两个数相加放到数组中就是2,3,2,5,然后在从中选出最小的两个数2,2作为两个路径的权值。再将这两个数相加放到数组中得到4,3,5.然后在选出两个最小的数4,3作为两个路径的权值。在将这两个数的和放到数组中,得到7,5这就是最后的两个权值。将这些权值全部加起来就是1+1+2+2+4+3+7+5=25,这就是整个路径的长度

 

线索化树中,每个结点必须设置一个标志来说明它的左、右链指向的是树结构信息,还是线索化信息,若 0 标识树结构信息, 1 标识线索,对应叶结点的左右链域应标识为11:

叶子没有子树,所以其左右链域都是线索化信息,并且叶子的左右标志都是1,题目问题问的是叶节点的左右链域,所以就用1标识线索信息。

 

线索二叉树中某结点 R 没有左孩子的充要条件是R.ltag=1:

线索二叉树中左右孩子如果为空会分别指向前后驱节点,所以判定孩子为空的标准就是看此节点是否是左右线索。是左线索则左孩子原本为空。

 

任意二叉树的终端节点(叶子节点)个数 = 度为2的节点数 + 1

 

最佳二叉搜索树是搜索时平均比较次数最少的二叉搜索树。

 

求最小生成树的普里姆(Prim)算法中边上的权可正可负

 

在哈夫曼树中,结点的度可能为0或2。

完全二叉树共有700结点,该二叉树有350个叶子结点:

用完全二叉树的性质 n2=n0-1, 而度为1的有一个或没有。700个结点的话 2n0-1 +n1=700,n1肯定为1 ,要不然左边肯定不是偶数。所以n0=350.

首先根据完全二叉树的定义,通过满二叉树计算得到该完全二叉树的深度n=10,易得到第9层的节点个数为256,深度为9的满二叉树节点个数为511.。由700-511=189得到该完全二叉树第10层节点个数为189,对189除2得到94余1,可以知道第10层已经填满了第9层的94个节点,并在第95个节点下有一个左孩子。那么第9层贡献的叶子节点个数为256-95. 结果为res=256-95+189=350。

下列线索二叉树中(用虚线表示线索),符合后序线索树定义的是D:


 

(1)先忽略虚线部分,只看实线,进行后序遍历,得dbca前驱后继关系

(2)考虑每个结点,当左孩子为空时,指向前驱;右孩子为空时指向后继。(用虚线)

结点d,无左右孩子,则指向前驱null,后继b

结点b,  无左孩子,则指向前驱d

结点c,无左右孩子,则指向前驱b,后继a

结点a,, 有左右孩子。


 

先序序列为a,b,c,d的不同二叉树的个数是14:

由f(0)和f(1)得出f(2),再由f(0)和f(1)和f(2)得出f(3),再由f(0)和f(1)和f(2)和f(3)得出f(4),f(4)就是答案。f(0)=f(1)=1,f(2)=2,f(3)=5,f(4)=14。

根据前序序列的特点:根、左、右,得出以下递推式:

 

其中,n是序列的元素数,f(n)是有n个元素的前序序列的所有可能的二叉树的个数,f(0)=1,f(1)=1。

一个有n个元素的前序序列,其左右子树的结点个数情况分别有:左0右n-1,左1右n-2,左2右n-3,...,左n-1右0,共n种情况;而若左子树有a个结点,右子树有b个结点,则总共有a*b种情况,即所有种情况是左右子树的所有种情况的笛卡儿积。于是,要计算f(n),就要对所有种左右子树结点数的情况进行情况数求和,是先分类再分步的计算。

 

设一棵树 T 可以转化成二叉树 BT ,则二叉树 BT  的根节点一定没有右子树:

将树转成二叉树:1. 将节点的孩子 放在左子树;2. 将节点的兄弟 放在右子树。只有一棵树的话,根节点没有兄弟结点,也就没有右子树。

 

设一课完全二叉树共有999个结点,则在该二叉树中的叶节点个数是500:

n0+n1+n2=999,n2=n0-1,2*n0-1+n1=999,因为为完全二叉树n1只可能为1或者0,若为1,n0不是整数,则只可能为0,求得n0=500。

在具有 2n 个结点的完全二叉树中,叶子结点个数为n。

 

把一棵树转换为二叉树后,这棵二叉树的形态是唯一的。

若一棵具有n(n>0)个结点的二叉树的先序序列后序序列正好相反,则该二叉树一定高度为n:

先序遍历:M-L-R; 后序遍历:L-R-M;只有中间的结点(M)顺序变化了,左右结点相对位置是不变的。要满足先序序列与后序序列正好相反,说明整个二叉树左子树或者右子树有一个没有(遍历就成了,先:M-L ;后:L-M 或者  先:M-R ;后:R-M )也就是必然是一条链

 

将一棵有100个结点的完全二叉树从根这一层开始,开始进行层次遍历编号,根节点为1,那么编号最小的叶节点的编号为51:最小编号的叶子一定是最后一个节点的父节点的右兄弟节点,最后节点编号100,父节点编号为50,所以答案是51

设一棵m叉树中度数为0的结点数为N0 ,度数为1的结点数为Nl ,……,度数为m的结点数为Nm,则N0=l+N2+2N3+3N4+……+(m-1)Nm

m叉树总的指针数为N1 + 2N+ ...+mNm总的节点数为N 0 +N1 + N+ ...+Nm ,需要的指针数为N 0 +N1 + N+ ...+Nm -1(树中的指针数会比节点数少一个,因为其他节点都会被指向,而根节点不会)以上两式相等得出N 0 =1+ N+ 2N3...+(m-1)Nm

­­(这种题最快的方法是将题目实例化:比如这是一棵二叉树,然后随便画一棵,统计度数为0、1、2的个数,然后对照答案,找出正确的算式。)

 

设高度为h(根的层次为1)的二叉树上只有度为0和度为2的结点,则此类二叉树中所包含的结点数至少为2h - 1:

对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。

由题意 该二叉树只有度为0和度为2的结点,则最下层之上每层至少有一个度为2的结点,即 n>= h-1,  总结点数 = n0+n= 2n2+1 >= 2(h-1)+1=2h-1 

 

设森林F中有三棵树,第一,第二,第三棵树的结点个数分别为M1,M2和M3。与森林F对应的二叉树根结点的右子树上的结点个数是M2+M3:

森林转换为二叉树: 1.将森林中每棵树转换为相应的二叉树 2.第一颗二叉树不动,依次把后一棵二叉树的根节点作为前一棵二叉树根节点的右孩子,所有二叉树链在一起。

 

某二叉树结点的中序序列为BDAECF,后序序列DBEFCA,则该二叉树对应的森林包括3棵树:

二叉树到森林:二叉树中节点的左孩子是森林中该节点的孩子,右孩子是森林中该节点的兄弟。

 

 

二叉排序树中的最小值可能在叶子节点,也可能在根节点,也可能在只有右孩子的父节点:

若根节点有左孩子,则必然根节点不是最小值;若根节点有右孩子,则必然根节点小于右孩子这个叶子结点;当根节点只有右孩子时,可能为根节点,即为只有右孩子的父节点;当根孩子只有左孩子,可能为叶子结点。当根节点只有右孩子时,可能为根节点,即为只有右孩子的父节点;当根孩子只有左孩子,可能为叶子结点。

 

二叉树的前序遍历是:-+abc*de/f,后序遍历是:bad*c+f/e-,则层序遍历为-+eac/b*fd、中序遍历为ab+d*c-ef/

 

哈夫曼树中,所有的字符串结点都是和其他字符串结点或者权值结点构成子树,因此不可能存在度为1的结点。

 完全二叉树意为前n-1层为满二叉树,最后一层连续缺失右边结点的二叉树,而哈夫曼树无法保证最后一层连续缺失右边结点以及前n-1层为满二叉树。

 

具有八个结点的二叉树共有1430种:卡特兰数:

平衡二叉树中插入一个结点后造成了不平衡,设最低的不平衡结点为A,并已知A的左孩子的平衡因子为0右孩子的平衡因子为1,则应作RL型调整以使其平衡。

LL就是在左子树的左孩子下插入,LR就是在左子树的有孩子下插入,RR就是右子树的右孩子下插入,RL就是在右子树的左孩子下插入。

平衡因子为1表示:左孩子 - 右孩子的高度 = 1

平衡因子为0:左孩子高度 = 右孩子高度

平衡因子为-1:右孩子高度 - 左孩子高度 = 1

A点右孩子的平衡因子是1 => 右孩子的左孩子高度多一层 属于RL型

1. LL型调整:在B点的左子树上插入一个节点。插入后B点的左子树的平衡因子变为1,A节点的平衡因子变为了2。这样可以看出来A节点为根节点的子树是最小不平衡子树。调整时,将A的左孩子B向右旋转代替A称为原来不平衡子树的根节点,将A的节点右下旋转称为B的右子树的根节点,而B的原右子树变为A的左子树。

2. RR型调整:在A节点的右孩子的右子树上插入节点,使得A节点的平衡因子由-1变为-2而引起的不平衡所进行的调整操作。

3. LR型的调整:在A节点的左孩子的右子树上插入节点,使得A节点的平衡因子由1变为了2而引起的不平衡所进行的调整的操作。

 

123个结点的完全二叉树采用顺序存储,从1开始按层次编号,则编号最小的叶子结点编号是62:

1、设度为0的结点数目为n0,度为1的结点数目为n01,度为2的结点数目为n2,则有n2=n0-1。n1的值为0或1,123为奇数,因此n1为0,n0为62,n2为61。

2、层数为6的满二叉树的结点个数为63,因此第七层的结点个数为60。而由1中求得的n0为62,因此在第六层中有2个叶子结点。而前六层中的总结点数为63,因此编号最小的叶子结点为第六层的倒数第二个结点,即编号为62。

 

从根分别到达两个叶结点路径上的权值序列,能属于同一棵哈夫曼树的是24,10,5 和 24,14,6:

首先根据两个叶子,以及访问到叶子的前一个结点,这个结点一定是叶子的父亲结点。再根据哈夫曼树的结点一定有兄弟,即不存在度为1的结点。因此可以知道兄弟的权值,给定的一个序列就可以推出两个叶子,两个序列推出四个叶子,这样就可以根据是否选择最小的两个叶子结点组合在一起作为判据,决定这个序列是否成立。


 

 

 

 

由第一个序列的10,5推出另一个叶子也是5,它们的父亲是10。两个叶子形成的结点有个权值为14的兄弟,但是不知道是叶子结点还是一个由叶子形成的结点。再看第二个序列知道叶子结点6和父亲14,可知有个叶子兄弟是8,这个权值是14的结点刚好可以和第一个结合成兄弟,且父亲为24,满足要求。


 


设一棵m叉树中有N1个度数为1的结点,N2个度数为2的结点,……,Nm个度数为m的结点,则该树中的叶子结点个数为: :

假设叶子结点数为n0,并假设树的结点数为N,N = n0+n1+n2+...+nm
N = n1+2*n2+3*n3+...+m*nm+1
这样得到n0+n1+n2+...+nm = 1+n1+2*n2+3*n3+...+m*nm
即得:n0 = n2+2*n3+3*n4+...+(m-1)*nm+1

二叉排序树查找关键字为35的节点,则依次比较关键字有可能是{46,36,18,28,35}:

快速解题思路:任意一个节点之后的所有节点,要么全比它大,要么全比它小

二叉排序树查找过程,类似于折半查找,都是不断缩减查找的范围,比如:a[0]-a[n-1]有序(从小到大),所以刚开始的查找范围为【a[0], a[n-1]】.通过跟根结点比较,假设根结点为a[i],要查找的元素为x,如a[i]<x  则在右子树查找范围为【a[i],  a[n-1]】  若a[i]>x  则在右子树查找范围为【a[0],  a[i]】由此得知查找的范围都是在逐渐减小的。令a[0]=-∞   a[n-1] =+ ∞,只要满足起始查找范围为(-∞,+∞)且查找范围不断缩小的就是正解

 

B+树B树都是平衡的多分树,它们都可以用于文件的索引结构。

B+树不同于B树的特点之一是能支持顺序查找

B+树是有序的树,所有叶结点中包含了全部的关键字信息,且叶结点本身依关键字从小到大顺序链接,可以进行顺序查找,而B树不支持顺序查找(只支持多路查找)。

 

关于B树:

1. N阶B树就是一棵N叉树,每个节点都对应着一个数值区间,兄弟节点的区间是相邻的,孩子节点是对父节点区间的分割;

2. 节点的关键字就是本区间上的一个分割点,是直接使用插入的元素值来作为分割点的,N叉搜索树的一个节点最多有N-1个关键字;

3. B树节点的空间利用率保持在50%~100%,这是通过节点的分裂和合并来实现的,插入时空间满了就分裂,删除时少于50%就合并(根节点除外,因为当有元素插入时,如果所有节点都满了,当前的根节点就会诞生出2个新的孩子节点,并把节点内容转移到这两个孩子中去,此时树高+1);

4. 此外,B树的所有叶子都保持在同一层(这需要去把整个插入删除、分裂合并的过程认真过一遍才能明白);B树保证了动态性能和查找性能(树是整体平衡的)

 

B+树:在B树基础上进行扩展。B+树的元素值统一存储在叶子上,树的中间节点仅作索引之用;B+树的叶子节点添加指针域,链接相邻叶子,使之构成顺序结构。B+树优化了 “排序需求” 和 “范围查找和输出”(只要找到两个端点,然后在叶子链表上从前往后依次输出)

 

B*树:在B+树基础上进行扩展。在中间节点上额外添加指针域,指向兄弟节点;增加元素时,如果当前节点已满,若兄弟节点有空间就挤出部分数据到兄弟节点中,以腾出空间供插入;若兄弟节点也满则分裂:创建新节点,分担当前节点的1/3数据和兄弟节点的1/3数据,这样当前节点、兄弟节点和新节点就都有2/3的空间利用率)B*树优化了空间利用率(从50%至66.7%)

B*树

基于哈希的索引和基于树的索引的区别:

l  hash索引仅满足“=”、“IN”和“<=>”查询,不能使用范围查询:

经过hash算法处理后的hash值的大小关系,并不能保证与处理前的hash大小关系对应,因此只能进行等值的过滤,不能基于范围的查找

l  hash索引无法被用来进行数据的排序操作:

hash索引中存放的都是经过hash计算之后的值,而hash值的大小关系不一定与hash计算之前的值一样,所以数据库无法利用hash索引中的值进行排序操作。

l  对于组合索引,Hash索引在计算Hash值的时候是组合索引键合并后再一起计算Hash值,而不是单独计算Hash值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash索引也无法被利用

l  Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高:

对于选择性比较低的索引键,如果创建 Hash 索引,将会存在大量记录指针信息存于同一个 Hash 值相关联。要定位某一条记录时会浪费多次表数据的访问,造成整体性能低下。

 

已知三叉树T中6个叶结点的权分别是 2, 3, 4, 5, 6, 7,T 的带权(外部)路径长度最小是46:

(2+3)*3+(4+5)*2+6+7=46

 

设一棵三叉树中有2个度数为1的结点,2个度数为 2 的结点,2个度数为3的结点,则该三叉链权中有7个度数为0的结点:

三叉树结点的度数均不大于3, (1) 结点总数应等于i度结点数(记为ni)和:N=n0+n1+n2+n3 。(2) i度结点有i个孩子,根结点不是任何结点的孩子,结点总数为:N=n1+2n2+3n3+1

由 (1)(2)​得到:n0=n2+2n3+1=2+2*2+1=7。

 

在一颗度为4的树T中,若有20个度为4的结点,10个度为3的结点,1个度为2的结点,10个度为1的结点,则树T的叶结点个数是82:

任何一棵树中,结点个数比分支个数多一。分支个数等于20*4+10*3+1*2+10*1=122,所以这棵树一共有123个结点。度不为零的结点数目为20+10+1+10=41,所以叶子结点也就是度为零的结点个数为123-41=82。

 

给定n个带权结点,其Huffman树的结构不唯一

 

一颗完全二叉树第六层有8个叶结点(根为第一层),则结点个数最多有111个:

完全二叉树第六层8个叶结点,则第七层缺少16个结点不满,结点个数等于2^7 -1-16=111。

 

任何一个带权的无向连通图最小生成树有一棵或多棵:

最小生成树是在连通图的一个概念,保留全部节点,选择边的一个子集,使得结果仍然连通,但是所有保留边的权重和最小。但是有可能出现的情况是保留的边不同,而权重和一样。

 

二叉树在线索化后,仍不能有效求解的问题是后序线索二叉树中求后序后继:

前序求后继,后序求前驱,中序啥都行。

下列二叉树中,可能成为折半查找判定树(不含外部结点)的是A:

A           B     C       D

折半查找树的特点在于其中序遍历是升序序列,因此相比于二分查找,折半查找二叉树的优点在于不用自己找中点,而是直接将要查找的关键字与根节点相比,小的话再和根节点的左子节点(又是相应的左子树中点)比较,大的话则和根节点的右子节点比较。

这道题的解题思路就在于向上或向下取整的问题,也就是如果升序序列是偶数个,那么中点应该偏左多右少还是左少右多,但是应该进行一个统一。B和C选项中间的对称部分明显就是选择了不同的策略,D则由根节点左子树4个节点而点右子树5个节点可以确定用的是向下取整策略,但是它的左子节点在左子树中对应的中点左边2个数,右边1个数,明显是向上取整策略,策略没有统一,所以错。

 

平衡二叉树中插入一个结点后造成了不平衡,设最低的不平衡结点为A,并已知A的左孩子的平衡因子为-1,右孩子的平衡因子为0,则应作LR型调整以使其平衡:

空树是一棵平衡二叉树,平衡因子为0。结点A没有右孩子结点(右孩子的平衡因子为0),而A的左孩子节点的左子树为空,有一个右孩子节点(A的左孩子的平衡因子为-1)

1.插入点位于x的左孩子的左子树中。   左左LL   右旋

2.插入点位于x的左孩子的右子树中。   左右LR   较低的先左旋,转化为LL问题,再右旋
3.插入点位于x的右孩子的左子树中。   右左RL    较低的先右旋,转化为RR问题,再左旋
4.插入点位于x的右孩子的右子树中。   右右RR   左旋

 

已知一棵有2011个结点的树,其叶结点个数为116,该树对应的二叉树中无右孩子的结点个数是1896:

树中每一个分支结点的所有子结点中的最右子结点无右孩子。树转换为的二叉树没有右孩子,就说明在原来的树结构该结点没有右兄弟,即该节点是其父节点最右边的孩子。2011-116=1895。 此外,根节点没有父节点当然也没有兄弟,因此也没有右孩子。所以+1=1896。

普通树转化为二叉树后,度为1的结点只有左孩子而无右孩子,问题就可转化为求叶子结点(度为0)加上度为1结点的总数,即n0+n1。 由二叉树公式:n=n0+n1+n2,且n2=n0-1,所以n0+n1=n-n0+1,即2011-116+1=1896。

 

由树转化成二叉树,该二叉树的右子树一定为空,森林转化为二叉树,右子树不为空。

 

 

 

如果约定树中结点的度数不超过2,则它实际上就是一棵二叉树(错误):

二叉树度为2,子树有左右之分,次序不能颠倒。因此仅仅说度不超过2不能说是一颗二叉树。

对于含有 n 个元素的子集树问题,最坏情况下其解空间叶结点数目为2n

当所给问题是从N个元素的集合S中找出满足某种性质的子集时,相应的解空间称为子集树。所有可能解的数目是组合数求和 C0n + C1n + ... + Cnn = 2n 。

 

将森林转换为对应的二叉树,若在二叉树中,结点X是结点Y的父结点的父结点,则在原来的森林中,X和Y可能具有兄弟关系或父子节点:

 

已知中序遍历的序列为abcdef,高度最小的可能的二叉树的叶子是ace、acf、adf:

高度最小的二叉树是平衡二叉树,则根节点为c或者d。当c为根节点时,左子树的叶子节点可能是a或者b中的一个,右子树的叶子节点是d和f,可能是adf或者bdf;当d为根节点时,左子树的叶子节点可能是a和c,右子树的叶子节点是e或f,可能是ace或者acf。

 

若一个具有N个结点M条边无向图构成一个森林,(N>M), 则该森林必有N-M棵树:

设该森林共有m棵树,每棵树有ni(1≤i≤m)个节点,依据树的性质有N=n1+n2+…+nm,

M=(n1-1)+(n2-1)+…+(nm-1)。则N-M=1+1+…+1=m。而m就是树的个数,即N-M棵树。

 

某二叉树结点的中序序列为A、B、C、D、E、F、G、H,后序序列为B、D、C、A、F、G、H、E。该二叉树的层次次序序列为E、A、H、C、G、B、D、F。

 


一棵3阶B树如图所示。删除关键字78得到一棵新 B 树,其最右叶结点所含关键字是65:

n=ceil(3/2)-1 。所以将左节点最大关键字62上移,同时将双亲结点中大于62的关键字(65)下移至删除结点位置。

 


在B-树叶结点上删除一个关键字的方法:

首先将要删除的关键字 k直接从该叶子结点中删除。然后根据不同情况分别作相应的处理:

(1)如果被删关键字所在结点的原关键字个数n>=ceil(m/2)(即向上取整),说明删去该关键字后该结点仍满足B-树的定义。这种情况最为简单,只需从该结点中直接删去关键字即可。

(2)若被删关键字所在结点的关键字个数n=ceil(m/2)-1,说明删去该关键字后该结点将不满足B-树的定义,需要调整。如果其左右兄弟结点中有“多余”的关键字,即与该结点相邻的右(左)兄弟结点中的关键字数目大于ceil(m/2)-1。则可将右(左)兄弟结点中最小(大)关键字上移至双亲结点。而将双亲结点中小(大)于该上移关键字的关键字下移至被删关键字所在结点中。

 

对任意一棵二叉树T,H(T)表示树的高度。若树T含有n个结点,那么H(T)≥O(logn)。

 

如果二叉树中某结点的度为1,则说该结点只有一棵子树:

树的深度:树中最大的结点层;结点的度:结点子树的个数;叶子结点:度为 0的结点;

 

一棵完全二叉树第六层有9个叶结点(根为第一层),则结点个数最多有109:

第一层1,第二层2,第三层4,第四层8,第五层16,第六层32,第七层:2*(32-9)。

 


根据使用频率为 5 个字符设计的哈夫曼编码不可能是00,100,101,110,111:

哈夫曼树的节点要么是叶子节点,要么是度为2,不可能出现度为1的节点。

 


将有关二叉树的概念推广到三叉树,则一棵有244个结点的完全三叉树的高度为6:

等比数列求和  s=a1(1-q^n)/1-q。a1=1,q=3,∴s=(3^n-1)/2,得到n=6

 

将一棵树t 转换为孩子—兄弟链表表示的二叉树h,则t的后根序遍历是h 的中序遍历:

树转化为二叉树,前根遍历对应二叉树的先序遍历,后根遍历对应二叉树的中序遍历。

 

红黑树是一种自平衡二叉树,自平衡的意思是能自己调整树高。平衡二叉树也是自平衡二叉树。 但红黑树不一定是平衡二叉树。

 

一棵树(>=3个节点)最少需要删掉1个节点才能使得这棵树不连通:

删掉同时拥有父节点和子节点的那个节点,或者是同时拥有左右子节点的那个节点。

 

对于有10个结点的二叉树,若先序遍历序列与层次遍历序列相同,这棵二叉树是一棵单支树

 

中序线索化即根据中序遍历的顺序,找出该字母的前驱和后继。中序遍历结果为debxac。x的前驱是b,后继是a。所以结点x的左、右线索指向的结点分别是b,a。

 

任何一个带权的无向连通图最小生成树可能有多棵。

从连通图中不同顶点出发或从同一顶点出发按照不同的优先搜索过程可以得到不同的生成树。

 

含有n个叶子结点的最优二叉树中共有n-1个分支结点。

最优二叉树(哈夫曼树)中只有度为2和0的结点,因此,其节点总数为2n-1。而其中叶子节点是n,则非叶子节点是2n-1-n=n-1。

 

若某完全二叉树结点个数为100,则第60个结点的度为0:

节点为n时,左孩子节点为2n,右孩子节点为2n+1。第60个节点左孩子为2*60=120,120大于100,总结点才100个,所以60节点肯定没有孩子。

 

若平衡二叉树的高度为6,且所有非叶结点的平衡因子均为 1,则该平衡二叉树结点总数为20:

平衡二叉树的最小结点数:f(n)=f(n-1)+f(n-2)+1。斐波那契数列,括号中的数代表深度,1是根节点,f(n-1)是左子树的节点数量,f(n-2)是右子树的节点数量。f(1)=1, f(0)=0。

从下往上数:深度为2时,f(2)=f(1)+f(0)+1=2;f(3)=f(2)+f(1)+1=4;f(4)=7;f(5)=12;f(6)=20。

 

一个二叉树有N个度为2的节点,叶节点的数目为N+1:

除根节点外,每个度为 2 的节点都贡献一个叶节点。而根节点贡献两个叶节点。

 

求N个节点完全二叉树的叶子节点数:N为偶数,叶子节点为N/2;N为奇数,叶子节点为N/2+1

 

如果T1是由有序树T转换而来的二叉树,那么T中结点的前序就是T1中结点的前序,T中结点的后序就是T1中结点的中序。

 

深度优先搜索类似于二叉树的先序遍历;广度优先搜索类似于二叉树的层序遍历。

 

向二叉排序树中插入一个结点需要比较的次数小于等于该二叉树的高度。

若X是后序线索二叉树中的叶结点,且X存在左兄弟结点Y,则X的右线索指向X的父结点:

后序遍历左右根,X左线索指前驱,右线索指后继,X有左兄弟,则X的后继是父结点

 

若初始森林中共有n棵二叉树,最终求得的哈夫曼树共有2n-1个结点:

n个叶子的哈夫曼树要经过n-1次合并,产生n-1个新结点。

 

二叉树线索化后,先序与后序线索化最多有1个空指针域,中序线索化最多有2个空指针域。

 

n 个节点的满二叉树调整成一个最小堆的最优复杂度为O(N):

N个节点,分支节点N/2,每个分支最多进行两次比较和互换操作,构建过程时间复杂度为N

 

二叉树上结点的左子树深度减去其右子树深度称为该结点的平衡因子。平衡二叉树中任意结点的平衡因子只能是-1, 0, 1

 

一棵有15个节点的完全二叉树和有15个节点的普通二叉树,叶子节点的个数最多会差7个:

节点数为n的完全二叉树的叶子节点数:n/2【n为偶数】,(n+1)/2【n为奇数】。15个节点的完全二叉树一共有8个叶结点,而普通二叉树最少可以有1个叶结点。相差8-1=7。

 

若X是二叉树中序线索树中一个有左孩子的结点,且X不为根,则X的前驱为X的左子树中最右结点。

 

高度为1的平衡二叉树节点为1个,高度为5的最少12个:高度与最小节点数的对应关系:1 -> 1,2 -> 2,3 -> 4,4 -> 7,5 -> 12,F(N) = F(N-1)+ F(N-2)+1,F(0)=0,F(1) = 1。

 

二叉树的三种遍历算法区别仅在于对树根、左右子树访问先后顺序的不同(错误):

根不同,左右总是先左后右。本质上三种遍历都是基于DFS的遍历,只是打印输出顺序不同。

 

B+树是应文件系统所需而产生的B-树的变形,B+树适用于实际应用中的操作系统的文件索引和数据库索引,磁盘读写代价更低,查询效率更加稳定。B+树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入,这与二叉树恰好相反。

 

某二叉树的先序序列和后序序列正好相反,则该二叉树一定是高度等于其结点数。

 

给定一棵二叉搜索树前序和后序遍历结果,可以确定这棵二叉搜索树。

 

当向二叉排序树中插入一个结点,则该结点一定成为叶子结点

二叉排序树中插入的关键字均存储在新创建的叶子上,由于插入位置总是在空指针域上,因此空指针域上链接的一个新结点必为叶子结点。

 

 

 

 

度为m哈夫曼树中,其叶结点个数为n,则非叶结点的个数为(n-1)/(m-1):

一般所说的哈夫曼树是指最优二叉树,也叫严格二叉树(不是完全二叉树),但是哈夫曼树也存在于多叉树中,即度为m的哈夫曼树,也叫最优m叉树,严格m叉树。这题表示哈夫曼树的节点 的度要么是0要么是m。设度不为0(即非叶结点 )的个数为X,则总的结点数为:X+n。
除根结点外,其余的每一个结点都有一个分支连向一个结点,对于度为m的每个结点都有m个分支,而度为0的结点是没有分支的,所以从分支的情况来看总的结点数位:X*m + 1(这里的1为根结点)两者相等,所以X=(n-1) / (m-1)。

 

一棵深度为4的三叉树,最多有40个节点:

根节点深度为1,深度为n的满三叉树的总结点数: , f(4) = 40

 

n 个顶点,m 条边的全连通图,至少去掉m-n+1条边才能构成一棵

全连通图任意两个顶点都是相连的。若全连通图有n个结点,则边数是n(n-1)/2。n个顶点的树一定有n-1条边,所以需要去掉m-(n-1)=m-n+1条边。

 

完全二叉树的某结点若无左孩子,则它必是叶结点。

 

图G的一棵最小代价生成树的代价未必小于G的其它任何一棵生成树的代价。

因为最小生成树不唯一,所以可能相等。

 

高为5的3阶B-树至多包含242个关键字:

3阶b树每个节点最多拥有三颗子树两个关键字,一共五层则节点数最多为1+3+9+27+81,每个节点又最多拥有两个关键字 则关键字最多为(1+3+9+27+81)*2 = 242

 

哈夫曼树的二叉树左右子树可以交换

哈夫曼树的构造不唯一,其左右子树位置交换,并不影响该二叉树的带权路径长度最小的性质。

 

将森林转换为对应的二叉树,若在二叉树结点中,结点m是结点n的双亲结点的双亲结点,则在原来的森林中,m和n可能具有的关系是.父子关系或兄弟关系:

 

 

三叉链表作二叉树的存储结构,当二叉树中有n个结点时,有n+2个空指针:

三叉链表每个节点有三个指针,左孩子,右孩子,父节点。对于有n个节点的树结构,有n-1条边,每条边是孩子节点指向父节点的指针,也是父节点指向孩子节点的孩子指针, 所以一共是2(n-1)个指针,总的指针就是3n-2(n-1)=n+2

 

一棵有n个结点的完全二叉树,按层次从上到下、同一层从左到右顺序存储在一维数组A[1..n]中,则二叉树中第i个结点(i从1开始)的右孩子在数组A中位置是A[2*i + 1](2*i + 1 <= n):

 

 

一棵满二叉树同时又是一棵平衡树。

 

在一棵树中,如果结点 A 有 3 个兄弟,而且 B 是 A 的双亲,则 B 的度是 4:

 

一棵二叉树高度为h(根的高度为1),结点的度是0或2,则这棵二叉树最少有2h - 1个结点

 

使用一个长度最大为150的队列,对满二叉树进行广度优先遍历时,能够容纳的二叉树的最大深度(第一层深度为1)为8:

广度优先遍历,队列中最多会容纳满二叉树的最后一层即2^(n-1)个。2^7=128,所以是8层。

 

的形状是一棵完全二叉树:堆不保证节点的个数正好能构成满二叉树,也不是二叉排序树。

平衡二叉树肯定是一颗二叉排序树,堆不是二叉排序树,所以堆也不是平衡二叉树。

 

一个具有513个节点的二叉树,有504种可能的层高:最高的情况是每层一个结点,最低则是完全二叉树,513的结点完全二叉树情况下高度是10层。所以从10 到513 共504种情况。

 

将森林F转换为对应的二叉树T,F中叶结点的个数等于T中左孩子指针为空的结点个数:

在二叉树中,节点的左指针指向其孩子,节点的右指针指向其兄弟。所以在一颗二叉树中,如果某个节点的左指针为NULL,就说明这个节点在原来的森林中没有孩子,是叶结点。

 

在一棵具有15个关键字的4阶B树中,含关键字的结点数最多是15:只需要满足根结点有1个关键字。非叶结点至少1个关键字,即一个结点一个关键字时结点数最多,最多15个结点。

 

若 A=10、B=4、C=6、D=4、E=15 则后缀表达式“ AB*CD+-E+ ”的值为 45:

遇到数字先进栈,遇到符号弹出两个数字,运算后再入栈,直到结束全部弹出

> 先让AB入栈,碰到*,AB出栈,计算AB,结果40入栈,
> 紧接着CD入栈,碰到+,CD出栈,计算C+D,结果10入栈,
> 碰到-,40和10出栈,计算40-10,结果30入栈,
> E入栈,碰到+,30和E出栈,计算30+E,得出结果45​

 

 

获取包含n个元素的大顶堆中的最小值,最多需要查找n/2次:

 

筛选法:开始按现有的顺序从上到下,从左到右放到一个完全二叉树里面。然后把这个树调节成堆。调节的时候从下往上,从右到左的顺序,从第一个非叶结点开始调整。

 

对于包含n个元素的序列,对其进行堆排序需要经历n次建堆操作。

 

对数组进行建堆,如果只用堆的 push 操作,则一个大根堆依次输入 3,7,2,4,1,5,8 的建堆过程:

 

 

对于根元素为最小值的二叉堆:

A.删除最小元素,即要调整堆,复杂度为O(logn)

B.插入新元素,要和堆中元素进行比较,寻找插入位置,复杂度为O(logn)

C.合并两个堆,即堆1的每个元素插入堆2,由B可得,复杂度为O(nlogn)

D.查询最小元素,小顶堆的最小元素即为堆顶,复杂度为O(1)

 

 

将一个元素插入大小为len的小顶堆中,最多需要移动log(len)次,最少需要0次。

 

值类型与引用类型区别:

 

值类型

引用类型

存储方式

直接存储数据本身

存储的是数据的引用,数据存储在数据堆中

内存分配

分配在栈中的

分配在堆中

效率

效率高,不需要地址转换

效率较低,需要进行地址转换

内存回收

使用完后立即回收

使用完后不立即回收,而是交给GC处理回收

赋值操作

创建一个新对象

创建一个引用

类型扩展

不易扩展,所有值类型都是密封(seal)的,所以无法派生出新的值类型

具有多态的特性方便扩展

实例分配

通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中

总是在进程堆中分配(动态分配)

 


 

设某无向图有n个顶点,则该无向图的邻接表中有n个表头结点。

 

无向图的邻接矩阵是对称矩阵。

 

有向图用邻接表表示后,顶点 i 的出度等于邻接表中顶点 i 后链表的长度。

 

一个具有 n 个顶点的无向连通图,它所包含的连通分量数为1:

无向图G的极大连通子图称为G的连通分量( Connected Component)。任何连通图的连通分量只有一个,即是其自身,非连通的无向图有多个连通分量。

 

邻接矩阵法存储一个图时,在不考虑压缩存储的情况下,所占用的存储空间大小只与图中结点个数有关,而与图的边数无关。


 

已知图的邻接表如图所示,则从顶点0出发按广度优先遍历的结果是0123:

 


有 ABCDEF 六个城市,每一个城市都和其他所有城市直接相连,路径不允许经过某个城市两次,从 A—B 有65种连接方式:

按照途经中间城市的个数依此累加。6个城市,最多4个中间城市,因为先经过A再经过B和先经过B再经过A是不一样的,所以用排列数。途径0个中间城市:A(0,4) = 1;途径1个中间城市:A(1,4) = 4;途径2个中间城市:A(2,4) = 12;途径3个中间城市:A(3,4) = 24;途径4个中间城市:A(4,4) = 24;总路径数为:1+4+12+24+24=65。

 

拿到邻接矩阵首先判断是无向图还是有向图:邻接矩阵是对称矩阵就是无向图,否则为有向图。无向图顶点i的为第i行的元素和。有向图顶点i的为第i行的元素和+第i列的元素和。

 

用邻接矩阵存储有 n 个结点 (0,1,...,n) 和 e 条边的有向图 (0≤e≤n(n-1))。判断结点 i,j(0≤i,j≤n-1)有边的时间复杂度是O(1)。


 

某软件系统结构图如图所示,该结构图的深度为3:
D的深度是2。转换为树可以看到,C,E指向的都是兄弟节点,不是子节点。

 


可以检测一个有向图中是否存在环的算法是深度优先遍历和拓扑排序。

关键路径算法要求图内无环,这是算法的起始条件,但算法本身不含有判断是否有环的部分。

 

有向图邻接矩阵中,第 i 行上和第 i 列上非零元素总数等于顶点 Vi 的度数:

第i行上非零元素的和为顶点Vi的入度,第i列上非零元素的和为顶点Vi的出度。

在一个包含 n 个顶点的有向图中,所有顶点的出度之和为 s,则所有顶点的入度之和为s。

 

设某有向图中有n个顶点,则该有向图对应的邻接表中有n个表头结点:

对于图G=(V,E),V代表vertex顶点,E代表edge边。邻接链表由一个包含V条链表的数组所构成,每个顶点有一条链表。因此顶点有多少个,链表就有多少条。

 

有 n 个顶点的无向图, 采用邻接矩阵表示, 图中的边数等于邻接矩阵中非零元素总数的一半。

 

一个有向图邻接表逆邻接表中结点的个数相等:

“逆邻接表”只是把“邻接表”中弧头和弧尾的次序换了,并不是一种新表,它和“邻接表”的唯一区别就是弧尾的nextarc指针指向弧头而已.所以节点数是相等的。

 

假设我们用d=(a1,a2,….a5)表示无向无自环图G的5个顶点的度数,{4,2,2,1,1}是可能的:

无自环是指一个顶点不能自己到自己,而不是图没有环,所以可以有环。无向图边数最多和总度数最多,就是完全无向图,边数为E=n(n-1)/2条,总度数为边数的两倍即D=n(n-1)。所以只要边数小于等于E,度小于等于D,且总度数D是一个偶数,都可以构成图。题目5个顶点,其完全无向图的边数为10,总度数为20,则数字之和是偶数且不大于20的即为答案。


 

在下图的多边形ABCDE中从B或E点出发,可以遍历图上的每条边一次,而且仅遍历一次:

无向图中,G有欧拉通路的充分必要条件为:G连通,G中只有两个奇度顶点(它们分别是欧拉通路的两个端点)。


一个无向图的连通分量是其极大的连通子图:

无向图中任意两个节点之间有连通,则称为连通图。每一个非连通图可分为几个极大连通部分,每一个极大连通子图称为连通分量;极大连通子图是无向图的连通分量,极大即要求该连通子图包含其所有的边;极小连通子图既要求保持图连通,又要使得边数最少的子图。

 

在工程网络计划中,工作 M 的最早开始时间为第 16 天,其持续时间为 5 天。该工作有三项紧后工作,他们的最早开始时间分别为第 25 天、第 27 天和第 30 天,最迟开始时间分别为第 28 天、第 29 天和第 30 天。则工作 M 的总时差为7天,自由时差为4天:

工作M的最迟开始时间为第23天(23+5=28,第28天要开始另外的工作了,所以最晚必须第23天开始),最早开始时间为第16天,所以总时差有7天。最早完成工作M的时间是第21天,下次项目开始的最早是第25天,所以自由时差是25-21=4天。

自由时差简称FF(Free Float),指一项工作在不影响其紧后工作最早开始时间的条件下,本工作可以利用的机动时间。 总时差TF(Total Float)指一项工作在不影响总工期的前提下所具有的机动时间,即用最迟完成时间与最早完成时间之差。总时差就是拖拖拖,可以拖多少天才做工作M,自由时差就是早早完成,然后在下次工作委派之前可以休息多少天。

 

用 DFS 遍历一个无环有向图,并在 DFS 算法退栈返回时打印相应的顶点,则输出的顶点序列是逆拓扑有序

拓扑有序是指如果点U到点V有一条弧,则在拓扑序列中U一定在V之前.深度优先算法搜索路径恰恰是一条弧,栈的输出是从最后一个被访问点开始输出,最后一个输出的点是第一个被访问的点。所以是逆的拓扑有序序列。

当各边上的权值均相等时,BFS算法可用来解决单源最短路径问题。
权值相同则最短路径问题转化为求边数最少的问题,BFS可以保证求得源点到汇点的最少边数。

 

图示是一个网络流从s到t的某时刻快照。此时t处一共接收到10+13+16=39单位流量。每条横线上的数字表示当前流量和管道的容量。该网络最大的流量是41:

 

首先计算每一层向终点方向的最大输出能力,不包括回流的量,然后计算总体的最大流量,为各个层中流量最小的一层的流量。本题中分为三层:

第一层为s。朝终点最大输出量为11+22+10 = 43

第二层为节点1、2、3。朝终点最大输出量为10+17+14 = 41(10是因为节点4最多接受10,出度为10,14是因为节点3的入度为14,所以是14而不是16)

第三层为节点4、5、6。朝终点最大输出量为10+16+16 = 42。综合考虑总体最大的流量为41.

 

邻接矩阵适用于稠密图(边数接近于顶点数的平方),邻接表适用于稀疏图(边数远小于顶点数的平方)。

 

在一个图中,所有顶点的度数之和等于图的边数的2倍

 

相比于Kruskal算法,Prim算法更适合于求边稠密的无向网最小代价生成树

 

无向图 G 中有 n 个顶点,则该无向图的最小生成树上有n-1条边。

有 8 个结点的无向连通图最少有7条边。

 

AOE 网应该是一个无环图

 

AOE 网表示工程的进度,欲估算该工程的最短工期,应使用求关键路径操作。

生成树和最小生成树有许多重要的应用。例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

拓扑排序解决一个工程能否顺利进行的问题。 关键路径解决工程完成需要的最短时间问题。

最短路径用于计算一个节点到其他所有节点的最短路径,以起始点为中心向外层层扩展,直到扩展到终点为止。 Dijkstra算法能得出最短路径的最优解,但遍历计算的节点多,效率低。

 

假设一个无向图中包含 12 个顶点,其中 5 个顶点有 5 个度,7 个顶点有 7 个度,那么这个图有37条边:无向图中,所有结点度之和=边数*2 (7*7+5*5)/2=37

 

一个有向图D=(V, A),满足对任意 v∈V, od(v)=1;时,是V到V的一个映射的图。

从Vi到Vj的映射,是指对于V中的每一个元素i,V中都有一个唯一的元素j与之对应,也就是 i--->j 唯一,出度=1。

邻接矩阵存储有向图,矩阵主对角线以下的元素均为零,则该图拓扑序列存在但可能不唯一。

有向图中存在拓扑序列,则该图不存在回路

 

只要在无向有权图中存在1个环(回路)的权值之和为负值,就称此无向图存在“负权回路”。最短路径 Bellman-Ford 算法可以检验一个无向图是否存在负权回路。

 

对于一个具有 n 个顶点的无向图,若采用邻接表表示,则表头向量的大小为n:

表头向量的大小等于顶点的数量,等于表头的数量。

 

无向图特有:连接多重表。有向图特有:十字链表、边集数组。二者共有:邻接表、邻接矩阵。

 

关键路径是 AOE 网中的从源点到汇点的最长路径

 

下面关于求关键路径的说法正确的是:

求关键路径是以拓扑排序为基础的。 关键活动一定位于关键路径上、

一个事件的最早开始时间同以该事件为尾的弧的活动最早开始时间相同。 一个事件的最迟开始时间为以该事件为头的弧的活动最迟开始时间与该活动的持续时间的差。

 

具有 7 个顶点的有向图至少应有7条边才可能成为一个强连通图

强连通图必须从任何一点出发都可以回到原处,每个节点至少要一条出路(单节点除外)至少有n条边,正好可以组成一个环。

 

含有n个顶点的连通且无环的简单无向图, 其邻接矩阵存储结构中共有n²-2n+2个零元素:

n个点的单向连通图有n-1条边,考虑上三角或者下三角,邻接上/下三角矩阵中有n-1个值不为0。又因为邻接矩阵在描述无向图时是对称的,所以有n^2-2(n-1) 个零元素。

 

设 G 是一个具有 6 个顶点, 11 条边的图,其每个顶点的度为3或4,则图G是平面图

任意两点度的和大于或等于顶点总数,那这个图一定是哈密顿图。

 

含n个顶点的连通图中的任意一条简单路径,其长度不可能超过n-1。

 

设 G 是有 p 个顶点 q 条边的(简单)无向图,且 G 中每个顶点的度数不是 k 就是 k+1,则 G 中度为 k 的顶点的个数是p(k+1)-2q

顶点度数只有两种,设k度的顶点数为x,k+1度的顶点数为(p-x)。无向图顶点数乘以相应度数之和为图中边数的2倍(一条边代表一次入和一次出),kx+(k+1)(p-x)=2q,解得x=p(k+1)-2q。

 

无向图 G = (V, E) 中含 7 个顶点,保证图在任何连边方式下都是连通的, 需要的边数最少是16:

要让 7 个点都连通,那么先让 6 个点完全连通,所谓完全就是每个点能够支出的边是满的,这样 6 个点的情况下,边和点的关系是满的。其边的数量由公式 n*(n-1)/2 得出(无向完全连通图),也就是 6*5/2=15;此时多了一个7 号点,只需要在那 6 个点的图中连一根边过来,7 号点就可以访问任意 6 点图中的点了。

 

要保证在任意连接方式下都能连通具有10个顶点的无向图,至少需要37条边:

需要前面9个顶点两两相连,第10个顶点加入一条边就能保证连通。从9个节点中任选两个节点连接,则需要C(9,2)条边,再加上最后一条边,则总边数为: C(9,2)+1=(9*8)/(1*2)+1=37。

 

用有向无环图描述表达式 (A+B)*((A+B)/A), 至少需要顶点的数目为5:

 

 

用相邻矩阵 A 表示图,A[i][j] = 1 表示 Vi 和 Vj 之间有边相连,A[i][j] = 0 表示无边相连。要判定任意两个顶点 Vi 和 Vj 之间是否有长度为 m 的路径相连,则只要检查A^m的第 i 行第 j 列的元素是否为零即可:

”长度为m的路径“是从 Vi起经过m-1个点到达Vj

 

 

两人在一个 n 个点的无向完全图上进行游戏,每次可以选择当前图中某条两个端点度数奇偶性相同的边删除,谁不能操作谁输,则在n=1,2,3,......,9,10 中,有5个图先手有必胜策略。:

N个点的无向完全图边数为:N*(N-1)/2;先手获胜必须总数为奇数;N=1,2,3,......,9,10代入公式,为奇数的只有N=2,3,6,7,10这5个。

 

p 个顶点 p 条边的连通图中至少有3个生成树:

p个顶点的连通图图至少p-1条边,p条边必有一环,环至少有3条边组成,所以生成树至少有排列的C3取1等于3个。

 

已知一有向图的邻接表存储结构如下图所示。根据有向图的深度优先遍历算法,从顶点 v1 出发,所得到的顶点序列是v1,v2,v3,v5,v4;或v1,v2,v3,v4,v5; 或v1,v3,v4,v5,v2

 

posted @   JavinC  阅读(621)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示