数据结构考研复习(详细指导)(持续更新中)

本文全面介绍了数据结构的基础概念,如数据、算法特性和复杂度分析,并详细讲解了顺序表、单链表、双链表、循环链表等典型数据结构的实现、操作和时间复杂度。深入探讨了线性表的插入、删除和查找,以及算法的正确性、可读性和效率。
摘要由CSDN通过智能技术生成

目录

绪论

数据结构

数据结构在学什么

​编辑

数据结构的基本概念

算法 

算法的基本概念

算法的特性

好算法的特性

算法的时间复杂度

算法的空间复杂度

线性表

定义

 基本操作

顺序表

顺序表的定义

顺序表的实现

顺序表的插入和删除

顺序表的查找

单链表

单链表的定义

单链表的实现

单链表的插入

 单链表的删除

单链表的查找

单链表的建立

双链表

双链表的定义

双链表的初始化(带头结点)

判断双链表是否为空

双链表的插入

双链表的删除

销毁双链表

双链表的遍历

循环链表

循环单链表

循环双链表


绪论

数据结构

数据结构在学什么

数据结构的基本概念

数据:数据是信息的载体,是描述客观事物属性的数、字符、及所有能输入到计算机中并能被计算机程序所识别和处理的符号的集合。数据是计算机程序加工的原料

早期计算机处理的数据(只能处理纯数值型问题)

现代计算机可以处理非数值型问题

 对于非数值型的问题:

1、我们关心每个个体的具体信息

2、我们还关心个体之间的关系

数据元素--描述一个个体

数据元素、数据项数据元素是数据的基本单位,通过作为一个整体进行考虑和处理。一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位

数据对象:是具有相同性质的数据元素的集合,是数据的一个子集

数据结构相互之间存在一种或多种特定关系的数据元素的集合

同一个数据对象里的数据元素可以组成不同的数据结构

数据结构三要素:逻辑结构、数据的运算、存储结构(物理结构)

算法 

算法的基本概念

程序=数据结构(如何用数据正确地描述现实世界的问题,并存入计算机)+算法(如何高效地处理这些数据,以解决实际问题)

算法(Algorithm)是特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作

Step1:扫描5个元素,找到年龄最小的一个元素,插入到第一个位置

Step2:扫描剩下的4个元素,找到年龄最小的一个元素,插入到第二个位置

。。。。依次执行结果如下:(该算法不具备确定性,不能称之为算法)

算法的特性

有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。(算法必须是有穷的,而程序可以是无穷的

算法是用有限步骤解决某种特定的问题

微信是程序而不是算法

确定性:算法中每条指令必须有确切的含义,对于相同的输入只能得出相同的输出

可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现(该方案必须能用计算机代码来实现)

输入:一个算法中有零个或多个输入,这些输入取自于某个特定的对象的集合。

输出:一个算法有一个或多个输出,这些输出与输入有着某种特定关系的量

好算法的特性

正确性:算法应能够正确地解决问题

可读性:算法应具有良好的可读性,以帮助人们理解(写注释)

健壮性:输入非法数据时,算法能当适应地做出反应或进行处理,而不会产生莫名其妙的结果

高效率(花费的时间少时间复杂度低)与低存储需求(不费内存,空间复杂度低)

算法的时间复杂度

 一个算法的时间复杂度只考虑最高阶的部分,并且去掉系数

加法规则多项相加只保留最高阶的项并且系数变为1

 乘法规则多项相乘,都保留

时间复杂度排序(当n趋近于无穷时)  常对幂指阶,只考虑阶数,用大O记法表示 注意

顺序执行的代码只会影响常数项,可以忽略
只需挑循环中的一个基本操作分析它的执行次数与n的关系即可

如果有多层嵌套循环,只需要关注最深层循环循环了几次

练习

最坏时间复杂度:最坏情况下算法的时间复杂度

平均时间复杂度:所有输入示例等概率出现的情况下,算法的期望运行时间

最好时间复杂度:最好情况下算法的时间复杂度

一般只考虑最坏和平均情况下

算法的空间复杂度

 这种叫做算法原地工作--算法所需内存空间为常量

 

 

 在调用函数中没有出现数组(长度与传递的参数有关)的定义情况下,空间复杂度=递归调用的深度

 

 

线性表

定义

线性表是指相同的数据类型的n个(n>=0)个数据元素的有限序列,其中n为表长,当n=0时线性表是个空表,若用L命名线性表,则一般表示为L=(a1,a2,.....ai,ai+1,...an)

ai是线性表中的“第i个”元素线性表中的位序

a1是表头元素;an是表尾元素

除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且只有一个直接后继

 基本操作


Tips:

1、对数据的操作--创销、增删改查

2、C语言函数的定义--<返回值类型> 函数名(<参数1类型> 参数1,<参数2类型> 参数2....)

3、实际开发中,可根据实际需求定义其他的基本操作

4、函数名和参数的形式、命名都可以改变,注意命名要注意可读性

5、什么时候要传入引用"&"--对参数的修改结果需要“带回来”

顺序表

顺序表的定义

顺序表--用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系又存储单元的邻接关系来体现。

注意:ElemType是数据类型,也可以是结构体类型 

顺序表的实现

静态分配

#define MaxSize 10  //定义最大长度
typedef struct{
ElemType data[MaxSize]; //用静态的“数组”存放数据元素
int length;  //顺序表的当前长度
}SqList;//顺序表的类型定义(静态分配方式)

动态分配

#define InitSIZE 10//顺序表的初始化长度 
typedef struct{
	ElemType *data;//指示动态分配数组的指针 
	int MaxSize;//顺序表的最大容量 
	int length;//顺序表的当前长度 
}SqlList;//顺序表的类型定义(动态分配) 

malloc()函数--动态分配内存函数,malloc函数返回一个指针,需要强制转型为你定义的数据元素类型指针,malloc函数的参数,指明要分配多大的连续内存空间

L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);

free()释放空间函数 

初始化顺序表

void InitList(SeqList &L){
	//用malloc函数申请一片连续的存储空间
	L.data=(int *)malloc(InitSize*sizeof(int));
	L.length=0;
	L.MaxSize=InitSize;
} 

增加动态数组的长度

//增加动态数组的长度 
void IncreaseSize(SeqList &L, int len){
	int *p=L.data;
	L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
	for (int i=0;i<L.length;i++){
		L.data[i]=p[i];//将数据复制到新区域 ,时间开销较大
	}
	L.MaxSize=L.MaxSize+len;//顺序表最大长度增加 
	free(p)//释放原来的内存空间 
}

realloc函数也可以实现,但建议使用malloc和free更能理解背后过程。

顺序表的特点:

随机访问,即可以在O(1)时间内找到第i个元素

存储密度高,每个节点只存储数据元素

拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度比较高)

插入和删除不方便,需要移动大量元素

顺序表的插入和删除

插入:

bool ListInsert(SqList &L, int i, int e){
if(i<1||i>L.length+1)//判断i是否有效
   return false;
if(L.length>=L.MaxSize)//当前存储空间已满,不能插入
   return false;
 for(int j=L.length;j>=i;j--)//将i个元素往后移
    L.data[j]=L.data[j-1];
L.data[i-1]=e;//插入i
L.length++;//长度加1
return true;
}

删除:

bool ListDelete(SqList &L,int i,int &e){
 if(i<1||i>L.length)
    return false;
 e=L.data[i-1]
for(int j=i;j<L.length;j++)
L.data[j-1]=L.data[j];
L.length--;
return true;
}

顺序表的查找

按位查找

ElemType GetElem(SeqList L,int i){
return L.data[i-1]
}
//由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第个
//元素--"随机存取"的特性

 按值查找        

//在顺序表中L查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SeqList L,ElemType e){
for(int i=0;i<L.length;i++){
if(L.data[i]==e)
  return i+1;
return 0;
}

                             

单链表

单链表的定义

每个节点除了存储数据元素之外,还存储下一个节点的指针

优点:不要求大片连续的空间,改变容量方便

缺点:不可随机存取,要耗费一定空间存放指针

单链表的实现

不 带头节点的单链表实现

 带头节点的单链表实现

 判断单链表是否为空

不带头节点:L==NULL

带头节点:L->next==NULL

单链表的插入

按位序插入

带头节点:(平均时间复杂度为o(n))

//在第i个位置插入元素e(带头节点)
bool ListInsert(LinkList &L,int i, ElemType e){
if(i<1)//位置不合法
return false;
LNode  *p;//声明一个节点指针
int j=0;
while(p!=null&&j<i-1){//循环找到第i-1个节点
p=p->next;
j++;
}
if(p==null)//位置不合法
return false;
//位置合法,为节点分配内存
LNode  *s=(ElemType *)malloc(sizeof(LNode));
s->next=p->next;
s->data=e;
p-next=s;
return true;
}

不带头节点:

bool ListInsert(LinList &L,int i,ElemType e){
if(i<1)
return false;
if(i==1){//如果没有头结点,i==1时为特殊情况
LNode *s=(ElemType *)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s;//头指针指向新的节点
return true;
}
//如果不是特殊情况与上面的带头节点的代码一致,此处省略,只需j=1即可
}

指定节点的后插操作

//后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *P,ElemType e){
if(p==null)
   return false;
 LNode *s=(LNode *)malloc(sizeof(LNode));
if(s==null)//内存分配失败
   return false;
s->data=e;//用结点s保存数据元素e
s->next=p->next;
p->next=s;//将节点s连接到p之后
return true;
}

指定节点的前插操作(时间复杂度O(1))

//前插操作:在p结点之前插入结点s
bool InsertPriorNode (LNode *p,LNode *s){
if(p==null||s==null)
    return false;
s->next=p->next;
p->next=s;//s连到p之后
ElemType temp=p->data;//交换数据域部分
p->data=s->data;
s->data=temp;
return true;
}

 单链表的删除

按位序删除(带头节点、时间复杂度O(n))

bool ListDelete(LinkList &L,int i, ElmeType &e){
if(i<1)
   return false;
LNode *p;//指针p指向当前扫描到的结点
int j=0;//当前p指向的是第几个结点
p=L;//L只想头结点,头结点是第0个节点(不存储数据)
while(p!=null&&j<i-1){
p=p->next;
j++;
}
if(p==null)//i值不合法
   return false;
if(p->next==null)
   return false;
LNode *q=p->next;//令q指向被删除结点
e=q->data;//用e返回元素的值
p->next=q->next;//将*q结点从链中断开
free(q)//释放结点的存储空间
return true;//删除成功
}

指定结点的删除(此方法不能解决删除最后一个结点)

//删除指定节点p
bool DeleteNode(LNode *p){
if(p==null) 
   return false;
LNode *q=p->next;//令q指向*p的后继节点
p->data=p->next->data;//和后继节点交换数据域
p->next=q->next;//将*p节点从链中断开
free(q);//释放后继节点的存储空间
return true;
}

单链表的查找

按位查找

//按位查找,返回第i个元素(带头结点)
LNode * GetElem(LinkList L, int i){
if(i<0)
  return null;
LNode *p;//指针p指向当前扫描到的结点
int j=0;//当前p指向的是第几个结点
p=L;//L指向头结点,头结点是第0个结点(不存数据)
while(p!=null && j<i){//循环找到第i个结点
p=p->next;
j++;
}
return p;
}

按值查找

//按值查找,找到数据域==e的结点
LNode * LocateElem(LinkList L,ElemType e){
LNode *p=L->next;
//从第一个结点开始查找数据域为e的结点
while(p!=null&&p-data!=e){
p=p->next;
}
return p;//找到后返回该结点指针,否则返回null
}

求单链表的长度

//求表的长度
int Length(LinkList L){
int len=0;//统计表长
LNode *p=L;
while(p->next !=null){
p=p->next;
len++;
}
return len;
}

单链表的建立

尾插法建立

LinkList List_Taillnsert(LinkList &L){//正向建立单链表
int x;//设ElemType为整型
L=(LinkList)malloc(sizeof(LNode));//建立头结点
LNode *s,*r=L;//r为表尾指针
scanf("%d",&x);//输入结点的值
while(x!=9999){//输入9999表示结束
  s=(LNode *)malloc(sizeof(LNode));
  s->data=x;
  r->next=s;
  r=s;
   scanf("%d",&x);//r指向新的表尾结点
}
r->next=null;//尾结点指针置空
return L;
}

 头插法建立

重要应用!!!链表的重置

初始化头结点的时候要让头结点指向Null

LinkList List_HeadInsert(LinkList &L){//逆向建立单链表
LNode *s;
int x;
L=(LinkList)malloc(sizeof(LNode));//创建头结点
L->next=null;
scanf("%d",&x);//输入结点的值
while(x!=9999){//输入9999表示结束
s=(LNode *)malloc(sizeof(LNode))//创建新结点  
s-data=x;
  s-next=L->next;
  L->next=s;//将新结点插入表中,L为头指针
scanf("%d",&x);
}
return L;
}

双链表

双链表的定义

typedef struct DNode{
ElemType data;//数据域
struct DNode* prior;//前指针
struct DNode* next;//后指针
}DNode,*DLinkList

双链表的初始化(带头结点)

//初始化双链表
bool InitDlinkList(DLinkList &L){
L=(DNode *)malloc(sizeOf(DNode));//分配头结点
if(L==null){
  return false;//分配内存失败
}
L->prior=NULL;//头结点的前指针永远指向null
L->next=NULL;//头结点的尾指针初始化的时候指向null;
return true;
}

 DLinkList<=>DNode *

判断双链表是否为空

//判断双链表是否为空
bool Empty(DLinkList L){
 if(L->next==null){
  return true;
 }
else
  return false;
}

双链表的插入

后插法

//在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
if(p==null||s==null)
   return false;
s->next=s->next;
if(p->next!=null)//如果p结点有后继结点
   p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}

在双链表中,其他的任何操作由于每个结点都有前驱结点,所以都可以转化成后插法插入结点

注意:修改指针的顺序

双链表的删除

//删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
if(p==null||p->next==null)//p为空后p没有后继结点
   return false;
   DNode *q=p->next//找到p的后继结点q
 p->next=q->next;
   if(q->next!=null){//q不是最后一个结点
   q->next->prior=p;
   }
 free(q);//释放空间
 return true;
}

销毁双链表

其实就是循环这个双链表删除双链表的后继结点直到双链表为空(循环内调用DeleteNextDNode())

void DestoryList(DLinkList &L){
//循环释放各个数据结点
while(L->next!=null)
   DeleteNextDNode(L);
free(L)//释放头结点
L=null;//头指针指向null
}

双链表的遍历

//向后遍历

while(p!=null){

//对p结点做相应处理,如打印

p=p->next;

}

//向前遍历

while(p!=null){

//对结点p做相应处理

p=p->prior;

}

向前遍历跳过头结点

while(p->prior!=null){

//对结点p做相应处理

p=p->prior;

}

双链表不可随机存取、按位查找(在循环里面加个计数器即可)、按值查找操作(循环里面对p指针进行对比即可)都只能用遍历的方式实现。事件复杂度O(n)

循环链表

循环单链表

typedef struct LNode{//定义单链表结点类型
ElemType data;//每个结点存放一个数据元素
struct LNode *next;//指针指向下一个结点
}LNode,*LinkList

初始化一个循环单链表

bool InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));//分配一个头结点
if(L==NULL)
   return false;
L->next=L;//头结点next指向头结点
return true;
}

判断循环单链表是否为空

bool Empty(LinkList L){
 if(L->next==L)
    return true;
 else
   return false;
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
 if(p->next==L)
  return true;
eles
 return false;
}

如果经常对链表的操作都是在头部和尾部,可以使用单链表

循环双链表

顺序表和单链表的比较

存取(读写)方式

顺序表可以顺序存取,也可以随机存取,链表只能从表头顺序存取元素

逻辑结构与物理结构

顺序表和单链表的逻辑结构都是线性表,逻辑上相邻。顺序表的物理结构中各元素也是相邻,单链表中的各元素在物理上不一定相邻。

查找、插入和删除

对于按值操作:当顺序表无序的时候,顺序表和单链表的时间复杂度都是O(n),但是当顺序表有序的时候,顺序表的时间复杂度是O(log2n),原因是当顺序表有序的时候可以采用一些算法实现。

对于按序号查找:顺序表支持随机存取,时间复杂度是O(1),而链表的时间复杂度还是O(n)。

当插入和删除的时候,顺序表需要移动大量元素,代价较大;单链表只需要修改指针即可。

空间分分配

顺序表在静态存储的时候,存储空间不变;动态存储的时候虽然存储空间可变,但是需要移动大量元素,代价较大。

单链表只要在内存空间有空闲的时候,都可以进行存储。

栈和队列

栈相关知识与操作

  • 41
    点赞
  • 347
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
数据结构1800题1. 算法的计算量的大小称为计算的( )。【北京邮电大学2000 二、3 (20/8分)】 A.效率 B. 复杂性 C. 现实性 D. 难度 2. 算法的时间复杂度取决于( )【科院计算所 1998 二、1 (2分)】 A.问题的规模 B. 待处理数据的初态 C. A和B 3.计算机算法指的是(1),它必须具备(2) 这三个特性。 (1) A.计算方法 B. 排序方法 C. 解决问题的步骤序列 D. 调度方法 (2) A.可执行性、可移植性、可扩充性 B. 可执行性、确定性、有穷性 C. 确定性、有穷性、稳定性 D. 易读性、稳定性、安全性 【南京理工大学 1999 一、1(2分) 【武汉交通科技大学 1996 一、1( 4分)】 4.一个算法应该是( )。【山大学 1998 二、1(2分)】 A.程序 B.问题求解步骤的描述 C.要满足五个基本特性 D.A和C. 5. 下面关于算法说法错误的是( )【南京理工大学 2000 一、1(1.5分)】 A.算法最终必须由计算机程序实现 B.为解决某问题的算法同为该问题编写的程序含义是相同的 C. 算法的可行性是指指令不能有二义性 D. 以上几个都是错误的 6. 下面说法错误的是( )【南京理工大学 2000 一、2 (1.5分)】 (1)算法原地工作的含义是指不需要任何额外的辅助空间 (2)在相同的规模n下,复杂度O(n)的算法在时间上总是优于复杂度O(2n)的算法 (3)所谓时间复杂度是指最坏情况下,估算算法执行时间的一个上界 (4)同一个算法,实现语言的级别越高,执行效率就越低 A.(1) B.(1),(2) C.(1),(4) D.(3) 7.从逻辑上可以把数据结构分为( )两大类。【武汉交通科技大学 1996 一 、4(2分)】 A.动态结构、静态结构 B.顺序结构、链式结构 C.线性结构、非线性结构 D.初等结构、构造型结构
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大磊程序员(“hello world”)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值