数据结构-知识点总结

概论

1:数据的结构直接影响算法的选择和效率。

2:数据->数据元素(元素,结点,记录)数据的基本单位->数据项(字段,域)数据不可分割的最小单位

3:数据类型:原子数据类型:值不可分(整型,字符型,实型)和结构数据类型:值可分解(数组类型,结构类型)用户自己定义的

4:数据结构:逻辑结构,物理结构:存储结构(数据结构在计算机中的表示),运算特征。 逻辑结构:集合,线性结构(一对一),树型结构(一对多),图状结构(多对多) 运算:插入,删除,查找,排序。 数据结构定义:按某种逻辑关系组织起来的一批数据,应用计算机语言,按一定的存储表示方法把它们存储在 计算机的存储器中,并在这些数据上定义了一个运算的集合。 数据的4种基本存储方法: 顺序存储方法:逻辑上相邻的节点存储在物理位置相邻的存储单位中,结点之间的逻辑关系由存储单元的邻接关系来体现。 该方法主要应用于线性的数据结构。 链接存储方法:不要求逻辑上相邻的结点在物理位置上也相邻,结点之间的逻辑关系是由附加的指针来表示的。 索引存储方法:存储结点信息的同时,建立附加的索引表。索引表中的每一项称为索引项。索引项的一般形式:(关键字,地址) 散列存储方法:根据结点的关键字直接计算出该结点的存储地址。 例子:线性结构+顺序存储方法+栈=顺序栈,线性结构+链接存储方法+队列=链队列。

5:算法特性:有穷性,确定性,可行性,输入,输出。

6:算法好坏的判断依据:正确性,健壮性,可读性,执行耗费时间,执行耗费空间。

7:常用函数关系排序:c < log2n <\n < nlog2n < n^2 < n^3 < 10^n < n!

8:算法是通过数据结构求解问题的步骤,程序是用数据类型描述的算法。 9:数据结构: 基础数据结构: 线性结构:线性表,栈,队列,串 非线性结构:数组,广义表,树,二叉树,图 应用数据结构:查找,内部排序,外部排序,文件。 线性表

1:线性表特征: 有且只有一个“第一元素” 有且只有一个“最后元素” 除第一个元素之外,其他元素都有唯一的直接前趋 除最后元素之外,其他元素都有唯一的直接后继

2:线性表的基本操作:初始化,清除,求表长,插入数据,删除数据, 获得后继结点,获得前趋结点,获得位置i的节点,定位(按值查找) 在插入数据,删除数据,定位中平均移动比较次数是n/2,这三个操作的时间复杂度是O(n)。

3:线性表的存储结构用数组来实现

4:顺序表:顺序存储结构的线性表 特点:元素在计算机内部存储的物理位置相邻来表示线性表中数据元素之间的逻辑相邻关系。 一段连续的内存空间来保存线性表结点的值。

5:在顺序存储中,能够方便的查找指定位置的结点值,但是,插入和删除结点比较麻烦。

5:链表:链式存储结构的线性表

6:单链表:一个结点只包含一个指针域,一个结点结构=数据域+指针域。指针域保存地址信息。 用单链表来表示线性表时,结点之间的逻辑关系是由结点中的指针来指示的。

7:为了方便判断空表,插入和删除结点,我们在单链表的第一个结点前面加上一个头结点。 单链表的头指针L指向头结点,如果L->next=null,表示链表为空表。

8:单链表中,任何两个结点的存储位置之间没有固定的联系,要寻找某一个结点,必须从头指针出发, 一个一个结点地向后寻找。

9:线性表的链式存储方式利用不连续的内存空
间来保存结点的信息,因此,在结点中,不仅需要保存结点 本身的数据值,还需要利用指针域保存指向直接后继的指针。

10:单链表的操作:清除链表,求表长,定位查找,插入数据,删除数据时间复杂度为O(n)

11:在链式存储中,插入或者删除结点不需要大量的移动结点;但是在定位时,却需要遍历整个链表。

12:单循环链表:如果把单链表的最后一个节点的指针域指向第一个结点(头结点)。则构成一个首尾相连的循环链表。

13:循环链表中判断一个链表是否为空,需要看头结点的next域是否等于自身。

14:循环链表建立和判断的时间复杂度为O(1),插入和删除的时间复杂度为O(n)

15:双向链表:两个指针域,一个指向直接前趋,一个指向直接后继。

16:双向链表中结点的特点,结点的next的prior是结点本身,结点的prior的next是结点本身。

17:判断双向链表为空表:看是否两个指针域都为NULL。 18:双向链表的建立和判断时间复杂度为O(1),插入和删除的时间复杂度为O(n)

19:双向循环链表:双向链表中的头结点和尾结点连接起来。 20:判断双向循环链表为空表:看是否两个指针域都指向自身。

21:线性表顺序存储与链式存储的比较 时间角度:当线性表的操作操作主要是进行查找,而很少进行插入和删除时,应该采用顺序表进行存储 当对于频繁的插入和删除操作的线性表,则应该采用链表作为为存储结构 空间角度:顺序表的存储空间是静态分配的,在程序引入前必须规定其存储闺蜜,如果估计过大,会造成空间的浪费。估计过小,会造成空间的溢出。 动态链表是动态分配的,只要内存空间有空闲,就不会产生溢出。 线性表的长度变化比较大时,应该采用动态链表为存储结构。

22:顺序表是采用数组实现的,链表是采用指针(动态链表)或者游标(静态链表)来实现的。 栈和队列

1:栈和队列:操作受限的线性表,称为限定性的线性表结构。

2:栈:仅允许在一端进行插入和删除运算线性表。后进先出(LIFO)线性表 栈顶:允许进行插入和删除的那一端。 栈底:不可以进行插入和删除的那一端(线性表的表头) 进栈,入栈,压栈:在一个栈中插入新元素,成为新的栈顶元素 出站,退栈:在一个栈中删除一个元素,删除栈顶元素,使下一个成为新的栈顶元素。

3:栈的基本操作:构造空栈,清除所有元素,判断栈是否为空,返回栈顶元素,入栈,出栈。

4:根据存储结构:顺序栈,链栈 顺序栈:利用地址连续的存储单元依次存放从栈底到栈顶的数组元素,数组0位置的元素作为栈底元素 top=-1,表示栈空;top=maxsize-1,表示栈满,就相当于一维数组 在做入栈操作前,首先要判定栈是否满(满了叫上溢);入栈指针top先加1,然后入栈。 在做出栈操作前,先要判定栈是否为空(空的为下溢);出栈指针top先减1,然后出栈(指针+1)。 链栈:相当于结点构成的单链表。 栈顶元素为单链表的第一个结点,因为栈顶元素操作频繁,所以经常用没有头结点的单链表。 链栈是动态分配结点空间的,所以操作时无需考虑上溢问题。 链栈的优点:可使多个栈共享空间;在栈中元素变化的数量较大,且存在多个栈的情况下,链栈是栈的首选存储方式。

5:栈的应用:数值进制转换,括号匹配问题,子程序的调用,递归实现

6:队列:限定在表的一段进行插入而在另一端进行删除的线性表。先进先出线性表(FIFO) 队头:允许删除的一端 队尾:允许插入的一端 入队:向队列中插入新元素,成为新的队尾元素 出队:从队列中删除元素,其后继元素成为新的队头元素。

7:队列的基本操作:构造空队列,判断队列为空,求队列长度,返回队头元素,插入队尾元素,删除队头元素,清除队列元素。

8:根据存储结构:链队列,顺序队列 链队列:限制仅在表头进行删除和在表尾进行插入的单链表。拥有两个指针(头指针,尾指针) 头指针指向头结点,队尾指针指向真正的队尾元素结点。 判断链队列为空:头指针和尾指针全部指向头结点。 入队:先将尾指针指向的元素的指针指向入队元素,然后尾指针指向入队元素。 出队:修改头指针中的指针,指向后继结点,但是当删除的元素是最后一个元素时,尾指针需要指向回头结点。 顺序队列:用一组地址连续的存储单元依次存放从队列头到队列尾的元素。 这里除了定义一维数组还需要两个指针指向队头和队尾。 初始化空队列:front=rear=0; 入队:元素插入到rear所指向的空单元内,rear+1; 出队:删除数组头结点,front+1 在非空队列中,头指针始终指向队头元素,而尾指针始终指向队尾元素的下一个位置。 假溢出:当发生了若干次出队入队操作后,尾指针到了最后,无法插入了,但是队列元素<队列的单元数。 避免假溢出现象:使用循环队列。 循环队列:数组大小maxsize,数组所表示的循环队列最多允许有maxsize-1个结点,rear所指的单元始终为空。 循环队列队满条件:(rear+1)%maxsize==front 循环队列队空条件:rear==front

9:队列的应用:打印杨辉三角,迷宫问题。

10:递归:如果一个函数直接调用自己或者通过一系列调用间接地调用自己。

直接递归:在函数体内直接调用自身 间接调用:一个函数通过调用其他函数并由其他函数反过来又调用该函数 11:用到递归方法的情况: 第一种情况:定义是递归的。比如阶乘,Fibonacci数列 第二种情况:数据结构时递归的。比如链表就是一种递归的数据结构 第三种情况:有些问题自身没有明显的递归结构,但用递归方法求解更简单。

12:递归消除:由于递归程序运行时要花费较多的时间和空间,效率较低,有时需要消去一个程序中最经常执行部分的递归调用。 常用的是用迭代和栈进行递归消除。

1:串(字符串):是一种特殊的线性表,它的每个节点仅由一个字符组成。通常用双引号把串中字符括起来。

2:空串:长度为0的串

3:空格串:串中都是空格字符,它有长度。

4:子串:串中任意个连续字符组成的子序列。

5:串:串变量(值可以改变),串常量(只能被引用不能改变其值)

6:顺序串:串的顺序存储结构,用一组地址连续的存储单元来依次存放串中的字符序列。字符数组表示。 串结束标志:一般使用“\0”来确定串是否结束。也可以把串长度放在ch[0]中。 基本操作:求串长,串复制,串连接,求子串,串删除。 缺点:事先定义了串长,这在程序运行前是很难估计的 定义了串的最大长度,串值空间被确定,是静态的,插入,连接操作被限制。

7:串的堆存储结构:依然是一组空间足够大的,地址连续的存储单元存放串值字符序列,不同的是 存储空间的大小不是预先定义的,而是在程序执行过程中动态分配的。 指向字符串存储空间的是字符指针而非数组,若为空串,则指针为null。

8:链串:串的链式存储结构:每个结点仅存储一个字符。便于插入和删除。

9:块链:由于链串每个结点的指针域所占空间比字符域大,存储空间利用率低, 所以在链串的每个结点存放多个字符,这样的结点叫做块。块的大小就是结点所容纳字符的个数。 当结点大小大于1时,串的长度不一定正好是结点大小的整数倍,因此用特殊字符“#”填充。

10:串的模式匹配:子串定位操作。 两种方法:n是主串长度,m是模式串长度 第一种:时间复杂度O(mn) KMP算法:时间复杂度O(m+n) 多维数组和广义表

1:多维数组和广义表:非线性结构。 逻辑结构:每个数据元素可能有多个直接前趋和多个直接后继。

2:数组只有两种基本运算————读和写 读:给定一组下标,读出相应的元素 写:给定一组下标,修改相应的元素

3:数组的存储结构:一般取数组的开始地址作为基准,然后考察其他元素相对此基准的偏移量。因此基准加上该数组元素的偏移量就是该元素的绝对地址。 由于内存是一段连续的一维存储空间,并不是一个多维的存储空间,因此多维数组中的数据要存储在内存汇总,需要按照一定 的顺序存储在一维空间中。

4:矩阵的压缩存储:矩阵中有很多值相同的元素或者零值元素,为了节省存储空间,需要对他们进行压缩存储。 对称矩阵,三角矩阵(上三角或者下三角是常数),对角矩阵(除了主对角线和主对角线相邻两侧的若干条对角线的元素之外,其余元素皆为0)

5:稀疏矩阵: 三元组表:根据原矩阵上非零元素的行号,列号以及值来作为三元组中的一个值。

6:广义表:又称为列表,它是线性表的推广。就是放宽了线性表元素是原子项这个限制。允许他们具有自身独立的类型结构。 广义表可以表示为:LS=(a1…ai…an)。LS是广义表的名字,n是广义表的长度,若ai本身就是广义表,则称为它是LS的子表。 空表:不包含任何元素的广义表。 用大写字母表示广义表,小写字母表示原子。 若广义表非空,则a1称为LS的表头,其余元素组成的表(a2…ai…an)称为LS的表尾。显然,表尾一定是子表,表头可以是原子也可以是子表。 广义表结构:广义表是递归定义的。 不考虑广义表内部的结构,广义表就是一个线性表,反之,线性表就是一个不含子表的广义表。 广义表的深度:该表展开后所含括号的层数。

7:特殊广义表: ():长度为0的空表,对其不能求表头和表尾的运算。 (()):长度为1的非空表,对其进行分解,得到的表头和表尾均是空表。

8:纯表:广义表可以与树对应,没有共享和递归的成分

9:再入表:广义表中的元素存在共享。

10:递归表:广义表中有自己。有递归。

11:广义表特有的运算:取表头和表尾。 在广义表中取某个元素,需要将该元素所在的子表逐步分离出来,直到所求的元素成为某个子表的表头,再用取表头运算取出。 总的来说最后一步肯定是取表头。

树和二叉树

1:树是n个结点的有限集T,当n=0时,称为空树,当n>0时,满足以下条件 有且只有一个称为树根的结点 当n>1时,除树根结点以外的其余n-1个结点可以划分为m个互不相交的有限集,每一个集合本身又是一个数。

2:树的特点: 树中有且只有一个结点为树根的结点 树中各子树是互不相交的集合。

3:相关概念:结点的度(结点拥有子树的数目)

4:树的基本操作:初始化,求树根,创建一颗树,求双亲结点,求孩子结点, 插入子树,删除子树,树的遍历,清除树,判断树空。

5:二叉树:由n个结点的有限集构成,此集合可能为空集,也可能由一个根结点及两颗互不相交的左,右子树组成,并且左,右子树都是二叉树。

6:二叉树的特点: 二叉树有且仅有一个结点被称为树根的结点 当n>1时,每个结点至多有两颗子树(即二叉树不存在度大于2的结点) 二叉树的子树有左,右之分,且其次序不能任意颠倒,它是有序树。

7:二叉树的基本操作:基本和树一样,但是在求孩子结点的时候,有左孩子和右孩子之分。

8:满二叉树:一颗深度为k且有2^k-1个结点的二叉树。

9:完全二叉树:深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树一一对应。 特点: 叶子结点只可能在层次最大的两层上出现 对任一结点,若其右分支下子孙最大层次为p,则左分支下子孙的最大层次为p或者p+1

10:满二叉树必为完全二叉树,完全二叉树不一定是满二叉树。

11:二叉树的性质: 在二叉树的第i层至多有2^(i-1)个结点。 深度为k的二叉树至多有2^k-1个结点。 对任意一颗二叉树BT,如果其叶子结点树为n0,度为2的结点数为n2,则n0=n2+1 推倒过程:n=n0+n1+n2,n=1+n1+2n2 对于n个结点的完全二叉树的深度为logN+1,logN取下限,表示不大于logN的最大整数 对于具有n个结点的完全二叉树,如果对其结点按层次编号,则对任一结点i,有 如果i=1,则结点i是二叉树的树根,无双亲;如果i>1,则其双亲是i/2取下限 如果2i>n,则结点i无左孩子;如果2i<=n,则其左孩子是2i; 如果2i+1>n,则结点i无右孩子;如果2i+1<=n,则其右孩子是2i+1。

12:二叉树的顺序存储:用一组连续的存储单元来存储二叉树的数据元素。 在存储的时候,对于普通二叉树必须修补成完全二叉树进行存储,这也造成了存储空间的浪费。这是顺序存储的最大缺点。

13:二叉树的链式存储:二叉树的结点应由一个数据元素和分别指向其左,右子树的各个分支构成。 因此二叉树的链表中的结点包含三个域:数据域和指向左,右子树的指针域。称为二叉链表。

14:使用链式存储结构来存储二叉树比使用顺序结构存储二叉树更加方便,也更容易反应结点之间的逻辑关系。

15:二叉树的遍历: 先根遍历二叉树:根节点-左子树-右子树 中根遍历二叉树:左子树-根结点-右子树 后根遍历二叉树:左子树-右子树-根结点

16:线索二叉树:加上了指针“线索”的二叉链表组成的二叉树:目的是为了加速遍历过程和充分利用存储空间 线索:在有n个结点的二叉链表中有2n个指针域,但只要n-1个指针域用来存放左右指针,其余n+1个指针域均为空。 因此用这n+1个空指针域来存放遍历过程中的前趋和后继的指针。 规定: 若结点有左子树,则lchild指向左孩子,否则ltag=1,lchild指向直接前趋结点。 若结点有右子树,则rchild指向右孩子,否则rtag=1,rchild指向直接后继结点。

17:线索化:实质是在二叉链表的空链域中填写相应结点在一定遍历次序下的前趋和后继的地址。而这个地址只能在动态的遍历过程中才能得到。

18:在中跟遍历中: 在线索树中查找前趋结点:当ltag=1,lchild就是其前趋结点;当ltag=0时,lchild就是其左子树右链下最后一个结点。 在线索数中查找后继结点:当rtag=1,rchild就是其后继结点;当rtag=0时,rchild就是其右子树左链下最后一个结点。

19:树的存储结构 双亲表示法:用一组连续的存储空间(数组)来存储树中的结点,每个数组元素不但包含结点本身的信息, 还保存双亲结点的下标号。 好处:查找某个结点的双亲容易 坏处:查找某个结点的孩子结点很困难。 孩子链表表示法:把每个结点的孩子结点排列起来,构成一个单链表(孩子链表)。 然后将这样的将n个这样的数据元素放在一组连续的存储空间中。 好处:容易求得一个结点的孩子结点。 坏处:求得一个结点的双亲结点就很困难。 孩子兄弟链表表示法:链表中的结点有两个链域,分别指向第一个孩子结点和下一个(右)兄弟结点。 好处:容易实现数的任何操作,在结点上加上双亲域,可以方便双亲的查找。

20:树,森林和二叉树之间的转换 由于树的孩子兄弟链表示法和二叉树的二叉链表表示法完全一样,所以之间很容易实现相互转换 树转换成二叉树: -加线:在树中所有相邻的兄弟之间加一条线。 -抹线:对树中每个结点,除了其左孩子外抹去该结点与其余孩子之间的连线。 -整理:以树的根结点为轴心,将整树按顺时针旋转45度。 可以证明,树转换所构成的二叉树是唯一的。 转换成的二叉树中,左分支上的各结点在原来的树中是父子关系,右分枝上的各结点在原来的树中是兄弟关系。 森林转换成二叉树: -将森林中的每棵树转换成相应的二叉树 -第一课树不动,从第二课树开始,依次把后一颗二叉树的根结点作为前一颗二叉树根的右子树。 树转换成二叉树根结点没有右子树,森林转换成二叉树,根结点有右子树。 二叉树转换成树: -加线:若某结点是其双亲的左孩子,则把该结点右链上所有的结点都与该结点的双亲结点用线连接起来。 -抹线:抹掉原二叉树中所以双亲结点与其左孩子右链上所有结点的连线。 -整理 能够转换成树的二叉树一定没有右子树 二叉树转换成森林: -将二叉树中根结点与其右孩子连接,及沿右分支搜索到的所以右孩子间连线全部抹掉,使之成为孤立的二叉树。 -将孤立的二叉树还原成树。

21:树的遍历 先根遍历: 访问根结点 从左到右,依次先根遍历根结点的每一颗树。 后根遍历 从左到右,依次后根遍历根结点的每一颗树 访问根结点。 层次遍历 若树非空,则遍历方法是先访问第一层上的结点,然后依次遍历第二层到第n层的结点。

22:森林的遍历 先根遍历: 访问森林中第一颗树的根结点 先根遍历第一棵树的根结点的子树森林 先根遍历除去第一颗树之后剩余的树构成的森林。 中根遍历: 中根遍历森林中第一颗树的根结点的子树森林 访问第一颗树的根结点 中根遍历除去第一颗树之后剩余的树构成的森林 后根遍历: 后根遍历森林中第一颗树的根结点的子树森林 后根遍历除去第一课树之后剩余的树构成的森林 访问第一颗树的根结点

23:森林的先根遍历,中根遍历和后根遍历相对于转换成的二叉树的先根遍历,中根遍历和后根遍历相对应

24:树的先根遍历,后根遍历分别于森林的先根遍历和中根遍历。

25:哈夫曼树(最佳判定树):由n个带权叶子结点构成的所有二叉树中带权路径长度WPL最小的二叉树。 WPL:树的带权路径长度:结点的权值*结点的路径长度,然后所以结点的带权路径长度相加。 树带权路径长度越小越好。

26:前缀编码:任意一个编码不能成为其他任意编码的前缀。利用哈夫曼算法,可以设计出前缀编码 好处:可以减少定长编码的总位数。

27:哈夫曼树中没有长度为1的结点,一颗n个叶子结点的哈夫曼树共有2n-1个结点。 可以用一个一维数组来表示各个结点,数组中每个元素包括四个数据项:结点的权值,结点的双亲下标和结点左右孩子。

28:二叉树的一些结论 一颗二叉树的前根序列和中根遍历可确定一颗二叉树 一颗二叉树的后根序列和中根遍历可确定一颗二叉树 但是前和后不能确定。 含有n个结点的二叉树共有1/(n+1)*C(n|2n);和出栈次数一样 任何一颗二叉树的叶子结点在先根,中根和后根遍历序列中的相对次序不发生改变。 图

1:线性表:数据元素之间仅存在线性关系,每个数据元素只有一个直接前趋和一个直接后继; 树:数据元素之间有着明显的层次关系,且每层上的数据元素可能和下一层中多个元素(孩子)相关联,但只能和上一层 中一个元素相关联; 图形结构:结点之间的关系可以是任意的,图中任意两个数据之间都可能相关,每个结点都可以有多个直接前趋和多个直接后继。

2:树是图的特殊情况。

3:有向边<v1,v2>,无向边(v1,v2)

4:无向完全图:n个顶点的无向图,如果任意两个顶点间都有一条直接边相连。 有n(n-1)/2条边

5:有向完全图:n个顶点的有向图,如果任意两个顶点间都有方向互为相反的两条边相连 有n(n-1)条边

6:路径长度:这条路径上边的数目。

8:简单路径:除了第一个顶点和最后一个顶点之外,其余顶点都不同的路径

9:简单回路:第一个顶点和最后一个顶点相同的简单路径。

10:相关概念:连通图,连通分量(极大连通子图),强连通图,强连通分量,弱连通图(有向图中,至少有单向通路),弱连通分量。

11:度:顶点所具有的边的数目 出度:以某顶点为尾的边的数目 入度:以某顶点为终点的边的数目 一个顶点的度=出度+入度

12:生成树:连通图的G的一个子图如果是一颗包含G的所以顶点的树, n个顶点的生成树具有n-1条边。

13:图的存储结构:邻接矩阵,邻接表,邻接多重表,十字链表

14:若图为权图:若<v1,v2>不是E(G)中的边,则这条边的权值a12为0或者无穷大。 若图为非权图:如果顶点v1,v2之间无边,则a12=0; 如果顶点v1,v2之间有边,则a12=1。

15:无向图的邻接矩阵是对称的,而有向图的邻接矩阵不一定对称。 用邻接矩阵表示具有n个顶点的有向图时,要用n^2个单元存储邻接矩阵 用邻接矩阵表示具有n个顶点的无向图时,只需要存入三角矩阵,只需n(n+1)/2个存储单元。

16:邻接矩阵来表示图,易判定图中任意两个顶点之间是否有边相连,也易求得各个顶点的度数。 对于无向图,邻接矩阵第i行元素之和就是图中第i个顶点的度数。 对于有向图,第i行元素之和是顶点i的出度,第i列元素之和是顶点i的入度。

17:邻接矩阵并非图的顺序存储结构,只是借助了数组这一数据类型来表示图中元素间的相关关系。

18:邻接矩阵的事件复杂度为O(n^2)。

19:邻接表表示法:图的链式存储结构,包含两部分:链表和向量 邻接表:在邻接链表中,对图中每个顶点vi都建立一个单链表,第i个单链表中的结点表示依附于顶点i的边。 邻接表中的每个结点由两个域组成:顶点域和链域。 表头结点:数据域和链域。所有表头结点存放在一个顶点表中。 将无向图的邻接表称为边表,将有向图的邻接表称为出边表。

20:若无向图中有n个顶点,e条边,则邻接链表需n个表头结点和2e个表结点,每个结点有两个域 对于边很少的图,用邻接链表比用邻接矩阵更节省存储单元。

21:逆邻接表:为了计算有向图的入度;邻接表可以计算有向图的出度。

22:邻接表的时间复杂度:O(n+e)

23:当边数较少时,邻接表比较好;当边数较多时,邻接矩阵比较好。

24:邻接矩阵是唯一的,邻接表不是唯一的。

25:图的遍历:深度优先搜索法和广度优先搜索法。

26:深度优先搜索法:类似于树的前序遍历。当访问了一个顶点之后,尽量向纵深方向去访问下一个未被访问的顶点。 遍历的时间复杂度取决于存储结构 邻接表:O(n+e) 邻接矩阵:O(n^2)

27:广度优先搜索遍历:类似于树的层次遍历 遍历的时间复杂度取决于存储结构 邻接表:O(n+e) 邻接矩阵:O(n^2) 遍历的空间复杂度为O(n),辅助空间是队列和标志数组

28:非连通图的遍历:进行多次DFS或者BFS算法,从某个顶点出发进行遍历,执行一次不能保证访问到所以顶点 为此,需要再选择未被访问的顶点作为起点,再次执行DFS或者BFS。

29:生成树是连通图的极小连通子图:由于n个顶点的连通图至少有n-1条边,而所以包含n-1条边及n个顶点的连通图都是无回路的树。

30:生成树:连通图G的一个子图如果是一颗包含G的所有顶点的树。

31:构造最小生成树: 尽可能选择权值小的边,但不能构成回路 选取n-1条恰当的边以连接网的n个顶点

32:构造最小生成树的普里姆(prim)算法,从顶点出发寻找最短边。 算法的时间复杂度为O(n^2),比较适合构造稠密图的最小生成树,与边数无关。

33:构造最小生成树的克鲁斯卡尔(kruskal)算法,选取n-1条最小的边 算法的时间复杂度为O(eloge),与网中数目e相关,适合稀疏图的最小生成树。

34:最短路径: 迪杰斯特拉(dijkstra):提出一个按路径长度递增的次数产生最短路径的算法。 算法的时间复杂度O(n^2) 弗洛伊德(floyd):求每一对顶点之间你的最短路径 算法的时间复杂度O(n^3),比较适合稠密图,需要求A^n和path^n;

35:AOV网:以顶点表示活动,有向边表示活动之间的先后关系的网

36:拓扑排序:构造AOV网的拓扑有序序列的操作。是定义在有向图上的一种操作。AOV网不应该出现回路。 算法时间复杂度O(n+e)

37:对AOV网进行拓扑排序的步骤是: 在AOV中选取一个入度为0的顶点并输出它 从网中删去该顶点,并且删去从该顶点发出的全部有向边 重复上面两个步骤,直到网中全部顶点均已输出,或者当前图中不存在入度为0的顶点位置,后一种情况说明有向图有回路。

38:AOV网的拓扑有序序列不唯一。

39:AOE网:顶点表示子工程(事件),边表示活动,边上的权表示活动所需的时间。

40:关键路径:从开始顶点到结束顶点的最长路径长度。一个AOE网可能有多条关键路径,他们的长度是一样的。

41:最早开始时间:一个时间vk能够发生的最早时间取决于从始点v1到顶点vk的最长路径长度。

42:最迟开始时间:一个时间vk允许的最迟发生时间,应该等于结束顶点事件vn的最早发生时间减去vk到vn的最长路径长度。

43:最迟开始时间-最早开始时间 若为0,则活动为关键活动,否则为非关键活动。

44:关键路径的识别:为了使AOE网所代表的工程尽快完成,要先识别关键路径,只有缩短关键路径上的关键活动所需时间才能缩短整个工期。

45:关键活动算法: 对AOE网进行拓扑排序,按拓扑排序的次序求出各顶点事件的最早发生时间ve,若有向图中有回路,则算法结束,否则执行下一步 按拓扑序列的逆序求出各顶点事件的最迟发生时间vl 根据求得的各顶点的ve,vl,求出各活动ai的最早开始时间e(i)和最迟开始时间l(i),若e(i)=l(i),则ai为关键活动。

46:不论出度还是入度,顶点的度之和等于图边数的两倍。

47:用邻接表表示图进行广度优先遍历,通常采用队列来实现算法。

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

查找

1:查找表:同一类型的数据元素(或者记录,结点)构成的集合。

2:关键字:数据元素中某个数据项的值,用它可以标识或识别一个数据元素。

3:查找:给定一个关键字K,在含有n个结点的查找表中找出关键字等于给定值K的结点。 若查找成功,返回结点的信息或该结点在表中的信息。 若查找失败,返回相关的指示信息。

4:动态查找表:若在查找的同时需要对表进行修改操作(如插入,删除等)。

5:静态查找表:若对表只进行查找操作(查询,检索等)。

6:内查找:如果查询过程都在内存中进行。

7:外查找:如果查询过程需要访问外存。

8:平均查找长度(ASL):查找过程中对关键字需要执行的平均比较次数。 衡量一个查找算法效率高低的标准。

9:线性表的查找 顺序查找:从表的一端开始,顺序扫描线性表,依次将扫描到的结点关键字和给定值K相比较。 R[0]作哨兵,从后往前判断 顺序查找的存储结构:适用于线性表的顺序存储结构,也使用也链式存储结构(使用单链表,扫描从表头开始) 顺序查找的平均查找长度:(n+1)/2 顺序查找改进:每当查找成功时,就将找到的结点和后继结点交换,减少查找概率大的结点的比较次数。 顺序查找的优点:算法简单,对表结构无任何要求,不关注关键字是否有序。 顺序查找的缺点:查找效率低,当n特别大时,不宜用顺序查找。

二分查找(折半查找):效率较高的查找算法,要求线性表是有序表,并且要用向量作为表的存储结构。 二分查找的基本思想: 首先确定该区间内的中点位置 然后将待查的K值与中点位置进行比较:若相等,查找成功返回位置;否则确定新的区间差找 如果中间位置大于k,则新的区间则是左子表 如果中间位置小与K,则新的区间则是右子表 重复上面这个过程,直至找到关键字为K的结点。 二分查找判定树:把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树。 二分查找成功时的平均查找长度:log(n+1)-1 二分查找失败时的平均查找长度:log(n+1)取上限 二分查找的平均查找长度:logn 二分查找的优点:查找效率高,但是要排序关键字,最高效的排序需要花费O(nlogn) 二分查找的缺点:只适合顺序存储结构,适合一经建立就很少改动而又经常需要查找的线性表。 分块查找(索引查找):性能介于顺序查找和二分查找之间 分块查找表的索引结构: 分块有序的线性表:前一块的最大关键字小于后一块的最小关键字 索引表:抽取各块中最大关键字及其起始位置构成一个索引表,索引表是递增有序表。

分块查找的基本思想: 首先查找索引表,可采用二分查找或者顺序查找看看结点在哪一块。 然后在已确认的块中进行顺序查找。 分块查找的平均查找长度 二分查找确定块:log(n/s+1)+s/2 顺序查找确定块:(s^2+2s+n)/(2s) s=n取根号 分块查找的优点:在表中插入或者删除一个记录时,只要找到该记录所属的块,就可在该块内进行插入和删除运算。 因为块中的记录的存放是任意的,所以插入或者删除的时候不需要移动大量的结点。 分块查找的缺点:增加一个辅助数组的存储空间和将初始表分块排序的运算。 在这三种查找方式中,二分查找效率最高,但是只适用于静态查找表。

10:树表 二叉排序树(二叉查找树): 若它的左子树非空,则左子树上所有的结点的值小于根结点的值 若它的右子树非空,则右子树上所有的结点的值大于根结点的值 左右子树本身又个数一颗二叉排序树 树排序:二叉排序树的中序遍历是一个有序遍历,平均执行时间O(nlogn) 对于相同的实力,树排序是堆排序的2-3倍,因此在一般情况下构造二叉排序树是为了查找,并不是为了排序。 二叉排序树的插入: 若二叉排序树为空,则为待插入的关键字申请一个新结点,并令其为根; 若二叉树不为空,则将key和根的关键字比较: 若二者相等,说明树中已有此关键字,无须插入 若key小于根关键字,则插入左子树 若key大于根关键字,则插入右子树 二叉排序树的删除: p为叶子结点,只需将p的双亲parent中指向p的指针域置为空; p只有一个孩子,只需将child和p的双亲直接连接就可以,然后删除p; p有两个孩子,替换p为p右子树下最左边的点,删除该点。

二叉排序树上的查找:二叉排序树构建不唯一,二分查找树是唯一的 二叉排序树的查找的平均查找长度与二叉树的形态有关 最坏情况查找长度:构成一颗单树,(n+1)/2 最好情况查找长度,构成一颗形态与二分查找树相似的排序树,logn 插入,删除和查找算法的时间复杂度均为O(logn) 二叉排序树和二分查找的比较: 平均时间差不多, 如果有序表是静态查找表,用二分查找 如果有序表是动态查找表,用二叉排序树 平衡二叉树:指树中任一结点的左右子树的高度大致相同,为了保证二叉排序树的高度为logn 满二叉树是完全平衡的,只要二叉树的高度O(logn),可以看成是平衡的。 AVL树中任一结点的左右子树的高度之差的绝对值不超过1 最坏情况下,n个结点的AVL树高度为1.44logn 完全平衡的二叉树高度为log2n,AVL树是接近最优的。 B-树:查询的文件较大,且文件存放在磁盘等直接存取设备中,为了减少查询过程中对磁盘的读写次数。

特性: 每个结点至少包含以下数据域(n,p0,k1,p1,k2,…,ki,pi)k表示关键字(关键字序列递增有序),p表示指针 所有叶子结点都在同一层,叶子的层数为树的高度h 每个非根结点中所含关键字个数j为(m/2-1)<=j<=m-1(m为树的阶树) 由于内部结点的度数正好是关键字总数+1,所以每个非根结点至少有m/2颗子树,至多有m颗子树 高度为n的三阶B-树的结点至少是2^n-1 一颗高度为h的B-树,任一叶子结点所处的层数为h,当向B-树插入一个新关键字时,为检索插入位置所需读取h个结点。 11:散列表的查询:不同于顺序查找,二分查找,二叉排序树,不以关键字的比较为基本操作,而是采用直接寻址技术。 12:散列的基本思想:以结点的关键字K为自变量,通过一个确定的函数关系h,计算出对于的函数值,然后把这个值解释为结点 的存储地址,将结点存入函数值所指的存储位置上。在查找时,根据要查找的关键字用同一函数计算出地址 再到相应的单元里去取出要找的结点。 13:散列表冲突问题:两个不同的关键字,由于散列函数值相同,因而被映射到同一表位置上。 冲突不可能完全避免。 避免冲突的办法: 关键字的个数小于或者等于散列表的长度 选择合适的散列函数 14:装填因子:a=填入的结点n/表长m。a越大,表越满,冲突的机会也越大,通常去a<=1。 15:散列函数的选择: 平方取中法:先通过求关键字的平方值扩大相近数之间的差别,然后根据表长度取中间的几位数作为散列函数值。 除留余数法:最简单常用的一种方法。就是用表长m去除关键字,取其余数作为散列地址。m最好取素数。

相乘取整法: 首先用关键字key乘上某个常数A(0<\A<\1),并抽取出key*A的小数部分,A最好选黄金分割点; 然后用m乘以该小数后取整。 随机函数法:保证随机函数值在0-(m-1)之间。通常,当关键字长度不等时采用此方法来构造散列函数。

16:处理冲突的方法 开放地址法:冲突发生时,使用某种探查技术在散列表中形成一个探查序列,沿此序列逐个单元查找,直到找到给定的关键字 或者碰到一个开放的地址为止。hi=(h(key)+ di)% m  0<=i<=m-1 装填因子:0.5-0.9 探测序列的

方法: 线性探查法:探查序列:hi=(h(key)+ i)% m  0<=i<=m-1 即di=i 缺陷:堆积现象严重。 二次探查法:跳跃式探查。hi=(h(key)+ i*i)% m  0<=i<=m-1 即di=i^2 缺陷:可以减少堆积现象,但是不易探查到整个散列空间。 双重散列法:开放地址法中最好的方法之一。hi=(h(key)+ i * h1(key))% m  0<=i<=m-1 即di=i * h1(key) 用了两个散列函数。h1(key)=key%(m-1)+1 m为素数,则h1(key)取1-(m-1)之间的任何数均与m互素。 m为2的方幂,则h1(key)取1-(m-1)之间的任何奇数。

拉链法:将所有关键字为同义词的结点链接到同一个单链表中。

拉链法的优点: 拉链法处理冲突简单,没有堆积现象 拉链法结点空间动态申请,适合造表前无法确认表长的情况 拉链法可以a>=1 删除结点比较容易,直接在链表上删除;而对开放地址表来说,只能在被删结点上做标记,不能真正的删除结点 拉链法的缺点: 指针需要额外的空间,当结点规模小,与拉链法相比,开放地址发更省空间。

17:性能分析 查找:散列表的平均查找长度比顺序查找,二分查找等完全依赖与关键字比较的查找要小得多。 查找成功ASL:查找次数之和/关键字个数 查找失败ASL:查找不成功时对关键字需要执行的平均比较次数。 直接找关键字到第一个地址上关键字为空的距离即可,初始位置只能是MODn的0-n-1;

18:总结: 由同一个散列函数,不同的解决冲突方法构造的散列表,其平均查找长度是不同的 散列表的平均查找长度不是结点个数n的函数,而是装填因子a的函数。 顺序查找O(n),其他查找O(logn),散列法查找O

(1) 排序:

1:排序方法的稳定性:在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些相同的关键字的记录之间的相对次序 保持不变。反之,则是排序方法是不稳定的。

2:内部排序:排序过程中整个文件都在存放在内存中进行处理的。 一般适用于记录个数不多的小文件 插入排序,选择排序,交换排序,归并排序,基数排序

3:外部排序:排序过程中需要进行数据的内,外存之间的交换。 一般使用于记录个数太多,不能一次性将全部记录放入内存的大文件

4:排序算法的性能评价: 执行算法所需的时间 执行算法所需的辅助空间:O(1)是就地排序,非就地排序一般是O(n) 算法本身的复杂程度

5:不同存储方式的排序过程:顺序表,链表,索引表。

6:插入排序:每次将一个待排序的记录,按其关键字大小插入已经排序好的文件中的适当位置,直到全部记录插入完位置 (就像打牌一样,边抓边整理)。 直接插入排序:在排序中,引入哨兵这个概念,作用是: 在进入循环之前,保存了r[i]的副本,使得不致于因为记录后移而丢失r[i]中内容 在while循环中监视下标j是否越界,一旦越界,直接退出 引入哨兵后使得测试查找循环条件的时间大约减少了一半 算法分析: 时间复杂度: 关键词递增有序(正序):O(n) 关键词递减有序(反序):O(n^2) 关键词无序(平均):O(n^2) 空间复杂度: 辅助空间一个监视哨,O(1),

就地排序 直接插入排序是一个稳定的排序 希尔排序:直接插入排序的改进,先取一个小与n的整数d作为第一个增量,把文件分成d组,然后排序,然后减少增量 的值进行排序,最后增量变成1进行排序。 算法分析: 希尔排序的执行时间依赖于增量序列: 最后一个增量必须是1 应该避免序列中的值为倍数 当n较大时,希尔排序比较和移动的次数约为n^1.3 希尔排序时间性能上明显优于直接插入排序 希尔排序是不稳定的。

7:交换排序:两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录出现 冒泡排序:从下往上扫描数组,一旦违反轻气泡不能再重气泡之下的原则,就让其调换,直到最后任何两个气泡都是轻者在上,重者在下。

算法分析: 时间复杂度: 关键词递增有序(正序):O(n) 关键词递减有序(反序):O(n^2) 关键词无序(平均):O(n^2) 空间复杂度: 辅助空间一个监视哨,O(1),就地排序 冒泡排序是一个稳定的排序 快速排序(霍尔排序):采用分治法的思想,将原问题分解成若干个规模更小但结构与原问题相似的子问题,递归解决子问题 然后将这些子问题的解组合为原问题的解。

三个步骤: 分解:在数组中任选一个基准,依次划分左右两个较小的子区间,最后使得左区间的值<基准值<右区间值 求解;递归对左右区间进行快速排序 组合:求解后已经有序了,排序好了,其实是个空操作。 算法分析: 快速排序的时间主要耗费在划分操作上,对长度为k的区间进行划分,共需进行k-1次关键字的比较。 在无序数组中选择划分的基准关键字是决定算法性能的关键。

时间复杂度: 最坏:划分后基准是在最左边或者最右边O(n^2) 最好:划分基准是中值O(nlogn) 平均:O(nlogn) 空间复杂度:快速排序需要一个栈来实现 最好:O(logn) 最坏:O(n) 快速排序是非稳定的。 8:选择排序:每一趟从待排序的记录中选出关键字最小的记录,顺序存放在已排序好的子文件的后面,直到全部记录排序完毕。

直接选择排序:n个记录经过n-1此直接选择排序得到有序结果 第一趟,在无序区中选择最小的值和第一个元素交换, 第二趟,在2-n中选择最小的值和第二个元素交换。。。 算法分析: 时间复杂度:O(n^2) 空间复杂度: 辅助空间一个监视哨,O(1),就地排序 直接选择排序是不稳定排序。

堆排序:是一树型选择排序,其特点是:在排序过程中,将序列看成是一颗完全二叉树的顺序存储结构,利用完全二叉树 中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。 堆的定义:n个关键字序列k1,k2,。。。kn称为堆,当且仅当满足如下关系ki<=k2i且ki<=k(2i+1) 或者ki>=k2i且ki>=k(2i+1),存储序列可以看做是一颗完全二叉树的存储结构

堆的实质:是满足一下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子结点的关键字。

大根堆:根结点的关键字是堆里所有结点关键字中最大者 小根堆:根结点的关键字是堆里所有结点关键字中最小者 注意:堆中任意子树也是堆。 堆排序与直接选择排序的区别:在直接选择排序中,会出现重复的比较次数,由于堆排序可以通过树型结构保存 部分比较结果,因此能够减少比较次数。 大根堆排序基本思想: 先将初始文件R建成一个大根堆,此堆为初始的无序区 然后将最大的记录R[1](堆顶)和无序区最后一个记录R[n]交换,由此得到新的无序区R[1,,,N-1]和有序区[N] 然后调整新的无序区为大根堆,继续交换元素,直到无序区只有一个元素为止。

算法分析:堆排序适合待排序的记录个数较多的情况,因其运行时间主要消耗在建立初始堆和调整堆时进行 的反复筛选上。 时间复杂度: 最坏:O(nlogn) 平均:O(nlogn)接近最坏性能, 由于堆排序建立初始堆所需的比较次数较多,所以堆排序不适宜记录小的文件 空间复杂度: 辅助空间一个监视哨,O(1),

就地排序 堆排序是不稳定排序。 9:归并排序:将这些有序的子序列进行归并,从而得到有序的序列。 插入排序,选择排序,交换排序对待待排序的初始状态不做任何要求,但是归并排序要求待排序序列已经部分有序。 部分有序:待排序列由若干个有序的子序列组成。 两个有序子序列归并:R[LOW,M],R[M+1,HIGH],R1。三个指针分别指向三个集合,比较R[I]和R[J]的值,直到一个集合已经 全部复制完毕,此时将另一非空的子文件中的剩余记录依次复制到R1中。 二路归并排序算法: 自底向上:第一趟归并,待排序的文件n个长度为1的子文件,然后两两归并,第二趟,长度为2,直到归并完成。 效率虽然高,但是可读性较差 自顶向下: 分解:当前区间一分为二,分裂点是中间点 求解:递归对两个子区间进行归并排序 组合:把两个子区间合成一个有序的区间。 递归的终止条件是子区间的长度为1。

算法分析: 存储结构要求:顺序和链表都可以。 时间复杂度: 对于长度n的文件,需要进行logn趟二路归并取下限,每趟归并时间为O(n) 所以最好还是最坏都是O(nlogn)

空间复杂度: 需要一个辅助向量来暂存两个有序子文件归并的结果。O(n) 归并排序是稳定的排序。 10:基数排序:前面的各种排序方法是根据关键字值大小排序的。基数排序是借助多关键字排序的思想对单个逻辑关键字进行排序。

最高位优先:排序是先对最高位关键字K0进行排序,然后在对K1进行排序,最后对最次位关键字Kd-1进行排序。

最低位优先:排序先对最次位关键字Kd-1进行排序,然后对关键字Kd-2进行排序,直到对最高位关键字K0进行排序。 用到分配和收集来实现。

比如扑克牌按花色和牌面大小两个关键字排序。 算法分析: 时间复杂度:排序所需的计算时间不仅和文件大小n有关,而且还与关键字的位数d,关键字的基r有关。 (十进制的基为10,二进制的基为2) O(d(n+r)) 空间复杂度:O(n+r) 基数排序是稳定的。 当n很大,记录的关键字位数较小且可以分解(整型或者字符串)时,采用基数排序。

11:排序的分类 按平均时间 O(n^2):简单拍排序:直接插入,冒泡,直接选择 O(n^logn):快速,堆,归并 O(n^(1+a)):希尔 O((n+r)):基数 各种排序方法比较:简单排序中直接插入最好;快速排序最快;当文件为正序时,直接插入和冒泡均为最佳。 不同条件下排序方法的选择: 若n较小(n<=50),采用直接插入或者直接选择。

若n较大,选择时间复杂度(n^logn)的排序方法:快排,堆,归并 若文件初始状态为有序:直接插入,冒泡或者随机快排。 存储结构: 链表:插入排序,归并排序,基数排序,使之减少记录的移动次数 12:外部排序:磁盘文件排序和磁带文件排序 区别:初始归并段在外存储介质中的分布方式,磁盘时直接存取设备,磁带是顺序存取设备。

方法:按照内存大小,在外存中分成若干个h的子文件,然后读入内存进行内部排序,然后输出外存, 最后把这些归并段整合到一起。 归并方法:二路归并,多路归并。

  • 版权声明: 本博客所有文章,未经许可,任何单位及个人不得做营利性使用!转载请标明出处!如有侵权请联系作者。
  • Copyrights © 2015-2023 翟天野

请我喝杯咖啡吧~