前言
从本篇文章开始,正式开启考研专业课之一的数据结构的复习之旅,数学与专业课并驾齐驱,早开始,后期才能游刃有余。另外博客重点分享数据结构需要动手实践的代码部分,对于概念的解释将被一笔带过或者忽略,望周知。
文章目录
顺序表的定义
线性表的顺序存储又称为顺序表。它是一种使用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的元素在物理位置上也是相邻的。第1个元素存储在线性表中的起始位置,第i个元素的存储位置后面紧跟着存储的是第i+1个元素,称i为元素ai在线性表中的位序。
顺序表的基本操作
静态分配
静态分配,顾名思义,数组在运行之前的大小是固定好的,无法更改。
定义存储结构
//定义顺序表的存储结构
typedef struct {
int data[MaxSize];//顺序表的元素
int length;//顺序表的当前长度
}SqlList;//顺序表的类型定义
初始化顺序表
//初始化
void InitList(SqlList &L){
for(int i=0;i<MaxSize;i++){
L.data[i]=0;
}
L.length=0;
}
插入元素
//插入元素
bool ListInsert(SqlList &L,int i,int e){
//判断插入位置i是否合法
if(i<1||i>L.length+1) return false;
//判断当前存储空间是否已满,已满则不能插入
if(L.length>=MaxSize) return false;
//从第i个位置往后依次移动元素,空出第i个位置给待插入的元素
for(int j=L.length;j>=i;j--){
L.data[j]=L.data[j-1];
}
L.data[i-1]=e;//在位置i处插入该元素,下标为i-1
L.length++;//插入后长度加一
return true;
}
删除元素
//删除元素
bool ListDelete(SqlList &L,int i,int &e){
//判断删除位置i是否合法
if(i<1||i>L.length)
return false;
e=L.data[i-1];//将要删除的元素 存入e带回、
//从第i个位置开始后面元素依次向前移动,从而覆盖掉要删除的元素
for(int j=i;j<L.length;j++){
L.data[j-1]=L.data[j];
}
L.length--;
return true;
}
修改元素
//修改元素(按值修改) 按下标修改很简单,只需L.data[i-1]=e 即可
bool ListChange(SqlList &L,int oldNumber,int newNumber){
int flag=0;
for(int j=0;j<L.length;j++){
if(L.data[j]==oldNumber){
L.data[j]=newNumber;
flag=1;
}
}
if(flag!=0)return true;
else return false;
}
查找元素
//查找元素(按值查找) 按照下标查找直接返回L.data[i-1] 查找时间复杂度为O(1) 即随机存取
int ListSearch(SqlList &L,int target){
for(int i=0;i<L.length;i++){
if(L.data[i]==target){
return i+1;
}
}
return 0;
}
测试
#include"stdio.h"
#define MaxSize 20
//定义顺序表的存储结构
typedef struct {
int data[MaxSize];//顺序表的元素
int length;//顺序表的当前长度
}SqlList;//顺序表的类型定义
//初始化
void InitList(SqlList &L){
for(int i=0;i<MaxSize;i++){
L.data[i]=0;
}
L.length=0;
}
//插入元素
bool ListInsert(SqlList &L,int i,int e){
//判断插入位置i是否合法
if(i<1||i>L.length+1) return false;
//判断当前存储空间是否已满,已满则不能插入
if(L.length>=MaxSize) return false;
//从第i个位置往后依次移动元素,空出第i个位置给待插入的元素
for(int j=L.length;j>=i;j--){
L.data[j]=L.data[j-1];
}
L.data[i-1]=e;//在位置i处插入该元素,下标为i-1
L.length++;//插入后长度加一
return true;
}
//删除元素
bool ListDelete(SqlList &L,int i,int &e){
//判断删除位置i是否合法
if(i<1||i>L.length)
return false;
e=L.data[i-1];//将要删除的元素 存入e带回、
//从第i个位置开始后面元素依次向前移动,从而覆盖掉要删除的元素
for(int j=i;j<L.length;j++){
L.data[j-1]=L.data[j];
}
L.length--;
return true;
}
//修改元素(按值修改) 按下标修改很简单,只需L.data[i-1]=e 即可
bool ListChange(SqlList &L,int oldNumber,int newNumber){
int flag=0;
for(int j=0;j<L.length;j++){
if(L.data[j]==oldNumber){
L.data[j]=newNumber;
flag=1;
}
}
if(flag!=0)return true;
else return false;
}
//查找元素(按值查找) 按照下标查找直接返回L.data[i-1] 查找时间复杂度为O(1) 即随机存取
int ListSearch(SqlList &L,int target){
for(int i=0;i<L.length;i++){
if(L.data[i]==target){
return i+1;
}
}
return 0;
}
//打印顺序表
void PrintList(SqlList &L){
for(int i=0;i<L.length;i++){
printf("%d\t",L.data[i]);
}
printf("\n");
}
//测试
int main(){
SqlList p;
InitList(p);//初始化顺序表
//插入元素
for(int i=1;i<=10;i++){
ListInsert(p,i,2*i);
}
printf("1.插入元素后顺序表为:");
PrintList(p);
//删除元素
int location,e;//location表示要删除元素的位序,e存放被删除元素的值
printf("\n请输入你想要删除的元素的位置:");
scanf("%d",&location);
if(ListDelete(p,location,e)){
printf("2.删除成功,删除的元素为:%d\n",e);
printf("删除元素后顺序表为:");
PrintList(p);
}else{
printf("2.删除失败,请检查你输入的位序是否合法!\n");
}
//查找元素
int target;
printf("\n请输入你要查找的元素:");
scanf("%d",&target);
int index=ListSearch(p,target);
if(index!=0){
printf("3.查询成功,该元素的位序为:%d\n",index);
} else{
printf("3.查询失败,顺序表中不存在该元素!\n");
}
//修改元素
int oldNum,newNum;
printf("\n请输入你要修改的元素的值:");
scanf("%d",&oldNum);
printf("请输入其修改后的值:");
scanf("%d",&newNum);
if(ListChange(p,oldNum,newNum)){
printf("4.修改元素后顺序表为:");
PrintList(p);
}else {
printf("修改失败,顺序表中不存在该元素!\n");
}
return 0;
}
正常测试
异常测试
动态分配
动态分配,顾名思义,可以在程序运行时动态地改变数组的大小,使用malloc函数动态地向内存申请额外的内存空间达到扩容的目的,较为灵活,但在复制原来的数组内容时,时间复杂度较高。
定义存储结构
//定义存储结构
typedef struct {
int *data;//指向动态数组的指针
int MaxSize;//顺序表的最大容量
int length; //顺序表的当前长度
}SqlList;
初始化顺序表
//初始化
void InitList(SqlList &L){
//使用malloc函数向内存申请一整片连续的存储空间
L.data=(int *)malloc(InitSize*sizeof(int));
L.length=0;
L.MaxSize=InitSize;
}
数组动态扩容
//动态扩容增大数组长度
void IncreaseArraySize(SqlList &L,int len){
int *old=L.data;//存储原来动态数组的指针
//重新向内存申请更大的空间
L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0;i<L.length;i++){
L.data[i]=old[i];//将原来数组的数据放到新的数组中
}
L.MaxSize=L.MaxSize+len;//顺序表的最大长度扩大len
free(old);//释放原来数组所占用的内存空间
}
内存分配函数malloc()
测试
#include"stdio.h"
#include"stdlib.h"
#define InitSize 10
//定义存储结构
typedef struct {
int *data;//指向动态数组的指针
int MaxSize;//顺序表的最大容量
int length; //顺序表的当前长度
}SqlList;
//初始化
void InitList(SqlList &L){
//使用malloc函数向内存申请一整片连续的存储空间
L.data=(int *)malloc(InitSize*sizeof(int));
L.length=0;
L.MaxSize=InitSize;
}
//插入元素
bool ListInsert(SqlList &L,int i,int e){
//从第i个位置往后依次移动元素,空出第i个位置给待插入的元素
for(int j=L.length;j>=i;j--){
L.data[j]=L.data[j-1];
}
L.data[i-1]=e;//在位置i处插入该元素,下标为i-1
L.length++;//插入后长度加一
return true;
}
//动态扩容增大数组长度
void IncreaseArraySize(SqlList &L,int len){
int *old=L.data;//存储原来动态数组的指针
//重新向内存申请更大的空间
L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0;i<L.length;i++){
L.data[i]=old[i];//将原来数组的数据放到新的数组中
}
L.MaxSize=L.MaxSize+len;//顺序表的最大长度扩大len
free(old);//释放原来数组所占用的内存空间
}
//打印顺序表
void PrintList(SqlList &L){
for(int i=0;i<L.length;i++){
printf("%d\t",L.data[i]);
}
printf("\n");
}
int main(){
SqlList p;
//初始化顺序表
InitList(p);
//插入元素
for(int i=1;i<=10;i++){
ListInsert(p,i,3*i);
}
printf("1.插入元素后顺序表为:");
PrintList(p);
int len;
printf("请输入你要扩大数组的长度:");
scanf("%d",&len);
//数组扩充
IncreaseArraySize(p,len);
printf("继续插入%d个元素\n",len);
for(int j=p.length+1;j<=p.MaxSize;j++){
ListInsert(p,j,4*j);
}
printf("扩充后的顺序表为:");
PrintList(p);
return 0;
}
运行结果
动态分配内存下的顺序表只是数组长度可变,其基本的增删改查操作同静态分配中一致!
故此处不再赘述。
总结
- 顺序表在使用前务必先进行初始化,否则会输出内存中的异常数据造成紊乱,如下图
- C语言的动态分配语句为
L.data=(ElemType *)malloc((InitSize*sizeof(ElemType));
- C++语言的动态分配语句为
L.data=new ElemType[InitSize];
- 注意位序与下标的区别,位序从1开始,下标从0开始
- 在执行插入、删除、修改元素时,要先进行异常处理,即对于位序i进行合法性的判断
- 封装思想,在测试中经常使用到循环打印顺序表中的元素,可以将该操作封装起来复用
//打印顺序表
void PrintList(SqlList &L){
for(int i=0;i<L.length;i++){
printf("%d\t",L.data[i]);
}
printf("\n");
}
END.