数据结构考研讲义.pdf





《数据结构考研讲义.pdf》由会员分享,可在线阅读,更多相关《数据结构考研讲义.pdf(97页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、 1 目录 绪论.3 0.1 基本概念.3 第一章 线性表.4 1.1 线性表的定义.4 1.2 线性表的实现.4 1.2.2 线性表的链式存储结构.6 第二章 栈、队列和数组.11 2.1 栈.11 2.2 队列.15 2.3 特殊矩阵的压缩存储.17 2.3.1 数组.17 2.3.2 特殊矩阵.17 第三章 树与二叉树.20 3.1 树的概念.20 1.树的定义.20 2相关术语.20 3.2 二叉树.21 3.2.1 定义与性质.21 3.2.2 二叉树的存储.22 3.2.3 二叉树的遍历.23 3.2.4 线索二叉树.25 3.3 树和森林.29 3.3.1 树的存储结构.29 3
2、.3.2 森林和二叉树的转换.30 1 3.3.3 树和森林的遍历.30 3.4 哈夫曼(Huffman)树和哈夫曼编码.31 第四章 图.32 4.1 图的概念.32 4.2 图的存储及基本操作.33 4.2.1 邻接矩阵.33 4.2.2 邻接表.33 4.3 图的遍历.35 4.3.1 深度优先搜索.35 4.3.2 广度优先搜索.35 4.4 图的基本应用.37 4.4.1 最小生成树.37 4.4.2 最短路径.37 4.4.3 拓扑排序.39 4.4.4 关键路径.40 第五章 查找.42 5.1 查找的基本概念.42 5.2 顺序查找法.43 5.3 折半查找法.44 5.4 动
3、态查找树表.45 5.4.1 二叉排序树.45 5.4.2 平衡二叉树.47 5.4.3 B 树及其基本操作、B+树的基本概念.49 5.5 散列表.51 5.5.2 常用的散列函数.51 1 5.5.3 处理冲突的方法.52 5.5.4 散列表的查找.52 5.5.5 散列表的查找分析.53 第六章 排序.54 6.1 插入排序.54 6.1.1 直接插入排序.54 6.1.2 折半插入排序.54 6.2 冒泡排序.55 6.3 简单选择排序.56 6.4 希尔排序.57 6.5 快速排序.58 6.6 堆排序.60 6.7 二路归并排序.62 6.8 基数排序.63 6.9 各种内部排序算
4、法的比较.64 1 绪论 0.1 基本概念 1、数据结构 数据结构是指互相之间存在着一种或多种关系的数据元素的集合。数据结构是一个二元组 Data_Structure(D,R),其中,D 是数据元素的有限集,R 是 D 上关系的有限集。2、逻辑结构:是指数据之间的相互关系。通常分为四类结构:(1)集合:结构中的数据元素除了同属于一种类型外,别无其它关系。(2)线性结构:结构中的数据元素之间存在一对一的关系。(3)树型结构:结构中的数据元素之间存在一对多的关系。(4)图状结构:结构中的数据元素之间存在多对多的关系。3、存储结构:是指数据结构在计算机中的表示,又称为数据的物理结构。通常由四种基本的
5、存储方法实现:(1)顺序存储方式。数据元素顺序存放,每个存储结点只含一个元素。存储位置反映数据元素间的逻辑关系。存储密度大。但有些操作(如插入、删除)效率较差。(2)链式存储方式。每个存储结点除包含数据元素信息外还包含一组(至少一个)指针。指针反映数据元素间的逻辑关系。这种方式不要求存储空间连续,便于动态操作(如插入、删除等),但存储空间开销大(用于指针),另外不能折半查找等。(3)索引存储方式。除数据元素存储在一组地址连续的内存空间外,还需建立一个索引表,索引表中索引指示存储结点的存储位置(下标)或存储区间端点(下标)。(4)散列存储方式。通过散列函数和解决冲突的方法,将关键字散列在连续的有
6、限的地址空间内,并将散列函数的值解释成关键字所在元素的存储地址。其特点是存取速度快,只能按关键字随机存取,不能顺序存取,也不能折半存取。2 算法和算法的衡量 1、算法是对特定问题求解步骤的一种描述,是指令的有限序列。其中每一条指令表示一个或多个操作。算法具有下列特性:有穷性确定性可行性输入输出。1 算法和程序十分相似,但又有区别。程序不一定具有有穷性,程序中的指令必须是机器可执行的,而算法中的指令则无此限制。算法代表了对问题的解,而程序则是算法在计算机上的特定的实现。一个算法若用程序设计语言来描述,则它就是一个程序。2、算法的时间复杂度:以基本运算的原操作重复执行的次数作为算法的时间度量。一般
7、情况下,算法中基本运算次数 T(n)是问题规模 n(输入量的多少,称之为问题规模)的某个函数 f(n),记作:T(n)(f(n);也可表示 T(n)m(f(n),其中 m 为常量。记号“O”读作“大 O”,它表示随问题规模 n 的增大,算法执行时间 T(n)的增长率和 f(n)的增长率相同。注意:有的情况下,算法中基本操作重复执行的次数还随问题的输入数据集不同而不同。常见的渐进时间复杂度有:(1)(log2n)(n)(nlog2n)(n2)(n3)(2n)O(n!)O(nn)。3、算法的空间复杂度:是对一个算法在运行过程中临时占用的存储空间大小的量度。只需要 分析除输入和程序之外的辅助变量所占
8、额外空间。原地工作:若所需额外空间相对于输入数据量来说是常数,则称此算法为原地工作,空 间复杂度为 O(1)。1 第一章 线性表 1.1 线性表的定义 线性表是一种线性结构,在一个线性表中数据元素的类型是相同的,或者说线性表是由同一类型的数据元素构成的线性结构,定义如下:线性表是具有相同数据类型的 n(n0)个数据元素的有限序列,通常记为:(a1,a2,ai-1,ai,ai+1,an)其中 n 为表长,n0 时称为空表。需要说明的是:ai 为序号为 i 的数据元素(i=1,2,n),通常将它的数据类型抽象为 ElemType,ElemType 根据具体问题而定。1.2 线性表的实现 1.2.1
9、 线性表的顺序存储结构 1.顺序表 线性表的顺序存储是指在内存中用地址连续的一块存储空间顺序存放线性表的各元素,用这种存储形式存储的线性表称其为顺序表。因为内存中的地址空间是线性的,因此,用物理上的相邻实现数据元素之间的逻辑相邻关系是既简单又自然的。设 a的存储地址为 Loc(a),每个数据元素占 d 个存储地址,则第 i 个数据元素的地址为:Loc(ai)=Loc(a)+(i-1)*d 1in 这就是说只要知道顺序表首地址和每个数据元素所占地址单元的个数就可求出第 i 个数据元素的地址来,这也是顺序表具有按数据元素的序号随机存取的特点。线性表的动态分配顺序存储结构:#define LIST_
10、INIT_SIZE 100/存储空间的初始分配量#define LISTINCREMENT 10/存储空间的分配增量 typedef struct ElemType*elem;/线性表的存储空间基址 1 int length;/当前长度 int listsize;/当前已分配的存储空间 SqList;2.顺序表上基本运算的实现(1)顺序表的初始化 顺序表的初始化即构造一个空表,这对表是一个加工型的运算,因此,将 L 设为引用参数,首先动态分配存储空间,然后,将 length 置为 0,表示表中没有数据元素。int Init_SqList(SqList&L)L.elem=(ElemType*)m
11、alloc(LIST_INIT_SIZE*sizeof(ElemType);if(!L.elem)exit(OVERFLOW);/存储分配失败 L.length=0;L.listsize=LIST_INIT_SIZE;/初始存储容量 return OK;(2)插入运算 线性表的插入是指在表的第 i(i 的取值范围:1in+1)个位置上插入一个值为 x 的新元素,插入后使原表长为 n 的表:(a1,a2,.,ai-1,ai,ai+1,.,an)成为表长为 n+1 表:(a1,a2,.,ai-1,x,ai,ai+1,.,an)。顺序表上完成这一运算则通过以下步骤进行:将 aian 顺序向下移动,为
12、新元素让出位置;(注意数据的移动方向:从后往前依次后移一个元素)将 x 置入空出的第 i 个位置;修改表长。1 int Insert_SqList(SqList&L,int i,ElemType x)if(i L.length+1)return ERROR;/插入位置不合法 if(L.length=L.listsize)return OVERFLOW;/当前存储空间已满,不能插入/需注意的是,若是采用动态分配的顺序表,当存储空间已满时也可增加分配 q=&(L.elemi-1);/q 指示插入位置 for(p=&(L.elemL.length-1);p=q;-p)*(p+1)=*p;/插入位置及
13、之后的元素右移*q=e;/插入 e+L.length;/表长增 1 return OK;顺序表上的插入运算,时间主要消耗在了数据的移动上,在第 i 个位置上插入 x,从 ai 到 an 都要向下移动一个位置,共需要移动 ni1 个元素。(3)删除运算 线性表的删除运算是指将表中第 i(i 的取值范围为:1 in)个元素从线性表中去掉,删除后使原表长为 n 的线性表:(a1,a2,.,ai-1,ai,ai+1,.,an)成为表长为 n1 的线性表:(a1,a2,.,ai-1,ai+1,.,an)。顺序表上完成这一运算的步骤如下:将 ai+1an 顺序向上移动;(注意数据的移动方向:从前往后依次前
14、移一个元素)修改表长。int Delete_SqList(SqList&L;int i)if(i L.length)return ERROR;/删除位置不合法 1 p=&(L.elemi-1);/p 为被删除元素的位置 e=*p;/被删除元素的值赋给 e q=L.elem+L.length-1;/表尾元素的位置 for(+p;p data=x;s-next=L;L=s;scanf(%d,&x);return L;1 尾插法在单链表的尾部插入结点建立单链表 头插入建立单链表简单,但读入的数据元素的顺序与生成的链表中元素的顺序是相反的,若希望次序一致,则用尾插入的方法。因为每次是将新结点插入到链表
15、的尾部,所以需加入一个指针 r 用来始终指向链表中的尾结点,以便能够将新结点插入到链表的尾部。初始状态,头指针 L=NULL,尾指针 r=NULL;按线性表中元素的顺序依次读入数据元素,不是结束标志时,申请结点,将新结点插入到 r 所指结点的后面,然后 r 指向新结点(注意第一个结点有所不同)。LinkList CreateListR1()LinkList L=NULL;LNode*s,*r=NULL;int x;/设数据元素的类型为 int scanf(%d,&x);while(x!=flag)s=(LNode*)malloc(sizeof(LNode);s-data=x;if(L=NULL
16、)L=s;/第一个结点的处理 else r-next=s;/其它结点的处理 r=s;/r 指向新的尾结点 scanf(%d,&x);if(r!=NULL)r-next=NULL;/对于非空表,最后结点的指针域放空指针 return L;在算法 CreateListR1 中,第一个结点的处理和其它结点是不同的,原因是第一个结点加入时链表为空,它没有直接前驱结点,它的地址就是整个链表的指针,需要放在链表的头指针变量中;而其它结点有直接前驱结点,其地址放入直接前驱结点的指针域。“第一个结点”的问题在很多操作中都会遇到,如在链表中插入结点时,将结点插在第一个位置和其它位置是不同的,在链表中删除结点时,
17、删除第一个结点和其它结点的处理也是不同的,等等。1 为了方便操作,有时在链表的头部加入一个“头结点”,头结点的类型与数据结点一致,标识链表的头指针变量 L 中存放该结点的地址,这样即使是空表,头指针变量 L 也不为空了。头结点的加入使得“第一个结点”的问题不再存在,也使得“空表”和“非空表”的处理成为一致。头结点的加入完全是为了运算的方便,它的数据域无定义,指针域中存放的是第一个数据结点的地址,空表时为空。尾插法建立带头结点的单链表,将算法 CreateListR1 改写成算法 CreateListR2 形式。LinkList CreateListR2()LinkList L=(LNode*)
18、malloc(sizeof(LNode);L-next=NULL;/空表 LNode*s,*r=L;int x;/设数据元素的类型为 int scanf(%d,&x);while(x!=flag)s=(LNode*)malloc(sizeof(LNode);s-data=x;r-next=s;r=s;/r 指向新的尾结点 scanf(%d,&x);r-next=NULL;return L;因此,头结点的加入会带来以下两个优点:第一个优点:由于开始结点的位置被存放在头结点的指针域中,所以在链表的第一个位置上的操作就和在表的其它位置上的操作一致,无需进行特殊处理;第二个优点:无论链表是否为空,其头
19、指针是指向头结点在的非空指针(空表中头结点的指针域为空),1 因此空表和非空表的处理也就统一了。在以后的算法中不加说明则认为单链表是带头结点的。(2)查找操作 按序号查找 Get_LinkList(L,i)从链表的第一个元素结点起,判断当前结点是否是第 i 个,若是,则返回该结点的指针,否则继续后一个,表结束为止,没有第个结点时返回空。LNode*Get_LinkList(LinkList L,int i);LNode*p=L;int j=0;while(p-next!=NULL&jnext;j+;if(j=i)return p;else return NULL;(3)插入运算 后插结点:设
20、p 指向单链表中某结点,s 指向待插入的值为 x 的新结点,将*s 插入到*p 的后面,插入示意图如图所示。操作如下:s-next=p-next;p-next=s;注意:两个指针的操作顺序不能交换。1(4)删除运算 删除结点 设 p 指向单链表中某结点,删除*p。操作过程如图。要实现对结点*p 的删除,首先要找到*p 的前驱结点*q,然后完成指针的操作即可。操作如下:q=L;while(q-next!=p)q=q-next;/找*p 的直接前驱 q-next=p-next;free(p);因为找*p 前驱的时间复杂度为 O(n),所以该操作的时间复杂度为 O(n)通过上面的基本操作我们得知:(
21、1)单链表上插入、删除一个结点,必须知道其前驱结点。(2)单链表不具有按序号随机访问的特点,只能从头指针开始一个个顺序进行。1.2.2.2 循环链表 对于单链表而言,最后一个结点的指针域是空指针,如果将该链表头指针置入该指针域,则使得链表头尾结点相连,就构成了单循环链表。在单循环链表上的操作基本上与非循环链表相同,只是将原来判断指针是否为 NULL 变为是否是头指针而已,没有其它较大的变化。对于单链表只能从头结点开始遍历整个链表,而对于单循环链表则可以从表中任意结点开始遍历整个链表,不仅如此,有时对链表常做的操作是在表尾、表头进行,此时可以改变一下链表的标识方法,不用头指针而用一个指向尾结点的
22、指针 R 来标识,可以使得操作效率得以提高。1.2.2.3 双向链表 1 单链表的结点中只有一个指向其后继结点的指针域 next,因此若已知某结点的指针为 p,其后继结点的指针则为 p-next,而找其前驱则只能从该链表的头指针开始,顺着各结点的 next 域进行,也就是说找后继的时间性能是 O(1),找前驱的时间性能是 O(n),如果也希望找前驱的时间性能达到 O(1),则只能付出空间的代价:每个结点再加一个指向前驱的指针域,结点的结构为如图所示,用这种结点组成的链表称为双向链表。线性表的双向链表存储结构 C 语言描述下:typedef struct DuLNode ElemType dat
23、a;struct DuLNode*prior,*next;DuLNode,*DuLinkList;和单链表类似,双向链表通常也是用头指针标识,也可以带头结点。(1)双向链表中结点的插入:设 p 指向双向链表中某结点,s 指向待插入的值为 x 的新结点,将*s 插入到*p 的前面,插入示意图如所示。操作如下:s-prior=p-prior;p-prior-next=s;s-next=p;p-prior=s;指针操作的顺序不是唯一的,但也不是任意的,操作必须要放到操作的前面完成,否则*p 的前驱结点的指针就丢掉了。(2)双向链表中结点的删除:设 p 指向双向链表中某结点,删除*p。操作示意图如图所
24、示。1 1.2.2.4 顺序表和链表的比较 总之,两种存储结构各有长短,选择那一种由实际问题中的主要因素决定。通常“较稳定”的线性表选择顺序存储,而频繁做插入删除的即动态性较强的线性表宜选择链式存储。操作如下:p-prior-next=p-next;p-next-prior=p-prior;1 第二章 栈、队列和数组 2.1 栈 2.1.1 栈的定义 栈是限制在表的一端进行插入和删除的线性表。允许插入、删除的这一端称为栈顶,另一个固定端称为栈底。当表中没有元素时称为空栈。2.1.2 栈的存储实现和运算实现 栈是运算受限的线性表,线性表的存储结构对栈也是适用的,只是操作不同而已。利用顺序存储方式
25、实现的栈称为顺序栈。与线性表类似,栈的动态分配顺序存储结构如下:#define STACK_INIT_SIZE 100/存储空间的初始分配量#define STACKINCREMENT 10/存储空间的分配增量 typedef struct SElemType*base;/在栈构造之前和销毁之后,base 的值为 NULL SElemType*top;/栈顶指针 int stacksize;/当前已分配的存储空间 SqStack;需要注意,在栈的动态分配顺序存储结构中,base 始终指向栈底元素,非空栈中的 top 始终在栈顶元素的下一个位置。下面是顺序栈上常用的基本操作的实现。(1)入栈:若
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 考研 讲义

限制150内