[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=(n1)/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 数据结构]阶段二复习

NEFU大一下C语言阶段一考试参考代码(带注释)

[NEFU数据结构]阶段一往年卷子以及参考答案

[NEFU 数据结构]期末复习& 新网站

[NEFU 数据结构]期末复习& 新网站

[NEFU锐格 数据结构]实验一 线性表有关的操作