[NEFU 数据结构]阶段一复习
Posted 鱼竿钓鱼干
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[NEFU 数据结构]阶段一复习相关的知识,希望对你有一定的参考价值。
文章目录
[NEFU 数据结构]阶段一复习
by NEFU-2020-计6-zsl
Email:851892190@qq.com
ID:Fishingrod
结合PPT,教材,习题(教材,学习通)综合的玩意,用于应试,背就完事了。
主要是一些概念和知识点,不包含代码操作(建议把书上代码全部手写一遍hh)
背题(老师发的所有题),背算法模块(填空),刷链表操作
这篇博客只是记录一下个人认为的重点,还是以书本为主
算法模块
所有模块务必背下来,算法应用很长一片的不用背,应该主要考链表而且不会很难
建议那些链式的会画示意图,看清楚头指针尾指针到底指向什么
数据结构 | 算法模块 | 应用 |
---|---|---|
线性表 | 结构定义,初始化,取值,查找,插入,删除 | 有序表合并 |
单链表 | 结构定义,初始化,取值,查找,插入,删除,创建(前插法,后插法) | 有序表合并,单链表逆置,多项式 |
双向链表 | 结构定义,初始化,取值,插入,删除 | |
循环链表 | 只设尾指针的合并 | |
顺序栈 | 结构定义,初始化,入栈,出栈,取栈顶 | 进制转换,括号匹配,表达式求值 |
链栈 | 结构定义,初始化,入栈,出栈,去栈顶 | |
双向栈 | 判满条件 | |
递归 | 汉诺塔,阶乘,单链表递归 | |
循环队列 | 结构定义,初始化,求队列长度,入队,出队,取队头,判空,判满 | 舞伴问题,打印机 |
链队 | 结构定义,初始化,入队,出队,取队头,判空,判满 |
第1章 绪论
1.2 基本概念和术语
数据:客观事物在计算机中的符号表示
数据元素:数据的基本单位,用于完整地描述一个对象
数据项:组成数据元素的特定意义的最小单位
数据对象:具有相同特性的数据元素的集合,是数据的一个子集
数据结构:相互之间存在一种或多种特定关系的数据元素的集合
数据结构三要素:逻辑结构,存储结构,运算
逻辑结构:
与数据存储无关,独立于计算机
与数据元素本身的形式,内容无关
与所含数据元素的个数无关
与数据元素的相对位置无关
逻辑结构两要素:数据元素,关系
逻辑结构有:集合结构(属于同一个集合),线性结构(一对一),树结构(一对多),图结构(多对多)
线性结构:线性表,栈和队列,字符串,数组,广义表
非线性结构:树,二叉树,有向图,无向图,集合结构
存储结构:
存储结构(物理结构):顺序存储结构,链式存储结构,索引存储结构,散列存储结构( 知道前两个即可)
顺序存储结构:
借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系
要求元素依次存放在一片连续的存储空间中
链式存储结构:
无需占用一整块存储空间
为了表示结点之间的关系,需要给每个结点附加指针字段,用于存放后继元素的存储地址
算法的设计取决于选定的逻辑结构,算法的实现依赖于采用的存储结构
1.3抽象数据类型的表示与实现
数据类型:
是对数据取值范围,数据元素之间结构,以及允许施加操作的一种总体描述(范围,结构,操作)
简单类型(原子类型)
结构类型:由简单类型数据按照一定的规则构造而成。如数组等
抽象数据类型(ADT):
由用户定义的,表示应用问题的数学模型以及定义在这个数学模型上的操作的总称。
包含三个部分:数据对象,数据对象上的集合,对数据对象的基本操作的集合
独立于具体实现,取决于逻辑特性,与其在计算机内部如何表示和实现无关,只要数学特性不变就行
1.4 算法和算法分析
算法:对特定问题求解步骤的一种描述,是指令的有限序列。其中每条指令表示一个或多个操作
算法与数据结构:选的数据结构是否恰当直接影响算法的效率;而数据结构的优劣有算法的执行体现。
算法描述:自然语言,高级语言,伪码语言,框图
算法特性:有穷性(有穷步数,有穷时间),确定性(无二义性,同输入同输出),可行性(有限次),输入(>=0),输出(>0)
算法不等于程序:算法供人阅读,程序让机器执行的,算法用计算机语言实现时就是程序,程序有时不需要具有算法的有穷性
算法评价:正确性,可读性强,健壮性(应对非法数据),高效性(时间空间)
算法分析的目的是:看算法是否实际可行,并在同一问题存在多个算法时,可进行时间和空间效率上的比较从中跳出较优的算法
算法效率的衡量方法:
事后统计:利用计算机的时钟,缺陷是必须把算法转换成可执行程序,时空开销依赖于计算机的软硬件等环境因素,容易掩盖算法本身的优劣
事前分析估算:算法的选用策略,问题规模,编写程序的语言,编译程序产生的目标代码质量,机器执行指令的速度
问题规模:描述数据增大的程度的量叫做问题规模
时间复杂度:问题规模n的函数T(N),问题n趋于无穷大时,时间复杂度T(N)的数量级称为算法的渐近时间复杂度记作 O ( f ( n ) ) O(f(n)) O(f(n)),会算就ok啊,这玩意主要看题目, 看清题目说时间复杂度还是时间频次
空间复杂度:
执行时所占用的存储空间(程序的存储空间,变量,堆栈),分为固定部分和可变部分
若所用额外存储空间相对于输入数据量来说是常数就称为原地工作
第2章 线性表
线性结构特点:
存在一个被称作第一个的数据元素
存在一个被称作最后一个的数据元素
除第一个外,集合中每个数据元素均只有一个前驱
除最后一个外,集合中的每个数据元素均只有一个后继
2.3~2.4 线性表的顺序存储结构
顺序表:将线性表的元素一个接一个地存储在相邻的一片区域中
顺序表特点:
以元素在计算机内存中物理位置相邻来表示数据元素之间逻辑关系
只要确定首地址,线性表中任意数据元素都可以随机存取
存储密度=1
顺序表基本操作:
初始化:长度为0
取值:O(1)
查找:ASL(平均查找长度,就是平均比较次数)=(n+1)/2,O(N)
插入:在第i个位置插入一个元素需要移动n-i+1个元素, E i n s = n / 2 E_{ins}=n/2 Eins=n/2,O(N)
删除:删除第i个元素,移动n-i个元素, E d e l = ( n − 1 ) / 2 E_{del}=(n-1)/2 Edel=(n−1)/2,O(N)
关于 E i n s , E d e l E_{ins},E_{del} Eins,Edel要知道期望不是相等是的概率怎么算,最好会求平方和立方和
∑ i = 1 n i 2 = ( k + 1 ) ( k + 2 ) ( 2 k + 3 ) 6 \\sum_{i=1}^ni^2=\\frac{(k+1)(k+2)(2k+3)}{6} ∑i=1ni2=6(k+1)(k+2)(2k+3)
∑ i = 1 n i 3 = n 4 + 2 n 3 + n 2 4 \\sum_{i=1}^ni^3=\\frac {n^4+2n^3+n^2}{4} ∑i=1ni3=4n4+2n3+n2
顺序存储的优点:
逻辑相邻物理相邻
可以随机存取任一元素
存储空间使用紧凑
顺序存储的缺点:
插入删除操作需要移动大量元素
预先分配空间需按最大空间分配,利用不充分
表容量难以扩充
2.5 线性表的链式表示和实现
线性表的链式表示:
用指针表示一组数据元素的逻辑关系
元素的存储可以是连续的也可以不是连续的
结点至少包括数据元素和指针两个部分
那个表示方法也要看一下怎么表示
链表增加头节点的作用:便于首元节点的处理,便于空表和非空表统一处理(如果为空表,头节点指针域为空)
单链表
单链表基本操作:
初始化:创建新节点作为头节点,头节点指针域置空
取值:ASL=(n-1)/2,O(N)
查找:O(N)
插入:不需要移动但是要定位所以还是O(N)
删除:不需要移动但是要定位所以还是O(N)
创建单链表:前插法后插法都O(N)
单链表查找直接后继结点的执行时间为O(1),而查找直接前驱的执行时间为O(N)
单链表是非随机存储的存储结构,取得第i个数据元素必须从头指针出发顺链寻找,也称为顺序存取的存取结构
存储密度小于1
循环链表
循环链表:最后一个节点的指针域指向头节点,形成一个环
循环单链表和单链表操作基本一致,差别在于判别当前指针是否指向表尾节点的条件不同。
单链表:p!=NULL或p->next!=NULL
循环单链表p!=L或p->next!=L
若在循环链表中设立为指针而不设头指针,两个线性表合成一个表时,只需将第一个表的尾指针指向第二个链表的第一个节点,第二个表的尾指针指向第一个表的头节点,然后释放第二个表的头节点,时间复杂度为O(1)
双向链表
双向链表:优化单链表查找直接前驱时间为O(N),双向链表有两个指针域,一个指向直接后继,一个指向直接前驱
双向链表在插入和删除上和单链表不同,插入节点要修改四个指针,删除节点要修改两个节点,两者的时间复杂度均为O(N)
插入和删除算法背下来
顺序表和链表比较
存 储 密 度 = 数 据 元 素 本 身 占 用 的 存 储 量 / 结 点 结 构 占 用 的 存 储 量 存储密度=数据元素本身占用的存储量/结点结构占用的存储量 存储密度=数据元素本身占用的存储量/结点结构占用的存储量
熟记书本P50~51两个表
算法设计
1.两个递增有序表合并程一个递增有序表,不能重复
void MergeList(LinkList &La,Linklist &La,LinkList &Lc){
pa=La->next;
pb=Lb->nextl
Lc=pc=La;
while(pa&&pb){
if(pa->data<pb->data){
pc->next=pa;
pc=pa;
pa=pa->next;
}
else if(pa->data>pb->data){
pc->next=pb;
pc=pb;
pb=pb->next;
}
else{
pc->next=pa;
pc=pa;
pa=pa->next;
q=pb->next;
delete pb;
pb=q;
}
}
pc->next=pa?pa:pb;
delete Lb;
}
2.两个非递减有序链表合并为一个非递增有序链表
void MergeList(LinkList &La,Linklist &La,LinkList &Lc){
pa=La->next;
pb=Lb->nextl
Lc=pc=La;
Lc->next=NULL;
while(pa||pb){
if(!pa){
q=pb;
pb=pb->next;
}
else if(!pb){
q=pa;
pa=pa->next;
}
else if(pa->data<=pb->data){
q=pa;
pa=pa->next;
}
else{
q=pb;
pb=pb->next;
}
q->next=Lc->next;Lc->next=q;
}
delete Lb;
}
3.已知两个链表A和B分别表示两个集合,其元素递增排列,求交集放在A链表中
void Intersection(LinkList &La,Linklist &Lb,Linklist &Lc){
pa=La->next;
pb=Lb->nextl
Lc=pc=La;
while(pa&&pb){
if(pa->data==pb->data){
pc->next=pa;
pc=pa;
pa=pa->next;
u=pb;pb=pb->next;delete u;
}
else if(pa->data<pb->data){
u=pa;pa=pa->next;delete u;
}
else{
u=pb;pb=pb->next;delete u;
}
}
while(pa){
u=pa;pa=pa->next;delete u;
}
while(pb){
u=pb;pb=pb->next;delete u;
}
pc->next=NULL;
delete Lb;
}
4.已知两个链表A,B元素递增排列,求A-B差集,并返回集合元素个数
void Difference(Linklist &La,Linklist &Lb,int &n){
pa=La->next;
pb=Lb->next;
pre=La;
while(pa&&pb){
if(pa->data<pb->data){
n++;
pre=pa;
pa=pa->next;
}
else if(pa->data>pb->data){
pb=pb->next;
}
else{
pre->next=pa->next;
u=pa;pa=pa->next;delete u;
}
}
while(pa){
n++;
pa=pa->next;
}
}
5.将一个带头节点的非零单链表A分解为两个具有相同结构的链表B和C,其中B表示的节点为A表中值小于零的结点,而C表为大于零的节点
void Decompose(LinkList &La,LinkList &Lb,Linklist &Lc){
pa=La->next;
Lb=La;Lb->next=NULL;
Lc = new LNode;Lc->next=NULL;
while(pa!=NULL){
r=pa->next;//因为前插法会改变pa->next,所以事先存一下
if(pa->data<0){
pa->next=Lb->next;
Lb->next=pa;
}
else{
pa->next=Lc->next;
Lc->next=pa;
}
pa=r;
}
}
6.通过一趟遍历确定长度为n的单链表中值最大的级诶按,返回该节点的数据域
ElemType Max(Linklist L){
if(L->next==NULL)return NULL;
pmax=L->next;
p=L->next->next;
while(p!=NULL){
if(p->data>pmax->data)pmax=p;
p=p->next;
}
return pmax->data;
}
7.将链表中所有结点的链接访问方向原地旋转,要求仅利用原表的存储空间
void Inverse(Linklist &L){
p=L->next;
L->next=NULL;
while(p!=NULL){
q=p->next;
p->next=L->next;
L->next=p;
p=q;
}
}
8.删除递增有序链表中值大于mink且小于maxk的所有元素
void DeleteMinMax(Linklist &L,int mink,int maxk){
p=L->next;pre=L;
while(p&&p->data<=mink){
pre=p;
p=p->next;
}
while(p&&p->data<maxk)p=p->next;
q=pre->next;
pre->next=p;
while(q!=p){
s=q->next;
delete q;
q=s;
}
}
自己的
void DeleteMinMax(Linklist &L,int mink,int maxk){
p=L->next;
pre=L;
while(p){
if(p->data>mink&&p->data<maxk){
pre->next=p->next;
u=p;p=p->next;delete u;
}
else p=p->next;
}
}
9.已知p指向双向循环链表的一个结点,其结点结构为data,prior,next三个区域,写出算法Exchange§,交换p所指向的结点及其前驱节点的顺序
void Exchange(DuLinklist p){
q=p->prior;
q->prior->next=p;
p->prior=q->prior;
q->next=p->next;
q->prior=p;
p->next->prior=q;
p->next=q;
}
10.已知长度为n的线性表A采用顺序存储结构,写一个时间复杂度为O(N)空间复杂度为O(1)的算法,该算法可删除线性表内所有值为item的元素
void DeleteItem(SqList &La,ElemType item){
k=0;
for(int i=0;i<La.length;i++){
if(La.Elem[i]!=item){
La.Elem[k]=item;
k++;
}
}
La.length=k;
}
第3章 栈和队列
栈和队列都是限制存取点的线性结构,只能在首或者尾进行插入删除操作
栈
仅在表尾进行插入或删除操作的线性表
先进后出FILO或者后进先出LIFO
1~n合法出栈序列数量为卡特兰数
C
2
n
n
n
+
1
\\frac {C_{2n}^n}{n+1}
n+1C2nn
顺序栈
地址连续
栈顶元素是top-1对应的
链栈
直接背算法模块吧,其他好像没啥考的
双向栈
栈满判断,两个栈的长度之和=栈空间长度的大小(栈顶相遇),不要记结论容易死。做学习通和算法设计第一题看自己能不能写对
递归
递归:若一个函数,过程,数据结构的定义内部直接或间接出现定义本身的应用,就称他为递归的
递归过程的应用:问题的定义是递归的f(n)=n*f(n-1),数据结构是递归的比如“链表,问题的解法是递归的比如汉诺塔
递归工作栈:栈顶为工作记录,包括参数,局部变量,上一层的返回地址
递归时间复杂度的求解
任何递归算法都可以改写成非递归算法
队列
先进先出FIFO
普通队列
约定Q.r指示队尾后一个位置,Q.f指示队头位置,初始Q.f=Q.r=0
空队列条件:Q.f==Q.r
存在问题:Q.f!=0,Q.r=M,再有元素入队发生假溢出
解决方案:每次出队剩余元素向下移动,但这样浪费时间。我们可以使用循环队列来处理假溢出问题,但是要注意循环队列同样存在溢出问题因为空间有限
循环队列
循环队列,把队列想成环形,q[0]接在q[M-1]后面,取模实现
区分空队列和满队列:
对于循环队列,空队列和满队列条件都是Q.rear==Q.front, 没法区分是哪种情况。下面给出三种解决方案
少用一个存储空间
引入一个标志变量标记是否非空和是否非满
使用计数器
求循环队列长度:(Q.rear-Q.front+M)%M
队伍满: (Q.rear+1)%M==Q.front
队伍空:Q.rear=Q.front
链队
一个链队需要两个分别只是队头和队尾的指针才能唯一确定、
出队:链队出队后也需要判空,但是要记住链队出队后需要释放队头元素所占的空间,同时当队列中最后一个元素被删后,队列尾指针也丢了,因此需要对队尾巴指针重新赋值(指向头节点)
队头元素:Q.front->next->data,Q.front不是直接指向队头元素的
算法设计
1.将编号为0和1的两个栈存放于一个数组空间V[m]中,栈底分别处于数组的两端。当第0号栈的栈顶指针top[0]=-1时栈空,当第1号栈的栈顶指针top[1]=m时栈空,两个栈从两边向中间生长。编写双栈初始化,判断栈空,栈满,进栈和出栈等算法的函数
int isEmpty(DblStack S,int i){
return S.top[i]==S.bot[i];
}
int isFull(Dblstack S){
return S.top[0]+1==S.top[1];
}
Status Init(DblStack &S,int m){
S.V = new SElemType[m];
S.top[0]=S.bot[0]=-1;
S.top[1]=S.bot[1]=m;
return OK;
}
Status Push(DblStack &S,SElemType x,int i){
if(S.top[0]+1==S.top[1])return ERROR;
if(i==0)S.V[++S.top[0]]=x;
else S.V[--S.top[1]]=x;
return OK;
}
void Pop(DblStack &S,SElemType &x,int i){
if(S.top[i]==S.bot[i])return ERROR;
if(i==0)x=S.V[S.top[0]--];
else x=S.V[S.top[1]++];
return OK;
}
2.用栈进行字符串回文判断
int IsPalindrome(char *t){
InitStack(S);
int len=strlen(t);
int i=0;
for(i=0;i<len/2;i++)Push(S,t[i]);
if(len%2==1)i++;
while(!EmptyStack(S)){
x=Pop(S);
if(x!=t[i])return 0;
else i++;
}
return 1;
}
3.输入整数序列,用栈存储输入的整数,如果整数不等于-1,就进栈,否则输出栈顶整数并出栈。算法应对异常情况给出信息
void InOutS(int S[]){
top=0;
for(int i=1;i<=n;i++){
cin>>x;
if(x!=-1){
if(top==MAXSIZE-1){
puts("栈满");
exit(0);
}
else S[++top]=x;
}
else{
if(top==0){
puts("栈空");
exit(0);
}
else cout<<"栈顶元素"<<S[top--]<<endl;
}
}
}
4.从键盘上输入一个后缀表达式,是编写算法计算表达式的之。规定逆波兰式长度不超过1行,以$作为输入结束,操作数之间空格分开,操作符只有±*/
void Postfix(){
InitStack(OPND);
num=0.0;
ch=getchar();
while(ch!='$'){
int i=0;
while((ch>='0'&&ch<='9')||ch=='.'){
data[i++]=ch;
ch=getchar();
}
num=atof(data);
Push(OPND,num);
switch(ch){
case:' ':break;
case:'+':Pop(OPND,b);Pop(OPND,a);Push(OPND,a+b);
case:'-':Pop(OPND,b);Pop(OPND,a);Push(OPND,a-b);
case:'*':Pop(OPND,b)[NEFU 数据结构]阶段二复习