数据结构是指数据元素的集合及元素间的相互关系和构造方法,结构就是元素之间的关系。在数据结构中,元素之间的相互关系是数据的逻辑结构。按照逻辑关系的不同将数据结构分为线性结构和非线性结构,其中,线性结构包括线性表、栈、队列、串,非线性结构主要包括树和图。数据元素及元素之间关系的存储形式称为存储结构,主要有顺序存储和链式存储两种基本方式。
线性结构的特点是数据集合中的元素之间是一种线性关系,数据元素“一个接一个地排列”,也就是一个序列。
线性表是指一个序列,常采用两种存储方法:顺序存储和链式存储,主要的基本操作是插入、删除和查找。
线性表的定义:
一个线性表是n个有限序列(n≥0),通常表示为( a1 , a2 , … , an ),其特点是在非空的线性表中:
a)存在唯一的一个称作“第一个”的元素;
b)存在唯一的一个称作“最后一个”的元素;
c)除第一个元素外,序列中的,每个元素均只有一个直接前驱;
d)除最后一个元素外,序列中的每个元素均只有一个直接后继。
线性表的存储结构: ⭐ ⭐ ⭐
线性表的顺序存储:
指用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。这种存储方式下,元素间的逻辑关系无须占用额外的空间来存储。
优点:可以随机存取表中的元素,按序号查找元素的速度很快;
缺点:插入和删除操作需要移动元素。
一般地,以 LOC(a)表示线性表中第一个元素的存储位置,L表示每个元素所占空间的大小,
则顺序存储结构中,第i个元素a的存储位置为:
LOC(a)=LOC(a)+(i-1)* L
线性表的链式存储(链表):
线性表的链式存储用节点来存储数据元素,元素的节点地址可以连续,也可以不连续,因此,存储数据元素的同时必须存储元素之间的逻辑关系。另外,节点空间只有在需要的时候才申请,无须实现分配,基本的节点结构:
数据域存储数据元素的值,指针域存储当前元素的直接前驱或直接后继元素的位置信息,指针域中所存储的信息称为指针或链,单链表节点中只有一个指针域。
在链式存储结构中,只需要一个指针指向第一个结点,就可以按照链接关系顺序地访问表中的任意一个元素。 Head指针不存储实际的数据元素,用于辅助数据元素的定位,方便插入和删除操作。
优点:链式存储结构下进行插入和删除,实质是对相关指针的修改,不需要移动元素;
缺点:只能顺序地访问元素,而不能对元素进行随机存储。
链表的类别:
根据节点中指针信息的实现方式,有:
a)单链表:包含两个域,一个信息域和一个指针域,这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。 单链表存储不能随机访问表中的任一结点,必须从头结点依次.next。
b)双向链表:每个节点包含两个指针,分别指明当前元素的直接前驱和直接后继信息,可在两个方向上遍历链表中的元素。
c)循环链表:表尾节点的指针指向表中的第一个节点,可从表中任意节点开始遍历整个链表。
d) 静态链表:借助数组来描述线性表的链式存储结构。
线性表的查找方法: ⭐ ⭐ ⭐
a) 顺序查找
算法非常简单,但是效率较低,因为它是用所给关键字与线性表中各元素的关键字逐个比较,直到成功或失败。
b) 折半查找
优点是比较次数少,查找速度快,平均性能好;
缺点是要求待查表是有序表,且插入和删除困难。
适用于不经常变动而查找频繁的有序列表。
一般不进行表的插入和删除操作。
需要对中间元素进行快速定位,在链表结构上无法实现。
c) 分块查找
又称索引查找,主要用于分块有序表的查找。
所谓分块有序是指将线性表 L (一堆数组)分成m个子表(要求每个子表的长度相等),
且第 i+1 个子表中的每一个项目均大于第 i 个子表中的所有项目。
因此,分块查找的关键在于建立索引表,其查找的平均长度介于顺序查找和折半查找之间。
栈和队列是常用的两种数据结构,它们的逻辑结构与线性表相同,其特点在于运算受到了限制。栈按“后进先出”规则进行操作(类似杯子喝水),队列“先进先出”规则进行操作(类似沙漏)。
栈: 只能通过访问它的一端来实现数据存储和检索操作,按先进后出(FILO:First In Last Out)或后进先出(LIFO)规则进行插入删除操作。在栈中进行插入和删除操作的一端成为栈顶(Top),另一端成为栈底(Bottom)。
栈的基本运算:
a) 初始化栈initStack(S):创建一个空栈 S;
b) 判栈空isEmpty(S):当S为空时返回“真”值,否则返回“假”值;
c) 入栈 push(S,x):将元素x 加入栈顶,并更新栈顶指针;
d) 出栈 pop(S): 将栈顶元素从栈中删除,并更新栈顶指针。若需要得到栈顶元素的值,可将 pop(S)定义为一个函数,它返回栈顶元素的值;
e) 读栈顶元素 top(S): 返回顶元素的值,但不修改栈顶指针。
栈的存储结构:
a) 栈的顺序存储
栈的顺序存储是指用一组地址连续的存储单元依次存储自栈顶到栈底的数据元素,同时附设指针 top 指示栈顶元素的位置。
采用顺序存储结构的栈也称为顺序栈。
在顺序存储方式下,需要预先定义或申请栈的存储空间,也就是说栈空间的容量是有限的。
因此在顺序栈中,当一个元素入栈时,需要判断是否栈满(栈空间中没有空闲单元),若栈满,则元素入栈会发生上溢现象。
b) 栈的链式存储
为了克服顺序存储的栈可能存在上溢的不足,可以用链表存储栈中的元素。
用链表作为存储结构的栈也称为链栈。由于栈中元素的插入和删除仅在栈顶一端进行,
因此不必设置头结点,链表的头指针就是栈顶指针。
栈的应用:
栈的典型应用包括表达式求值、括号匹配等,在计算机语言的实现以及将递归过程转变为非递归过程的处理中,栈有重要的作用。
函数调用和返回控制是用栈实现的。 ⭐ ⭐ ⭐
队列: 是一种先进先出(FIFO:First In First Out)线性表,只允许在标的一段插入元素,而在标的另一端删除元素。允许插入元素的一端称为队尾,允许删除元素的一端称为对队头。
队列的基本运算:
a)初始化队列 initQucue(Q): 创建一个空的队列 Q;
b)判队空 isEmpty(Q):当队列为空时返回“真”值,否则返回“假”值;
c)入队 enQueue(Q,x):将元素 x 加入到队列Q的队尾,并更新队尾指针;
d)出队 deQueue(Q):将队头元素从队列Q 中删除,并更新队头指针;
e)读队头元素 frontQucue(Q): 返回队头元素的值,但不更新队头指针。
队列的存储结构:
a) 队列的顺序存储
队列的顺序存储结构又称为顺序队列,由于队中元素的插入 和删除限定在表的两端进行,因此设置队头指针和队尾指针,分别指示出当前的队首元素和队尾元素。
在顺序队列中,为了简化运算,元素入队时,只修改队尾指针;元素出队时,只修改队头指针。
由于顺序队列的存储空间是提前设定的,因此队尾指针会有一个上限值,当队尾指针达到其上限时,
就不能只通过修改队尾指针来实现新元素的入队操作了。此时,可将顺序队列假想成一个环状结构,称之为循环队列。
b) 队列的链式存储
队列的链式存储也称为链队列。为了便于操作,可给链队列添加一个头结点,并令头指针指向头结点,因此,队列为空的判定条件是头指针和尾指针的值相同,且均指向头结点。
队列的应用:
队列常用于处理需要排队的场合,如操作系统中处理打印任务的打印队列、离散事件的计算机模拟等。
串: 是仅有字符构成的有限序列,是取值范围受限的线性表。
串的基本运算:
a) 赋值操作 SrAssign(s,t):将串t的值赋给串s;
b) 连接操作 Concat(s,t):将串t接续在串s的部,形成一个新串;
c) 求串长 StrLength(s):返回串s 的长度;
d) 串比较 StCompare(s,t): 比较两个串的大小。返回值-1、0和1分别表示st三种情况;
e) 求子串 SubString(s,start,len):返回串s中从start 开始的长度为len 的字符序列。
串的存储结构:
a) 串的顺序存储
该方式是用一组地址连续的存储单元来存储串值的字符序列。由于串中的元素为字符,
所以可通过程序语言提供的字符数组定义串的存储空间(即存储空间的容量固定),
也可以根据串长的需要动态申请字符串的空间(即存储空间的容量可扩充或缩减)。
b) 串的链式存储
字符串也可以采用链表作为存储结构,当用链表存储串中的字符时,每个结点中可以存储一个字符,也可以存储多个字符,
需要考虑存储密度问题。
通常情况下,字符串存储在一维字符数组中,每个字符串的末尾都有一个串结束符,在C/C++程序中以特殊字符“\0”作为结束标记。
数组可看作是线性表的推广,其特点是多维数组的数据元素仍然是一个表。
数组:
数组的定义及基本运算:
一维数组是长度固定的线性表,数组中的每个数据元素类型相同。n 维数组是定长线性表在维数上的扩张,即线性表中的元素又是一个线性表。
数组结构的特点:
a) 数据元素数目固定。一旦定义了一个数组结构,就不再有元素的增减变化。
b) 数据元素具有相同的类型。
c) 数据元素的下标关系具有上下界的约束且下标有序
在数组中通常做下面两种操作:
(1) 取值操作。给定一组下标,读其对应的数据元素
(2) 赋值操作。给定一组下标,存储或修改与其相对应的数据元素
数组顺序存储:
由于数组一般不作插入和删除运算,也就是说,一旦定义了数组,则结构中的数据元素个数和元素之间的关系就不再发生变动,因此数组适合于采用顺序存储结构。
矩阵:
在一些矩阵中,存在很多值相同的元素或者是零元素。为了节省存储空间,可以对这类矩阵进行压缩存储。压缩存储的含义是为多个值相同的元素只分配一个存储单元,对零元不分配存储单元。
矩阵分类:
党见的特殊知阵有对称矩阵、三角矩阵和对角矩阵等。
a) 对称矩阵 :若矩阵An*n中的元素有aij =aji (1≤i,j≤n),则称之为对称矩阵。
特性:
1.对称矩阵是一个方阵,即就是行和列长度相等
2.对称矩阵中的所有元素都是相互对称的,即就是矩阵的下三角和上三角是对称的
元素的位置:
1.行=列时,此时表明此元素位于对角线
2.行>列时,表明此元素位于下三角中
3.行<列时,表明此元素位于上三角中
b)三对角矩阵:
• 对角矩阵是指矩阵中的非零元素都集中在以主对角线为中心的带状区域中,其余的矩阵元素都为零。
• 下面主要考虑三对角矩阵,即只有主对角线及其左右两边为非零元素。
• 若以行为主序将n阶三对角矩阵An×n的非零元素存储在一维数组B[k] (0≤k<3n-2)中,
则元素位置之间的对应关系为: k=3 × (i-1)-1+j-i+1=2i+j-3 (1≤i,j≤n)。
c) 稀疏矩阵: 在一个矩阵中,若非零元素的个数远远少于零元素的个数,且非零元素的分布没有规律,则称之为稀疏矩阵。
可以用一个三元组(i,j,aij)唯一确定矩阵中的一个元素。
树:
树是n(n≥0)个节点的有限集合,当n=0时称为空树,在任一非空树(n>0)中,有且仅有一个称为根的节点,其余节点可分为m(m≥0)个互不相交的有限集T1 , T2 ,…, Tm,其中每个集合又是一棵树,并且称为根节点的子树。
树的相关术语:
a) 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点,如上图中B节点是E、F节点的双亲节点;
b) 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点,如上图中B、C、D是A的子节点;
c) 兄弟节点:具有相同双亲节点的节点互称为兄弟节点,B、C、D和E、F互为兄弟节点;
d) 节点的度:一个节点含有的子树的个数称为该节点的度;
e) 叶子节点或终端节点:度为0的节点称为叶子节点,如上图C、E、F、G;
f) 非终端节点或分支节点:度不为0的节点;
g) 树的度:一棵树中,最大的节点的度称为树的度;
h) 节点的层次:从根开始定义,根为第1层,根的子节点为第2层,以此类推;
i) 树的高度或深度:树中节点的最大层次,上图中树的深度为2;
j) 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
k) 有序(无序)树:若将树中节点的各子树看成是从左到右具有次序的,即不能交换,则称该树为有序树,否则称为无序树。
l) 森林:m(m>=0)棵互不相交的树的集合;
二叉树:
二叉树是n(n≥0)个节点的有限集合,它或者是空树(n=0),或者是由一个根节点及两颗不相交的、分别称为左子树和右子树的二叉树所组成,简单来说,就是度不超过2的树(节点最多有两个叉)。
树与二叉树的区别:
a) 二叉树中节点的子树要区分左子树和右子树,即使在节点只有一颗子树的情况下也要明确指出该子树是左子树还是右子树,树种则不区分;
b) 二叉树中节点的最大度为2,而树中不限制节点的度数。
二叉树的性质:
a) 二叉树第i层(i≥1)上至多有2^{i-1}个节点;
b) 深度为k的二叉树至多有2^k-1个节点( k ≥ 1 );
c) 对任何一棵二叉树,若其终端节点数为n0,度为2的节点数为n2,则n0 = n2 + 1;
d) 具有n个节点的完全二叉树的深度为⌊log2^n+1⌋;(向下取整,小于等于)。
满二叉树和完全二叉树:
满二叉树:一个二叉树(深度为k),每一个层的节点数都达到最大值2^k-1。
完全二叉树:当深度k、有n个节点的二叉树,其每一个节点都与深度为k的满二叉树中编号为1~n的节点一一对应。叶子节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边的若干位置的二叉树。
满二叉树一定是完全二叉树,但是完全二叉树不一定是满二叉树。
二叉树的存储结构:
顺序存储:
用一组地址连续的存储单元存储二叉树中的节点,必须把节点排成一个适当的线性序列,并且节点再这个序列中的相互位置能反映出节点之间的逻辑关系,对于深度为k的完全二叉树,除第k层外,其余各层中含有最大的结点数,即每一层的结点数恰为其上一层结点数的两倍,由此从一个结点的编号可推知其双亲、左孩子和右孩子的编号。
假设有编号为i的结点,则有:
若 i=1,则该结点为根结点,无双亲;若 >1,则该结点的双亲结点为[i/2]
若 2i ≤ n,则该结点的左孩子编号为 2i,否则无左孩子
若 2+1 ≤ n,则该结点的右孩子编号为2i+1,否则无右孩子
显然,顺序存储结构对完全二叉树而言既简单又节省空间,而对于一般二叉树来说浪费空间了,不适用。
在坏的情况下,一个深度为h 且只有 h 个结点的二叉树(单枝树)却需要2^k-1个存储单元。
链式存储:
由于二叉树中节点包含有数据元素、左子树根、右子树根及双亲等信息,因此可以用三叉链表或二叉链表(即一个节点含有3个指针或2个指针)来存储二叉树,链表的头指针指向二叉树的根节点。
二叉树的遍历:
遍历是按某种策略访问树中的每个节点,且仅访问一次。
按照遍历左子树要在遍历右子树之前进行的约定,依据访问根节点的位置的不同,可以得到二叉树的前序、中序和后序三种遍历方法。
a) 前序遍历(根→左→右):
访问根结点的操作发生在遍历其左右子树之前,若二叉树非空,操作步骤:
(1)访问根节点;(2)遍历左子树;(3)遍历右子树。
b) 中序遍历(左→根→右):
访问根结点的操作发生在遍历其左右子树之中(间),若二叉树非空,操作步骤:
(1)中序遍历左子树;(2)访问根节点;(3)中序遍历右子树。
c) 后序遍历(左→右→根):
访问根结点的操作发生在遍历其左右子树之后,若二叉树非空,操作步骤:
(1)遍历左子树;(2)遍历右子树;(3)访问根节点。
d) 层次遍历:按层自上而下,自左而右逐层访问树中各层节点。
最优二叉树:
最优二叉树又称为哈夫曼树,是一类带权路径长度最短的树。
权:是一个人为的概念,表示计算机对每个结点的访问频率。
路径长度:从树中一个节点到另一个节点之间的通路称为节点间的路径,该通路上分支数目。
树的路径长度:树根到每一个叶子之间的路径长度之和。
节点的带权路径长度:从该节点到树根之间的路径长度与该节点权的乘积。
树的带权路径长度为树中所有叶子节点的带权路径长度之和,记为:
其中n为带权叶子节点数目,wk为叶子节点的权值,lk为叶子节点到根节点的路径长度,根据定义,如下b图的为最优二叉树。
1、最优二叉树的一个应用是对字符集中的字符进行编码和译码。
2、给定N个权值作为N个叶子结点,构造一颗二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
3、霍夫曼树可以用来进行通信电文的编码和解码。
4、B-树是一种平衡的多路查找树:
a) B-树中,所有非终端结点也就是非叶子结点都会包含关键字;
b) 所有叶子结点都出现在同一层次上并且不带信息(可以看作是外部结点或查找失败的结点),
层次相同也就是高度相同,从根结点到每个叶子结点的路径长度相同;
c) 所有非终端结点包含的关键字数量是不确定的,指向的子树个数也是不确定的。
5、树的前序遍历与二叉树的先序遍历一样;树的后序与二叉树的中序遍历一样。
6、散列就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值,如此建立的表为散列表,散列表是可以动态创建的。
7、二分查找(折半查找):要求关键字必须采用顺序存储结构,并且必须按关键字的大小有序排序。
二叉查找树:
二叉查找树又称为二叉排序树,它或者是一棵空树,或者是具有如下性质的二叉树:
a) 若其左子树非空,则其左子树中每个节点的值(关键码)都小于该节点值(关键码);
b) 若其右子树非空,则其右子树中每个节点的值(关键码)都大于该节点值(关键码);
c) 左、右子树本身就是2棵二叉查找树。
d) 平衡二叉树:或者是空树,或者是满足:树中任一节点左右子树的深度相差不超过1。
结点的平衡度:其右子树的深度减去左子树的深度(因此平衡度只能为1,0,-1)。
对二叉树进行中序遍历,可得到一个关键码递增有序的节点序列,使用二叉查找树来查找树中的数值比普通的二叉树更方便。
构造二叉树时需进行平衡化处理,使每个节点左右子树的高度的绝对值不超过1。
图G(Graph)是由两个集合:V和E构成的二元组,其中V(Vertex)是图中顶点的非空有限集合,E(Edge)是图中边的有限集合,图G的顶点集和边集分别记为V(G)和E(G),而将图G记作G=(V,E)。
可以看出,一个顶点集合与连接这些顶点的边的集合可以唯一表示一个图。从数据结构的逻辑关系角度来看,图中任一顶点都有可能与图中其他顶点有关系,而图中所有顶点都有可能与某一顶点有关系,所以图中一个节点的前驱和后继的数目是没有限制的。
在图中,数据结构中的数据元素用顶点表示,数据元素之间的关系用边表示。
边数等于所有顶点的度数之和的一半。
有向图:
图中每条边都是有方向的,从顶点vi到vj的有向边< v i , v j >,vi称为弧尾,v j称为弧头,< v i , v j >和< v j , v i >为两条弧。
无向图:
图中每条边都是无方向的,< v i , v j > 和< v j , v i > 为同一条边。
Tips:
1、有向图中所有顶点的出度数之和等于入度数之和。
2、在有向图中,顶点为n的边数等于n(n-1) / 2 ,无向图中边数等于n(n-1) 。
完全图:
一个无向图具有n个顶点,且每个顶点与其他n − 1个顶点之间都有边则称为无向完全图,n个顶点的无向完全图的弧数目为n ( n − 1 ) / 2,n个顶点的有向完全图的弧数目为n ( n−1 ),因为任意两个不同顶点之间都存在方向相反的两条弧。
度、出度、入度:
顶点 v 的度是指关联于该顶点的边的数目,记作D(v),若为有向图,度表示该顶点的入度和出度之和。
顶点的入度是以该顶点为终点的有向边的数目;
顶点的出度指以该顶点为起点的有向边的数目。
路径:
第一个顶点和最后一个顶点相同的路径称为回路或环。若一条路径上除了顶点Vp和顶点Vq可以相同外,其余顶点均不相同,这种路径称为一条简单路径。
子图:
连通图:
如果无向图 G中任意两个顶点都是连通的,则称其为连通图。图 3-22(b)所的无向图是连通图。
强连通图:
从顶点Vi到顶点Vj和从顶点Vj到顶点Vi都存在路径,则称图G为强连通图。
网: 边或者弧具有权值的图
图的存储结构:
邻接矩阵表示法:
邻接链表表示法:
为图中的每个顶点建立一个单链表,第i个单链表中的节点表示依附于定点v i 的边(对于有向图是以v i为尾的弧)。
其中各参数的含义如下:
adjvex:指示与顶点v邻接的顶点的序号
nextarc:指示下一条边或弧的结点。
info:存储和边或弧有关的信息,如权值等
data:存储顶点的名或其他有关信息。
firstarc:指示链表中的第一个结点。
图的遍历:
深度优先搜索( DFS ):
访问起始点 v;
若v的第1个邻接点没访问过,深度遍历此邻接点;
若当前邻接点已访问过,再找v的第2个邻接点重新遍历。
广度优先搜索( BFS ):
在访问了起始点v之后,依次访问 v的邻接点;
然后再依次(顺序)访问这些点(下一层)中未被访问过的邻接点;
直到所有顶点都被访问过为止。
算法是问题求解过程的精确描述,它为解决某一特定类型的问题规定了一个运算过程。
算法特性:
a)有穷性:一个算法必须在执行有穷步骤之后结束,且每一步都可在有穷时间内完成。
b)确定性:算法的每一步必须是确切定义的,不能有歧义。
c)可行性:算法应该是可行的,这意味着算法中所有要进行的运算都能够由相应的计算装置所理解和实现,并可通过有穷次运算完成。
d)输入:一个算法有零个或多个输入,它们是算法所需的初始量或被加工的对象的表示这些输入取自特定的对象集合。
e)输出:一个算法有一个或多个输出,它们是与输入有特定关系的量。
算法考查:
a)正确性:正确性也称为有效性,是指算法能满足具体问题的要求。即对任何合法的输入,算法都能得到正确的结果。
b)可读性:可读性指算法被理解的难易程度。人们常把算法的可读性放在比较重要的位置,因为晦涩难懂的算法不易交流和推广使用,也难以修改和扩展。因此,设计的算法应尽可能简单易懂。
c)健壮性:健壮性也称为鲁棒性,即对非法输入的抵抗能力。对于非法的输入数据,算法应能加以识别和处理,而不会产生误动作或执行过程失控。
d)效率:粗略地讲,就是算法运行时花费的时间和使用的空间。对算法的理想要求是运行时间短、占用空间小。
算法与数据结构:
算法实现时总是建立在一定的数据结构基础之上,计算机程序从根本上看包括两方面的内容:一是对数据的描述,二是对操作(运算)的描述。概括来讲,在程序中需要指定数据的类型和数据的组织形式就是定义数据结构,描述的操作步骤就构成了算法。因此,从某种意义上可以说“数据结构+算法 = 程序”。
算法描述:
常用的算法描述方法有流程图、N/S 盒图、伪代码和决策表等。
流程图:
流程图 (fow chart)即程序框图,是历史最久、流行最广的一种算法的图形表示方法。每个算法都可由若干张流程图描述。流程图给出了算法中所进行的操作以及这些操作执行的逻辑顺序。程序流程图包括三种基本成分:加工步骤,用方框表示;逻辑条件,用菱形表示;控制流,用箭头表示。
N/S 盒图:
盒图是结构化程序设计出现之后,为支持这种设计方法而产生的一种描述工具。在 N/S 图中,每个处理步用一个盒子表示,盒子可以嵌套。对于每个盒子,只能从上面进入,从下面走出,除此之外别无其他出入口,所以盒图限制了随意的控制转移,保证了程序的良好结构。
伪代码:
用伪代码描述算法的特点是借助于程序语言的语法结构和自然语言叙述,使算法具有良好的结构又不拘泥于程序语言的限制。这样的算法易读易写,而且容易转换成程序。
决策表:
假设含有n个记录的文件内容为{ R 1 , R 2 , … , R n },其相应的关键字为{ k 1 , k 2 , … , k n }。
常见的内部排序算法:
直接插入排序:
在插入第i个记录时,R 1 , R 2 , … , R i − 1已经排好序,这时将记录R i的关键字依次与关键字k i − 1 , k i − 2 , … , k 1进行比较,从而找到Ri 应该插入的位置,插入位置及其后的记录依次向后移动。
直接插入排序是一种稳定的排序方法,其时间复杂度为 O(n^2)。排序过程中仅需要一个元素的辅助空间,空间复杂度为 O(1)。
void insertSort(int data[], int n)
/*将数组 data[0]~data[n-1]中的n个整数按非递减有序的方式进行排列*/
{
int i, j;
int tmp;
for (i = 1; i < n; i++) {
if (data[i] < data[i - 1]) {
tmp = data[i];
data[i] = data[i - 1];
for (j = i - 1; j >= 0 && data[j] > tmp; j--)
data[j + 1] = data[j];
data[j + 1] = tmp;
} /*if*/
} /*for*/
} /*insertSort*/
冒泡排序:
首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则交换两个记录的值,然后比较第二个记录和第三个记录的关键字,以此类推,直至第n-1个记录和第n个记录的关键字比较完为止。
void bubbleSort(int data[], int n)
/*将数组data[]~data[n-1]中的n个整数按非递减有序的方式进行排列*/
{
int i, j, tag;
/*用tag 表示排序过程中是否交换过元素值*/
int tmp;
for (i = 1, tag = 1; tag == 1 && i < n; i++) {
tag = 0;
for (j = 0; j < n - i; j++)
if (data[j] > data[j + 1]) {
tmp = data[j];
data[j] = data[j + 1];
data[j + 1] = tmp;
tag = 1;
} /*if*/
} /*for*/
} /*bubbleSort*/
简单选择排序:
void selectSort(int data[], int n)
/*将数组 data[]~data[n-1]中的n个整数按非递减有序的方式进行排列*/
{
int i, j, k;
int tmp;
for (i = 1; i < n; i++) {
k = i;
for (j = i + 1; j <= n; j++)
/*找出最小元素的下标,用 k 表示*/
if (data[j] < data[k]) k = j;
if (k != i)[tmp = data[i]; data[i] = data[k]; data[k] = tmp;
} /*if*/
} /*for*/
} /*selectSort*/
每次选出关键字最小的记录,并和未排序记录进行交换。
希尔排序:
又称为“缩小增量排序”,是对直接插入排序方法的改进,先将整个待排记录序列分割成若干个子序列,然后分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。
快速排序:
通过一趟排序将待排的记录划分为独立的两部分,称为前半区和后半区,其中,前半区中记录的关键字均不大于后半区记录的关键字,然后再分别对这两部分记录继续进行快速排序,从而使整个序列有序。
1、直接插入排序:按顺序插入待排关键字,插入时依次查找位置,直接插入,后面的依次后移。
2、冒泡排序:依次把相邻的两个记录进行比较,然后交换位置。
3、简单选择排序:每次选择最小的,与第一个没有排过序的记录交换。
4、希尔排序:间隔若干个空的记录分为一组,进行直接插入排序,依次将间隔缩小到1为止。
5、快速排序:设两个指针指示头尾,从尾开始,首尾交替轮流和枢轴记录(第一个记录)进行比较,并交换位置。
6、堆排序:反复将待排序列建立成堆,并取堆顶。
7、归并排序: 两两归并为一组,再四个地录归并为一组,依此类推。
查找表是指由同一类型的数据元素(或记录)构成的集合,分为静态查找表(只进行查找和检索操作)和动态查找表(还可进行插入和删除操作),哈希表是一种动态查找表。
对于查找算法来说,其基本操作是“将记录的关键字与给定值进行比较”,因此,通常以“其关键字和给定值进行过比较的记录个数的平均值”作为衡量查找算法好坏的依据。为确定记录在查找表中的位置,需和给定值进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。
Pi为查找表中第i 个数据元素的概率,一般为1/n;Ci为找到第i个数据元素时已经比较过的次数,显然,Ci 随查找方法的不同而不同。
顺序查找:
从表中的一段开始,逐个进行记录的关键字和给定值的比较,若找到一个记录的关键字与给定值相等,则查找成功;若整个表中的记录均比较过,仍未找到关键字等于给定值的记录,则查找失败。
顺序查找是对顺序存储和链式存储方式的查找表都适用,优点算法简单且适用面广,缺点查找效率较低。
在等概率的情况下,顺序查找成功的平均查找长度为:
折半查找(二分查找):
折半查找基本思想:先令查找表中间位置记录的关键字和给定值比较,若相等,则查找成功;若不等,则缩小范围,直至新的查找区间中间位置记录的关键字等于给定值或者查找区间没有元素时(表名查找不成功)为止。
折半查找的平均查找长度为:
折半查找比顺序查找的效率要高,但它要求查找表进行顺序存储并且按关键字有序排列,一般不进行表的插入与删除操作。因此,折半查找适用于表不易变动,且经常进行查找的情况。
索引顺序查找:
索引顺序查找又称分块查找,是对顺序查找方法的一种改进。
在分块查找过程中,首先将表分成若干块,每一块中关键字不一定有序,但块之间是有序的,即后一块中所有记录的关键字均大于前一个块中最大的关键字。此外,还建立一个“索引表”,索引表按关键字有序。
树表查找:
二叉查找树是一种动态查找表,其特点是表结构本身是在查找过程中动态生成的,即对于给定值key,若表中存在关键字等于key的记录,则查找成功返回,否则插入关键字等于key的记录。
哈希查找:
前面几种查找方法,由于记录的存储位置与其关键码之间不存在确定的关系,所以查找时都要通过一系列对关键字的比较,才能确定被查记录在表中的位置,即这类查找方法都建立在对关键字进行比较的基础之上。
哈希表思想:
依据记录的关键码直接得到对应的存储位置,即要求记录的关键码与其存储位置之间存在一一对应的关系,通过这个关系,能很快的由关键码找到记录。
哈希函数:
关键字作为自变量,关键字存放的地址作为因变量。
哈希冲突:
对于某个哈希函数Hash和两个关键字K1和K2,如果K1≠ K2而H a s h ( K 1 ) = H a s h ( K 2 ),则称为冲突,对哈希函数来说,K1和K2称为同义词。
采用哈希法主要考虑的两个问题是哈希函数的构造和冲突的解决。
冲突处理:
开放定址法、链地址法、再哈希法等。
a)开放定制法
b)链地址法
链地址法是一种经常使用且很有效的方法,它将具有相同哈希函数值得记录组织成一个链表,当链域的值为NULL时,表示已没有后继记录。
用链地址法解决冲突构造的哈希表中查找元素,就是根据哈希函数得到元素所在链表的头指针,然后在链表中进行顺序查找的过程。
递归(recursion)是一种描述和解决问题的基本方法,用来解决可归纳描述的问题,或者是可分解为结构自相似的问题。所谓结构自相似,是指构成问题的部分与问题本身在结构上相似。
生成树:
设图G = ( V , E ) 是个连通图,如果其子图是一棵包含G 的所有顶点的树,则该子图成为G的生成树。
对于连通图来说,边是带权值的,生成树的各边也都带权值,于是就把生成树各边的权值总和成为生成树的权,把权值最小的生成树称为最小生成树,求最小生成树最常用的两种算法:
a) 普利姆算法:以顶点为准,从已选顶点所关联的未选边中找出权重最小的边,并且生成树不存在环。
b) 克鲁斯卡尔算法:以边为主,找权值最小的边。
求单源点的最短路径算法:
是指给定带权有向图G和源点v0,求从v0到G中其余各顶点的最短路径,按路径长度递增的次序产生最短路径的算法。
第三章 数据结构与算法(经典例题)
6、(2022上)计算机在处理算数表达式78+21*(36-34)时,先将其转换成"(5)"的后缀形式表示,然后利用(6)进行计算。
A、栈
B、队列
C、数组
D、串
参考答案:A
试题解析: 操作符在操作数前面,则称为前缀表达式。如果操作符在操作数之间,则称为中缀表达如果操作符在操作数后面,则称为后缀表达式。
计算机在存储中缀表达式时,需要使用树这种数据结构,如果表达式过于复杂,那么树的高度会变得很高,大大增加了时间复杂度和空间复杂度。如果转换成线性结构,那么效率将变得高很多,所以需要将中缀表达式先转换成前缀或者后缀表达式,然后依靠栈这种线性数据结构来进行计算。
7、(2022上)依次在初始为空的队列中插入元素5、6、7、8以后,紧接着做了两次删除操作,此时的队头元素是(7)。
A、5
B、6
C、7
D、8
参考答案:C
试题解析:队列是一种操作受限制的线性表,是先入先出的线性表.
8、(2022上)以下关于串的叙述中,错误的是(8)。
A、串是仅由字符构成的有限序列
B、串是取值范围受限的线性表
C、空串不包含任何字符
D、串只可以采用顺序存储方式
参考答案:D
试题解析:串是由零个或多个任意字符组成的有限序列。串可以采用多种存储方式,比如顺序存储方式,块链存储方式等。
9、(2022上)折半查找要求查找表中的数据为(9)。
A、顺序存储、有序排列
B、散列存储、有序排列
C、顺序存储、无序排列
D、散列存储、无序排列
参考答案:A
试题解析:
折半查找又称二分查找,它仅适用于有序的顺序表。
基本思路是:首先将给定值 key 与表中中间位置元素的关键字比较,若相等,则查找成功,返回该元素的存储位置;若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分(例如,在查找表升序排列时,若给定值 key 大于中间元素的关键字,则所查找的元素只可能在后半部分)。然后在缩小的范围内继续进行同样的查找,如此重复,直到找到为止,或确定表中没有所需要香找的元素,则香找不成功。返回查找失败的信息。
10、(2022上)(10)的基本思想是先将待排的记录划分为独立的两个部分,然后分别对这两部分记录再执行该排序算法,最终使整个序列有序。
A、快速排序
B、冒泡排序
C、堆排序
D、希尔排序
参考答案:A
试题解析:
快速排序 (Quick Sort) 是从冒泡排序算法演变而来的,实际上是在冒泡排序基础上的递归分治法。快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分。
5、(2021上)一个栈的输入序列为1,2,3,4,5,不可能得到的输出序列是( )。
A.2,3,4,1,5
B.5,4,1,3,2
C.2,3,1,4,5
D.1,5,4,3,2
6、(2021上)( )算法是不稳定的排序算法。
A.简单选择
B.冒泡
C.直接插入
D.归并排序
参考答案:A
试题解析:
选择排序的基本思想是:
设所排序序列的记录个数为n。i取1,2,…,n-1,从所有n-i+1个记录(Ri,Ri+1,…,Rn)中找出排序码最小的记录,i个记录交换。执行n-1趟 后就完成了记录序列的排序。
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否称为不稳定的。
举个例子,序列10,8,10,2,9,
我们知道第一遍选择第1个元素10会和2交换,那么原序列中2个10的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。
6、(2021上)( )是一种先进先出的线性表,只允许在表的一端插入元素,而在表的另一端删除元素。
A.栈
B.队列
C.串
D.树
参考答案:A
试题解析:
选择排序的基本思想是:
队列是先入先出的线性表,队列仅在表头删除元素、在表尾插入元素。
8、(2021上)一棵5层的二叉树,其最多有( )个结点,第5层最多有( )个结点。
问题1
A.15
B.16
C.31
D.32
问题2
A.15
B.16
C.31
D.32
参考答案:C、B
试题解析:
二叉树的特性:
1、在二叉树的第i层上最多有2 个结点(i≥1);
2、深度为k的二叉树最多有2 -1个结点(k≥1);
3、对任何一棵二叉树,如果其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1。
代入公式得到正确答案为C,B。
9、(2021上)( )排序又被称为缩小增量排序,是对直接插入排序方法的改进。
A.简单选择
B.冒泡
C.快速
D.希尔
参考答案:D
试题解析:
希尔排序是插入排序的一种,又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。希尔排序稳定排序算法。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止了。
5、(2020上)在常见的数据结构中,( )是只能通过访问它的端来实现数据存储和检索的一种线性数据结构,它的修改遵循先进后出的原( )是一种先进先出的线性表。( )是取值范围受限的线性表。
问题1
A.链表
B.队列
C.栈
D.串
问题2
A.链表
B.队列
C.栈
D.串
问题3
A.链表
B.队列
C.栈
D.串
参考答案:C、B、D
试题解析:
本题考查数据结构方面的基础知识。
栈和队列都是操作受限的线性表,栈仅在表尾插入和删除元素,队列仅在表头删除元素、在表尾插入元素。
队列是先入先出的线性表,栈是后进先出的线性表。一个线性序列经过队列结构后只能得到与原始序列相同的序列,而经过一个栈结构后则可以得到多种元素序列。
串是由零个或多个任意字符组成的有限序列。
6、(2020上)二叉树遍历是按照某种策略访问树中的每个结点,且仅访问一次。按照遍历左子树要在遍历右子树之前进行的原则,根据访)位置的不同,可得到二叉树的前序、中序和后序三种遍历方法。
A.根节点
B.导航节点
C.叶子结点
D.兄弟节点
参考答案:A
试题解析:
本题考查数据结构基础知识。
遍历运算是二叉树的基本运算,主要有先序、中序、后序和层序遍历。
先序遍历的基本方法:对于非空二叉树,先访问根结点,然后先序遍历根的左子树,最后先序遍历根的右子树此,若已知某二叉树的先序遍历序列,则可直接得到其树的根结点。
中序遍历的基本方法:对于非空二叉树,先中序遍历根的左子树,然后访问根结点,最后中序遍历根的右子树此,若已知某二叉树的根结点,则可根据中序遍历序列将该二叉树左右子树上的结点划分开。
后序遍历的基本方法:对于非空二叉树,首先后序遍历根的左子树,接着后序遍历根的右子树,最后访问根结因此,若已知某二叉树的后序遍历序列,则可直接得到其树根结点。
因此,按照遍历左子树要在遍历右子树之前进行的原则,根据访问根结点位置的不同, 可得到二叉树的前序、序和后序三种遍历方法。
7、(2020上)以下有关哈夫曼树的说法中,错误的是( )。
A.哈夫曼树又被称为最优二叉树
B.哈夫曼树是一种带 权路径长度最短的树
C.具有n个叶子结点的权值为W ,W , … W 的最优二叉树是唯一的
D.哈夫曼树可以用来进行通信电文的编码和解码
参考答案:C
试题解析:
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二树,也称为哈夫曼树。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
哈夫曼树可以用来进行通信电文的编码和解码。利用哈夫曼树求得的用于通信的二进制编码称为哈夫曼编码。从根到每个叶子结点都有一条路径,对路径上的各分支约定指向左子树的分支表示“0”码,指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子结点对应的字符编码,即是哈夫曼编码。
具有n个叶子结点的权值为W ,W , … W 的最优二叉树的形态不是唯一的。
8、(2020上)查找算法中,( )要求查找表进行顺序存储并且按照关键字有序排列,一般不进行表的插入与删除操作。
A.顺序查找
B.折半查找
C.分块查找
D.动态查找
参考答案:B
试题解析:
本题考查数据结构方面的基础知识。
线性表的查找有顺序查找、折半查找、分块查找方法。
其中,顺序查找方法的特点是算法非常简单,但效率较低.,因为它是用所给关键字与线性表中各元素的关键字个比较,直到成功或失败。
折半查找方法的优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入和删除难。因此,折半查找方法适用于不经常变动而查找频繁的有序表。
分块查找方法又称索引查找,它主要用于“分块有序”表的查找。所谓“分块有序”是指将线性表L(一维数组)分个子表(要求每个子表的长度相等),且第i+1个子表中的每一个项目均大于第i个子表中的所有项目。“分块有序”表应该包括线性表L本身和分块的索引表I。因此,分块查找的关键在于建立索引表I,其查找的平均长度介于序查找和折半查找之间。
10、(2020上)以下关于哈希函数的说法中,不正确的是( )。
A.哈希表是根据键值直接访问的数据结构
B.随机预言机是完美的哈希函数
C.哈希函数具有单向性
D.哈希函数把固定长度输入转换为变长输出
参考答案:D
试题解析:
Hash,一般翻译为散列、杂凑,或音译为哈希,是把任意长度的输入通过散列算法变换成固定长度的输出,该出就是散列值。这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定的消息摘要的函数。
哈希表是根据键(Key)而直接访问在内存存储位置的数据结构。
在密码学里面,随机预言机(英语:Random oracle)是一部预言机,对任何输入都回传一个真正均匀随机的出,不过对相同的输入,该预言机每次都会用同一方法输出。换句话说,随机预言机是一个将所有可能输入与作随机映射的函数。
5、(2019上)令序列X、Y、Z的每个元素都按顺序进栈,且每个元素进栈和出栈仅一次。则不可能得到的出栈序列是( )。
A.X Y Z
B.X Z Y
C.Z X Y
D.Y Z X
参考答案:C
试题解析:
栈的顺序:先进后出。如要Z先出,则至少需要X-Y-Z依次全部进栈,此时栈内容已确定,出栈顺序只能为Z-Y因此,得不到序列ZXY。
6、(2019上)以下关于单链表存储结构特征的叙述中,不正确的是( )。
A.表中结点所占用存储空间的地址不必是连续的
B.在表中任意位置进行插入和删除操作都不用移动元素
C.所需空间与结点个数成正比
D.可随机访问表中的任一结点
参考答案:D
试题解析:
单链表存储不能随机访问表中的任一结点,必须从头结点依次.next。
7、(2019上)B-树是一种平衡的多路查找树。以下关于B-树的叙述中,正确的是( )。
A.根结点保存树中所有关键字且有序排列
B.从根结点到每个叶结点的路径长度相同
C.所有结点中的子树指针个数都相同
D.所有结点中的关键字个数都相同
参考答案:B
试题解析:
B-树中,所有非终端结点也就是非叶子结点,都会包含关键字,A选项错误。
B-树中,所有叶子结点都出现在同一层次上并且不带信息(可以看作是外部结点或查找失败的结点),层次相就是高度相同,从根结点到每个叶子结点的路径长度相同,B选项正确。
B-树中,所有非终端结点包含的关键字数量是不确定的,指向的子树个数也是不确定的,所以C选项和D选项错误。
8、(2019上)对于给定的关键字序列{47, 34, 13, 12, 52, 38, 33, 27, 5},若用链地址法(拉链法)解决冲突来构造哈希表,且哈希函数H(key)=key%11,则( )。
A.哈希地址为1的链表最长
B.哈希地址为6的链表最长
C.34和12在同一个链表中
D.13和33在同一个链表中
参考答案:C
试题解析:
链地址法(拉链法):在查找表的每一个记录中增加一个链域,链域中存放下一个具有相同哈希函数值的记录储地址。即利用链域将发生冲突的记录链接在一个链表里。
本题对于给定的关键字序列{47, 34, 13, 12, 52, 38, 33, 27, 5},哈希函数为H(key)=key%11,则其哈希值分{3, 1, 2, 1, 8, 5, 0, 5, 5}
可以看到哈希地址为5的冲突最多,其对应的链表最长,A选项和B选项错误。
34和12的哈希值都为1,放在同一个链表中,C选项正确。
13的哈希值为2,33的哈希值为0,不在同一个链表中,D选项错误。
9、(2019上)某有向图G的邻接表如下图所示,可看出该图中存在弧<V , V >,而不存在从顶点V 出发的弧。以下关于图G的叙述中,错误的是( )。
A.G中存在回路
B.G中每个顶点的入度都为1
C.G的邻接矩阵是对称的
D.不存在弧<V3, V1>
参考答案:C
试题解析:
根据邻接表,这里存在4个有向弧,分别为V →V ,V →V ,V →V ,V →V 。
分析可得,图中存在 V →V ,V →V ,V →V 回路,A选项正确。
V 入度为1, V 入度为1, V 入度为1, V 入度为1,B选项正确。
转换为邻接矩阵M,可以发现M[0,2]=1,M[2,0]=0,即 V 到 V 存在弧, V 到 V 不存在弧,邻接矩阵并不对称。所以C选项错误。
没有 V →V 的有向弧,D选项正确。
也可以直接画出对应的图和邻接矩阵如下:
根据图示分析,可以看到C选项不正确
10、(2019上)已知有序数组a的前10000个元素是随机整数,现需查找某个整数是否在该数组中。以下方法中,( )的查找效率最高。
A.二分查找法
B.顺序查找法
C.逆序查找法
D.哈希查找法
参考答案:A
试题解析:
对于有序序列的查找,二分法效率较高。
5、(2018上)设有n阶三对角矩阵A,即非零元素都位于主对角线以及与主对角线平行且紧邻的两条对角线上,现对该矩阵进行按行压缩存若其压储空间用数组B表示,A的元素下标从0开始,B的元素下标从1开始。已知A[0,0]存储在B[1],A[n-1,n-1]存[3n-2],那么非零元素A[i,j](0≤ i<n,0≤ j<n,│i-j│≤1)存储在B[( )]。
A.2i+j-1
B.2i+j
C.2i+j+1
D.3i-j+1
参考答案:A
试题解析:
对于有序序列的查找,二分法效率较高。
代入A[0, 0]到ABCD四个选项中,得到B[1]的,只有C和D;再代入A[n-1, n-1]得到B[3n-2]的只有C正确,D选项到的是B[3n-1]
6、(2018上)用哈希表存储元素时,需要进行冲突(碰撞)处理,冲突是指( )。
A.关键字被依次映射到地址编号连续的存储位置
B.关键字不同的元素被映射到相同的存储位置
C.关键字相同的元素被映射到不同的存储位置
D.关键字被映射到哈希表之外的位置
参考答案:A
试题解析: A选项为一种解决冲突的办法。题干问的是冲突是什么,自然是B选项的意思。
7、(2018上)对有n个结点、e条边且采用数组表示法(即邻接矩阵存储)的无向图进行深度优先遍历,时间复杂度为( )。
A.O(n )^2
B.O(e^2 )
C.O(n+e)
D.O(n*e)
参考答案:A
试题解析:
当用二维数组表示邻接矩阵图的存储结构时,查找每个顶点的邻接点所需时间为 O(n ^2),其中n 为图中顶点数当以邻接表作图的存储结构时,e 为无向图中边的数或有向图中弧的数,深度优先搜索遍历图的时间复杂度为O(n+e) 。