-
didi
第一章 绪论
一、基本概念和术语
数据(data)—所有能输入到计算机中去的描述客观事物的符号
数据元素(data element)数据的基本单位,也称结点(node)或记录(record)
数据项(data item)有独立含义的数据最小单位,也称域(field)
数据 > 数据元素 > 数据项
例:学生表 > 个人记录 > 学号、姓名……
数据对象(Data Object):相同特性数据元素的集合,是数据的一个子集
二、数据结构
数据结构(Data Structure)是相互之间存在一种或多种特定关系的数据元素的集合。记为: Data_Structure = {D, S} 其中,D 是数据元素的有限集,S 是 D 上关系的有限集。
数据结构的三个方面的含义:
1.逻辑结构--- 数据元素间抽象化的相互关系,与数据的存储无关,独立于计算机,它是从具体问题抽象出来的数学模型。
(1)线性结构
(2)非线性结构
另一种划分方法:(1)集合结构 (2)线性结构 (3)树形结构 (4)图形结构
2.存储结构(物理结构)---- 数据元素及其关系在计算机存储器中的存储方式。
(1)顺序存储结构——借助元素在存储器中的相对位置来表示数据元素间的逻辑关系
(2)链式存储结构——借助指示元素存储地址的指针表示数据元素间的逻辑关系
3.运算(算法) 检索、排序、插入、删除、修改等
逻辑结构和存储结构都相同, 但运算不同, 则数据结构不同. 例如, 栈与队列
三、数据类型
1.数据类型是一组性质相同的值的集合, 以及定义于这个集合上的一组运算的总称
2.抽象数据类型 (ADT: Abstract Data Types)
- 由用户定义,用以表示应用问题的数据模型
- 由基本的数据类型组成, 并包括一组相关的操作
- 抽象数据类型可以用以下的三元组来表示:ADT = (D,S,P)数据对象、数据对象上关系的集合、数据对象的基本操作的集合
数据结构的表示(存储结构)用类型定义(typedef)描述;
数据元素类型约定为 ElemType,由用户在使用该数据类型时自行定义。
内存的动态分配与释放。
使用new和delete动态分配和释放内存空间:
分配空间 指针变量=new 数据类型;
释放空间 delete指针变量;
输入语句 cin>>变量1>>变量2>>...>>变量n
输出语句 cout<<表达式1<<表达式2<<...<<表达式n
四、算法和算法分析
大O加上()的形式,里面其实包裹的是一个函数f ( ) , O ( f ( ) ) f(),O(f())f(),O(f()),指明某个算法的耗时/耗空间与数据增长量之间的关系。其中的 n nn 代表输入数据的量。
算法运行时间=一个简单操作所需时间×操作次数
算法的时间复杂度是由嵌套最深层语句的频度决定的
类C补充
第二章 线性表
先导 线性结构
1.定义:若结构是非空有限集,则有且仅有一个开始结点和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
2.线性结构表达式:(a1 , a2 , ……, an)
3.线性结构反映结点间的逻辑关系是 一对一 的
线性结构包括线性表(最常用)、堆栈、队列、字符串、数组等等
一、线性表
同一线性表中的元素必定具有相同特性
二、线性表顺序存储结构(顺序表)
线性表的顺序表示又称为顺序存储结构或顺序映像。
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
顺序存储方法:用一组地址连续的存储单元依次存储线性表的元素,可通过数组V[n]来实现。
典型操作的算法实现
1. 初始化线性表L
a.参数用引用
Status InitList_Sq(SqList &L){ //构造一个空的顺序表L
L.elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem) exit(OVERFLOW); //存储分配失败
L.length=0; //空表长度为0
return OK;
}
b.参数用指针
Status InitList_Sq(SqList *L){ //构造一个空的顺序表L
L-> elem=new ElemType[MAXSIZE]; //为顺序表分配空间
if(! L-> elem) exit(OVERFLOW); //存储分配失败
L-> length=0; //空表长度为0
return OK;
}
2. 销毁线性表L
void DestroyList(SqList &L)
{
if (L.elem) delete []L.elem; //释放存储空间
}
3.清空线性表L
void ClearList(SqList &L)
{
L.length=0; //将线性表的长度置为0
}
4. 求线性表L的长度
int GetLength(SqList L)
{ return (L.length);
}
5.判断线性表L是否为空
int IsEmpty(SqList L)
{
if (L.length==0) return 1;
else return 0;
}
6. 获取线性表L中的某个数据元素的内容
//根据指定位置,获取相应位置数据元素的内容
int GetElem(SqList L, int i, ElemType &e)
{
if (i<1||i>L.length) return ERROR;
//判断i值是否合理,若不合理,返回ERROR
e=L.elem[i-1]; //第i-1的单元存储着第i个数据
return OK;
}
7. 在线性表L中查找值为e的数据元素
int LocateELem(SqList L,ElemType e)
{
for (i=0;i< L.length;i++)
if (L.elem[i]==e) return i+1;
return 0;
}
查找算法时间效率分析
插入(插在第 i 个结点之前)
插在第 i 个结点之前,移动 n-i+1 次
实现步骤:
事先应判断: 插入位置i 是否合法?表是否已满? 应当有1≤i≤n+1 或 i=[1,n+1]
将第n至第i 位的元素向后移动一个位置;
将要插入的元素写到第i个位置;
表长加1。
8. 在线性表L中第i个数据元素之前插入数据元素e
Status ListInsert_Sq(SqList &L,int i ,ElemType e){
if(i<1 || i>L.length+1) return ERROR; //i值不合法
if(L.length==MAXSIZE) return ERROR; //当前存储空间已满
for(j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j]; //插入位置及之后的元素后移
L.elem[i-1]=e; //将新元素e放入第i个位置
++L.length; //表长增1
return OK;
}
插入算法时间效率分析
删除(删除第 i 个结点)
删除第 i 个结点,移动 n-i 次
实现步骤:
事先需要判断,删除位置i 是否合法? 应当有1≤i≤n 或 i=[1, n]
将第i+1至第n 位的元素向前移动一个位置;
表长减1。
9. 将线性表L中第i个数据元素删除
Status ListDelete_Sq(SqList &L,int i,ElemType &e){
if((i<1)||(i>L.length)) return ERROR; //i值不合法
e=L.elem[i-1]; //将欲删除的元素保留在e中
for (j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j]; //被删除元素之后的元素前移
--L.length; //表长减1
return OK;
}
删除算法时间效率分析
查找、插入、删除算法的平均时间复杂度为 O(n)
顺序表的空间复杂度S(n)=O(1) (没有占用辅助空间)
1.顺序表(顺序存储结构)的特点
(1)线性表的逻辑结构与存储结构一致
(2)可以粗略地认为,访问每个元素所花时间相等
这种存取元素的方法被称为随机存取法
2.顺序表的优缺点
优点: 存储密度大(结点本身所占存储量/结点结构所占存储量); 可以随机存取表中任一元素
缺点: 在插入、删除某一元素时,需要移动大量元素;浪费存储空间;属于静态存储形式,数据元素的个数不能自由扩充
顺序存储结构的问题:存储空间分配不灵活 ; 运算的空间复杂度高 ;属于静态存储形式,数据元素的个数不能自由扩充。
因此,我们引入链表
以下乱入
图书表的顺序存储结构类型定义
#define MAXSIZE 10000 //图书表可能达到的最大长度
typedef struct //图书信息定义
{
char no[20]; //图书ISBN
char name[50]; //图书名字
float price; //图书价格
}Book;
typedef struct
{
Book *elem; //存储空间的基地址
int length; //图书表中当前图书个数
}SqList; //图书表的顺序存储结构类型为SqList
三、线性表的链式存储结构
1.链式存储结构:结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
线性表的链式表示又称为非顺序映像或链式映像。
各结点由两个域组成
数据域:存储元素数值数据
指针域:存储直接后继结点的存储位置
与链式存储有关的术语
1、结点:数据元素的存储映像。由数据域和指针域两部分组成
2、链表: n 个结点由指针链组成一个链表。它是线性表的链式存储映像,称为线性表的链式存储结构
3、单链表、双链表、循环链表: 结点只有一个指针域的链表,称为单链表或线性链表 ;有两个指针域的链表,称为双链表 ;首尾相接的链表称为循环链表
4、头指针、头结点和首元结点
(I) 首元结点是指链表中存储第一个数据元素a1 的结点。如图 2.8 或图 2.9所示的结点"ZHAO" 。
头节点不能计入链表长度值
6.链表(链式存储结构)的特点
(1)结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
(2)访问时只能通过头指针进入链表,并通过每个结点的指针域向后扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等
这种存取元素的方法被称为顺序存取法
7.链表的优缺点
优点 :数据元素的个数可以自由扩充 ;插入、删除等操作不必移动数据,只需修改链接指针,修改效率较高
缺点 : 存储密度小 ; 存取效率不高,必须采用顺序存取,即存取数据元素时,只能按链表的顺序进行访问(顺藤摸瓜)
一些判断题
三、单链表(线性链表)
单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名
若头指针名是L,则把链表称为表L
注意区分指针变量和结点变量两个不同的概念
指针变量p:表示结点地址
结点变量*p:表示一个结点
Typedef struct LNode {
int data; // 数据域
struct LNode *next; // 指针域
} *LinkList;
将 *LinkList 定 义为 struct LNode 类型,即 LinkList 被定义为一个类型名。
这样就可以用 LinkList 来定义说明新的变量了,如: LinkList L;
即将 L 定义为 struct LNode 类型的指针变量。
1.初始化(构造一个空表 )
Status InitList_L(LinkList &L){
L=new LNode;
L->next=NULL;
return OK;
}
2.销毁单链表
Status DestroyList_L(LinkList &L){
LinkList p;
while(L)
{
p=L;
L=L->next;
delete p;
}
return OK;
}
5.判断线性表L是否为空
查找
(1)按序号查找
(2) 按值查找
6.获取线性表L中的某个数据元素的内容
Status GetElem_L(LinkList L,int i,ElemType &e){
p=L->next;j=1; //初始化
while(p&&j<i){ //向后扫描,直到p指向第i个元素或p为空
p=p->next; ++j;
}
if(!p || j>i)return ERROR; //第i个元素不存在
e=p->data; //取第i个元素
return OK;
}//GetElem_L
7.在线性表L中查找值为e的数据元素
LNode *LocateELem(LinkList L, Elemtype e)
{
p=L->next;
while(p &&p->data!=e)
p=p->next; //寻找满足条件的结点
return p;
//返回L中值为e的数据元素的位置,查找失败返回NULL
}
8.返回线性表L中e的直接前驱元素
Status PriorElem(LinkList L,ElemType cur_e,ElemType *pre_e)
{//若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱, 返回OK;否则操作失败,pre_e无定义,返回INFEASIBLE
LinkList q,p=L->next; // p指向第一个结点
while(p->next) //p所指结点有后继
{ q=p->next;
if(q->data==cur_e)
{ *pre_e=p->data; return OK; }
p=q; /* p向后移 */
}
return INFEASIBLE; }
9.
10.插入(在第i个结点之前)
步骤:
(1)找到ai-1存储位置p
(2)生成一个新结点*s
(3)将新结点*s的数据域置为x
(4)新结点*s的指针域指向结点ai
(5)令结点*p的指针域指向新结点*s
11.删除
(1)找到ai-1存储位置p (2)临时保存结点ai的地址在q中,以备释放 (3)令p->next指向ai的直接后继结点 (4)将ai的值保留在e中 (5)释放ai的空间
12.链表的运算时间效率分析
四、循环链表
第三章 栈和队列
一、栈stack
1.定义:只能在表的一端(栈顶)进行插入和删除运算的线性表
2.运算规则:只能在栈顶运算,且访问结点时依照后进先出(LIFO)或先进后出(FILO)的原则
3.栈与一般线性表的区别:仅在于运算规则不同
top 指示真正的栈顶元素之上的下标地址
base == top 是栈空标志
三、递归
递归算法的效率分析
时间效率:与递归树的结点数成正比 O(2^n)
空间效率:与递归树的深度成正比O(n)
四、队列
队列是一种先进先出(FIFO) 的线性表. 它只允许在表的一端进行插入,而在另一端删除元素
空队标志:front= =rear
入队:base[rear++]=x;
出队:x=base[front++];
真、假溢出
循环队列
第4章 串、数组和广义表
一、串
1.串(String)----零个或多个字符组成的有限序列
2.串的存储结构
(1)顺序存储
(2)链式存储
优点:操作方便
缺点:存储密度较低
串的模式匹配算法
二、数组
本章的数组既可以是顺序的,也可以是链式结构,用户可根据需要选择。
三、广义表
1.广义表与线性表的区别
线性表的成分都是结构上不可分的单元素 ;广义表的成分可以是单元素,也可以是有结构的表
线性表是一种特殊的广义表; 广义表不一定是线性表,也不一定是线性结构
2.广义表的基本运算
(1)求表头GetHead(L):非空广义表的第一个元素,可以是一个单元素,也可以是一个子表 (2)求表尾GetTail(L):非空广义表除去表头元素以外其它元素所构成的表。表尾一定是一个表
3.特点
有次序性 一个直接前驱和一个直接后继
有长度 =表中元素个数
有深度 =表中括号的重数
可递归 自己可以作为自己的子表
可共享 可以为其他广义表所共享
第五章 树和二叉树
一、树
树是n个结点的有限集,除根结点外, 又可分为m个互不相交的有限集,称为子树
(根唯一、子树不相交)
二、二叉树
1.二叉树基本特点: 结点的度小于等于2 ;有序树(子树有序,不能颠倒
2.二叉树的性质
性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2。
3.
三、遍历二叉树
1.遍历:指按某条搜索路线遍访每个结点且不重复(又称周游)
2.
访问结点并入栈遍历左子树,出栈遍历右子树
3.二叉树遍历算法的应用
计算二叉树结点总数
计算二叉树叶子总数
计算二叉树高度
第六章 图
稀疏图:有很少边或弧的图。
稠密图:有较多边或弧的图。
权:图中边或弧所具有的相关数称为权。
网:边/弧带权的图。
邻接:有边/弧相连的两个顶点之间的关系。
存在(vi, vj),则称vi和vj互为邻接点;存在<vi, vj>,则称vi邻接到vj, vj邻接于vi
关联(依附):边/弧与顶点之间的关系。
存在(vi, vj)/ <vi, vj>, 则称该边/弧关联于vi和vj
顶点的度:与该顶点相关联的边的数目,记为TD(v)
在有向图中, 顶点的度等于该顶点的入度与出度之和。
路径:接续的边构成的顶点序列。
路径长度:路径上边或弧的数目/权值之和。
回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
任意一个图的总度数等于其边数的2倍
在强连通图中,必定有一条回路经过所有顶点。
6.2 图的存储结构
1.顺序存储结构:数组表示法(邻接矩阵)
2.链式存储结构:邻接表表示法
邻接矩阵与邻接表表示法的关系
1. 联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。
2. 区别: ① 对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。 ② 邻接矩阵的空间复杂度为O(n^2),而邻接表的空间复杂度为O(n+e)。
3. 用途:邻接矩阵多用于稠密图;而邻接表多用于稀疏图
6.3 图的遍历
1.深度优先搜索DFS
2.广度优先搜索BFS
不是递归的
6.4 图的应用
最小生成树
1.普里姆算法
设连通网络 N = { V, E } 1. 从某顶点 u0 出发,选择与它关联的具有最小权值的边(u0, v),将其顶点加入到生成树的顶点集合U中 2. 每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u, v),把它的顶点加入到U中 3. 直到所有顶点都加入到生成树顶点集合U中为止
2.克鲁斯卡尔算法
设连通网络 N = { V, E } 1. 构造一个只有 n 个顶点,没有边的非连通图 T = { V, 空集 }, 每个顶点自成一个连通分量 2. 在 E 中选最小权值的边,若该边的两个顶点落在不同的连通分量上,则加入 T 中;否则舍去,重新选择 3. 重复下去,直到所有顶点在同一连通分量上为止
练习题
第七章 查找
关键字的平均比较次数,也称平均搜索长度ASL(Average Search Length)
7.2 线性表的查找
一、顺序查找(线性查找)
1.应用范围: 顺序表或线性链表表示的静态查找表; 表内元素之间无序
3.特点:算法简单,对表结构无任何要求(顺序和链式); n很大时查找效率较低
改进措施:非等概率查找时,可按照查找概率进行排序。
二、折半查找(二分或对分查找)
直至low>high时,查找失败
4.折半查找的性能分析-判定树
折半查找过程可以用二叉树来描述
5.查找过程:每次将待查记录所在区间缩小一半,比顺序查找效率高,时间复杂度O(log2 n)
适用条件:采用顺序存储结构的有序表,不宜用于链式结构
7.3 树表的查找
一、二叉排序树
1.二叉排序树或是空树,或是满足如下性质的二叉树:
(1)若其左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若其右子树非空,则右子树上所有结点的值均大于等于根结点的值;
(3)其左右子树本身又各是一棵二叉排序树
2.二叉排序树的操作-查找
若查找的关键字等于根结点,成功
否则 若小于根结点,查其左子树; 若大于根结点,查其右子树
在左右子树上的操作类似
3.二叉排序树的操作-插入
若二叉排序树为空,则插入结点应为根结点
否则,继续在其左、右子树上查找 树中已有,不再插入; 树中没有,查找直至某个叶子结点的左子树或右子树为空为止,则插入结点应为该叶子结点的左孩子或右孩子
插入的元素一定在叶结点上
4.二叉排序树的操作-生成
不同插入次序的序列生成不同形态的二叉排序树
7.3 哈希表的查找
1.术语
哈希方法(杂凑法): 选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放; 查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行比,确定查找是否成功。
哈希函数(杂凑函数):哈希方法中使用的转换函数
哈希表(杂凑表):按上述思想构造的表
冲突:不同的关键码映射到同一个哈希地址 key1≠key2,但H(key1)=H(key2)
同义词:具有相同函数值的两个关键字
冲突是不可避免的
如何减少冲突?
- 构造好的哈希函数;制定一个好的解决冲突方案
2.哈希函数的构造方法
a.直接定址法
b.除留余数法
3.构造哈希函数要考虑的因素
① 执行速度(即计算哈希函数所需时间) ② 关键字的长度 ③ 哈希表的大小 ④ 关键字的分布情况 ⑤ 查找频率
4.处理冲突的方法
2.链地址法(拉链法)
例题
要求
第八章 排序
1.直接插入排序
2. 折半插入排序
冒泡排序
1、冒泡排序分为n-1个阶段。
在第1个阶段,通过“冒泡”,将前n个元素的最大值移动到序列的最后一位。此时只剩前n-1个元素未排序。
2、在第i个阶段,此时序列前n-i+1个元素未排序。通过“冒泡”,我们将前n-i+1个元素中的最大值移动到最后一位。此时只剩前n-i个元素未排好序。
3、最终到第n-1个阶段,前2个元素未排序。将其中的较大值移动到后一位,则整个序列排序完毕。
4、在上面的算法过程中,我们提到“冒泡”将序列中的最大值移动到最后一位。