考研408复习笔记—— 数据结构(二)

编写不易,希望各位看到能点个赞。

若发布的内容有什么错误,欢迎留言探讨。

前篇链接

考研408复习笔记—— 数据结构(一)

二、线性表(一)

2.1 定义

1、线性表是具有相同数据类型的n(n>=0)数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表

基本逻辑结构如下图:
在这里插入图片描述

若用L命名线性表,则其一般表示为
在这里插入图片描述
【注】
1)由于线性表中的元素具有相同的数据类型,所以每个数据元素所占用的空间是一样大的,可以方便计算机快速找到元素的位置。

2)线性表是序列,所有元素之间是有次序的。同时,线性表是有限的,像所有整数按次序排列,就不可以说成是线性表。

3)几个概念:
ai是线性表中的“第i个”元素中位序。位序是从1开始的,数组是从0开始的。
a1表头元素,an表尾元素
除了第一个元素外,每个元素都有且仅有一个直接前驱
除了最后一个元素外,每个元素有且仅有一个直接后继

2.2 基本操作

1、初始化与销毁:
InitList(&L):初始化线性表,构造一个空的线性表L,分配内存空间
DestoryList(&L):销毁线性表,并释放线性表L所占用的内存空间

2、插入删除:
ListInsert(&L,i,e):插入操作,在表L中的第i个位置上插入元素e
ListDelete(&L,i,&e):删除操作,删除表L中第i个位置上的元素,并使用e返回删除元素的值。

3、查找:
LocateElem(L,e):按值查找操作,在表L中查找具有给定值e的元素
GetElem(L,i):按位查找操作,获取表L中第i个位置的元素的

4、其他常用操作:
Length(L):求表长。返回线性表L的长度,即L中元素的个数。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若L为空表,则返回true,否则返回false。

Tips:
1)对数据的基本操作——创建、销毁、增删改查
2)函数的定义——<返回值类型> 函数名(<参数类型1> 参数1,<参数类型2> 参数2,……)
3)实际开发时,可以根据实际重新定义基础操作。
4)上述例子中的L,i,e的变量以及函数的名称参考自,严蔚敏版《数据结构》(大部分学校的指定书目)。

2.3 顺序表

2.3.1 定义

顺序表:用顺序存储的方式实现线性表。
顺序存储:把逻辑上相邻的元素存储在物理上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
如图所示:
在这里插入图片描述
该表的在计算机中按顺序存储,就应在内存中相邻。若第一个元素的存放位置为L,则第二个元素存放位置便为L+数据元素的大小1,第三个为L+数据元素的大小2,以此类推,如下图:
在这里插入图片描述
Tips:在C语言中使用sizeof(数据元素类型)可以查看一个数据元素的大小。

       sizeof(int) = 4B;

2.3.2 顺序表的特点

1)随机访问:即可在O(1)时间内找到第i个元素。
2)存储密度高:每个节点只存储数据元素。
3)扩展容量不方便:即便采用动态分配的方式来实现,拓展长度的时间复杂度也较高。
4)插入、删除操作不方便:需要移动大量的元素。

2.3.3 静态分配

静态分配一般使用数组来存放数据元素,存放元素的多少是事先固定的,在程序运行的过程中不可更改。伪代码如下:

#define MaxSize 10    //定义最大长度

typedef struct{
		ElemType data[MaxSize];    //用静态的“数组”存放数据元素
		int Length;	//顺序表当前长度
}SqList;		//顺序表的类型定义

在实际代码中,我们需要给ElemType一个确切的数据类型,这样就可以直接在主函数中如int等数据类型一般直接使用SqList来声明变量,并进行操作。

在实际使用中的代码:

#include <iostream>

using namespace std;
#define MaxSize 10    //定义最大长度

typedef struct{
		int data[MaxSize];    //用静态的“数组”存放数据元素
		int Length;	//顺序表当前长度
}SqList;		//顺序表的类型定义

void InitList(SqList &L){
		for(int i=0; i<MaxSize;i++){
				L.data[i]=0;  //将所有的数据元素设置为默认初始值0
		}
		L.length=0;  //顺序表初始长度为0
}

int main(){
		SqList L;    //创建顺序表实例
		InitList(L);  //初始化顺序表
		return 0;
}

Tips:
如果不给顺序表进行初始化,那么由于在申请顺序表的空间时,所用地址并不全是分配的空地址,因此,顺序表申请到的地址中可能会存在遗留的“脏数据”,这便需要我们进行一个初始化将内容初始为0。

2.3.4 动态分配

静态分配一般使用指针来关联数据元素,存放元素的多少可以依靠实际使用的数量来决定的`。伪代码如下:

#define InitSize 10    //顺序表的初始长度

typedef struct{
		ElemType *data;    //指针数组实现动态分配
		int MaxSize;  	//顺序表的最大容量
		int Length;	//顺序表当前长度
}SeqList;		//顺序表的类型定义(动态分配)

C中存在malloc函数用于动态申请内存空间,free函数用于释放内存空间。

free函数在考研中经常会使用到,建议多了解学习。

C++中则使用指针搭配new来动态申请空间,释放空间则使用delete

实际应用如下:

#incude<stdlib.h>			//调用malloc和free的库
#define InitSize 10		//默认的最大长度

typedef struct{
	int *data;		//指针数组实现动态分配
	int MaxSize;	//顺序表的最大容量
	int length;	//顺序表的当前长度
}SqeList;

void InitSize(SqeList &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;	//顺序表最大容量增加len
	free(p);	//释放原有的内存空间
}

int main(){
	SeqList L;	//声明一个顺序表
	InitList(L);	//初始化顺序表
	/*
	此处需加入插入元素的操作。
	*/
	IncreaseSize(L,5);	//将顺序表扩展5个位置
	return 0;
}

相对来说,动态分配再顺序表长度上更加灵活,但是再扩展过程中需要复制数组的内容,因此耗费的时间将会增加。

2.3.5 顺序表的插入

ListInsert(&L,i,e):插入操作,在表L中的第i个位置上插入元素e

因为顺序表需要用存储位置的相邻来体现数据元素之间的逻辑关系,因此在第i个位置插入元素的时候,我们需要把i位之后的所有元素向后面的地址移动。同时,如果是动态数组,我们可能还要考虑一下先扩展表的空间再插入。

代码如下:

#define MaxSize 10

typedef struct{
	int data[MaxSize];
	int length;
}SqList;

//该代码为静态分配的顺序表中数据未满时插入数据的操作。
void ListInsert(SqList &L,int i,int e){
	for(int j =L.length;j>=i;j--){	//第i个元素之后的元素后移一位
		L.data[j]=L.data[j-1];
	}
	L.data[i-1]=e;		//在位置i放入e
	L.length++;		//长度+1
}

int main(){
	SqList L;
	InitList(L);
	/*
	省略向表中传入数据的操作
	*/
	ListInsert(L,3,3); 	//在第三个元素插入3
	return 0;
}

Tips:
在应用实际代码时,需要考虑i是否在表的范围内,同时要考虑数据表是否已将存满,并要给插入失败的操作一个提示。

时间复杂度:
当i位处于表尾,则最快时间复杂度为O(1)。
当i位处于表头时,最坏时间复杂度位O(n)。
相对平均时间:假设插入任何一个元素的该路相同,即i=1,2,3,4,……length+1,的概率都是p=1/(n+1)。
平均循环次数=(n-1)p+(n-2)p+……+p=n/2
因此,插入操作的平均时间复杂度为n/2,所以平均时间复杂度为O(n)。

2.3.6 顺序表的删除

ListDelete(&L,i,&e):删除操作,删除表L中第i个位置上的元素,并使用e返回删除元素的值。

与插入步骤相似,删除步骤需要我们先定位到i位置,然后删除该位置上的值,再将i位之后的元素挨个前移,并将表长length-1,从而彻底实现删除操作。

代码如下:

bool ListDelete(SqList &L,int i,int &e){
	if(i<1||i>L.length){	//判断i位置是否有效
		return false;	//i位置不合法则返回false
	}
	e=L.data[i-1];	//将被删除的元素赋值给e
	for(int j=i;j<L.length;j++){   //将第i个位置之后的数据前移
		L.data[j-1]=L.data[j];
	}
	L.length-1;	//线性表长度-1
	return true;
}

int main(){
	SqList L;
	InitList(L);
	int e=-1;	//声明一个变量e用于保存被删除的数据元素
	/*
		省略插入数据元素的步骤
	*/
	if(ListDelete(L,3,e)){
		//输出元素e
	} else{
		//返回提示i位置不合法。
	}
	return 0;
}

该处的删除函数使用了bool类型来进行返回,意在以此来查看删除操作是否成功执行。

时间复杂度:
当i位处于表尾,则最快时间复杂度为O(1)。
当i位处于表头时,最坏时间复杂度位O(n)。
相对平均时间:假设删除任何一个元素的概率相同,即i=1,2,3,4,……length,的概率都是p=1/n。
平均循环次数=(n-1)p+(n-2)p+……+p=(n-1)/2
因此,删除操作的平均复杂度为(n-1)/2,所以平均时间复杂度为O(n)。

2.3.7 顺序表的查找

1、按位查找
GetElem(L,i):按位查找操作,获取表L中第i个位置的元素的

相对于按值查找,按位查找的操作相对简单,如数组一般只需通过数据的位置i便可以直接取到相应的数据。代码如下:

ElemType GetElem(SqList L,int i){	//具体函数类型根据数据元素的类型进行更改
	return L.data[i-1];	//返回相应位置对应的数据值
}

该代码只是简单的进行了数据的调取,具体的内容需根据实际数据使用情况进行更改。但是无论是静态分配的顺序表还是动态分配的,都可以使用这种按位查找的方法直接调取对应位置的数据。

同样该查找的步骤仅有一步,所以时间复杂度为O(1)。

2、按值查找
LocateElem(L,e):按值查找操作,在表L中查找具有给定值e的元素

相对于按位查找,按值查找就相对有些复杂,我们需要从头遍历所有的数据元素,从中找到与所查数据元素相同的数据,并返回它的位置。代码如下:

//在顺序表中查找第一个元素值为e的元素位置
int LocateElem(SqList L,ElemType e){
	for(int i=0;i<L.lengrh;i++){	//遍历所有元素
		if(L.data[i]==e){	//如果该位数据与e相同
			return i+1;	//返回该位置的信息,数组是从0开始因此返回数据+1
		}
	}
	return 0;	//查找失败,退出循环
}

该查找同样在两种分配方式中都可以直接使用,只需根据具体情况对其中内容进行一定的更改。

相对于按位查找,按值查找所需的时间就比较长了,平局时间复杂度的计算就如插入删除一样,通过概率进行计算之后,平均时间为(n+1)/2,平均时间复杂度为O(n)。

此处的查找只是顺序表的基础查找,

后篇跳转

考研408复习笔记—— 数据结构(三)

  • 19
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值