文章目录
线性表的顺序存储结构
顺序表定义
线性表的顺序存储结构, 指的是用一段地址连续的存储单元依次存储线性表的数据元素.
线性表的(a1,a2,…,an)的顺序存储示意图如下:
顺序表的存储方式
线性表的顺序存储结构,就是在内存中找一块地方,通过占位的形式,把一定的空间占用,然后把相同数据类型的数据元素依次存放在这块空地中.既然线性表的每个数据元素的类型都相同,所以可以用C语言(其他语言也相同)的一维数组来实现顺序存储结构,即把第一个数据元素存到数组下标为0的位置中,接着把线性表相邻的元素存储在数组中的相邻位置.
来看线性表的顺序存储的结构代码:
#define SIZE 10 //存储空间初始分配量
typedef struct Sqlist
{
int elem[SIZE]; //数组存储数据元素,最大值为SIZE
int usedsize; //顺序表当前长度
}Sqlist,*PSqlist;
这里,我们发现描述顺序存储结构需要三个属性:
- 存储空间的起始位置 : 数组elem , 它的存储位置就是存储空间的存储位置.
- 线性表的最大存储容量 : 数组长度SIZE.
- 线性表的当前长度 : usedsize.
数据长度与线性表长度的区别
数组的长度 : 是存放线性表的存储空间的长度 , 存储分配后这个量是一般不变的.
线性表的长度 : 是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的.
在任意时刻,线性表的长度 <= 数组的长度
顺序表的基本操作
1.顺序表的初始化
顺序表的初始化,就是初始化一个空的顺序表,只需要把当前长度置为0即可.
void InitSqlist(PSqlist Psq)//初始化
{
assert(Psq != NULL);
if(Psq == NULL)
{
return ;
}
Psq->usedsize = 0;
}
2.在 pos 位置插入 val 值
在顺序表的第pos个位置插入元素,首先将第pos个位置及其之后的元素依次向后移动一个位置,然后将值为val的元素插入到第pos个位置.在元素的移动时,要注意一个小技巧,那就是从最后一个元素开始,从后往前的移动.在插入元素之前还要判断插入的位置是否合法 , 顺序表的是否已满.在插入之后要更新顺序表的长度,即usedsize++.
bool Insert(PSqlist Psq,int pos ,int val)//在 pos 位置插入 val 值
{
assert(Psq != NULL);
if(pos < 0 || pos > Psq->usedsize || isFull(Psq))//pos位置必须合法
{
return false;
}
//挪数据
for(int i = Psq->usedsize-1;i >= pos;i--)
{
Psq->elem[i+1] =Psq->elem[i];
}
Psq->elem[pos] = val;
Psq->usedsize++;
return true;
}
3.从pos位置开始查找 key 值
因为顺序表是依靠数组来实现,所以可以通过访问下标的方式来进行查找 , 如果找到与所要查询key值相等的元素 , 则返回其在表中的位置 , 如果没有找到 , 则返回 -1.
int Search(PSqlist Psq,int pos,int key)
{
assert(Psq != NULL);
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return -1;
}
for(int i = pos; i < Psq->usedsize;i++)
{
if(Psq->elem[i] == key)
{
return i;
}
}
return -1;
}
4.删除pos位置的值
删除表中第pos个位置的值 , 因为顺序表是依靠数组来实现 , 而数组没有删除单个某一个元素的功能 , 所以只需用后面的元素覆盖pos位置的元素即可.值得注意的是,在进行删除操作之前 , 需要判断表是否为空 , 如果为空 , 则返回false;在删除元素之后 , 要将顺序表长度,即suedsize- -;
bool DeletePos(PSqlist Psq,int pos,int *rtv)//删除 pos 位置的值
{
assert(Psq != NULL );
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return false;
}
if(rtv != NULL)
{
*rtv = Psq->elem[pos];
}
for(int i = pos ; i < Psq->usedsize-1;i++)
{
Psq->elem[i] = Psq->elem[i+1];
}
Psq->usedsize--;
return true;
}
5.删除一个 key 值
首先调用 Search() 函数找到元素的位置index , 若未找到则返回 -1 , 若找到 , 则调用 DeletePos() 函数删除 index位置的元素(即 key 值所在位置)
bool Delete(PSqlist Psq,int pos,int key)//删除一个 key 值
{
assert(Psq != NULL);
if(pos < 0 || pos >= Psq->usedsize)
{
return false;
}
int index = Search(Psq,pos,key);
if(index == -1)
{
return false;
}
return DeletePos(Psq,index,NULL);
}
6.得到pos位置的值
这个pos位置 , 指的是数组的下标
bool GetElem(PSqlist Psq,int pos,int *rtv)//得到pos位置的值
{
assert(Psq != NULL );
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return false;
}
*rtv = Psq->elem[pos];
return true;
}
7.得到顺序表的长度
int GetLength(PSqlist Psq)
{
assert(Psq != NULL );
return Psq->usedsize;
}
8.清空顺序表
就是将顺序表的长度suedsize置为 0 .
void Clear(PSqlist Psq)
{
assert(Psq != NULL );
Psq->usedsize = 0;
}
9.打印顺序表
void Show(PSqlist Psq)
{
assert(Psq != NULL );
for(int i = 0;i< Psq->usedsize;i++)
{
printf("%d ",Psq->elem[i]);
}
printf("\n");
}
以下是顺序表的完整代码
sqlist.h
//函数的定义 结构体的定义
#pragma once//预防头文件重复引用
#define SIZE 10
typedef struct Sqlist
{
int elem[SIZE];
int usedsize;
}Sqlist,*PSqlist;
void InitSqlist(PSqlist Psq);//初始化
bool Insert(PSqlist Psq,int pos ,int val);//在 pos 位置插入 val 值
int Search(PSqlist Psq,int pos,int key);//查找 key 值
bool DeletePos(PSqlist Psq,int pos,int *rtv);//删除 pos 位置的值
bool Delete(PSqlist Psq,int pos,int key);//删除一个 key 值
bool GetElem(PSqlist Psq,int pos,int *rtv);
int GetLength(PSqlist Psq);
void Clear(PSqlist Psq);
void Show(PSqlist Psq);
sqlist.cpp
#include<stdio.h>
#include"sqlist.h"
#include<assert.h>
void InitSqlist(PSqlist Psq)//初始化
{
assert(Psq != NULL);
if(Psq == NULL)
{
return ;
}
Psq->usedsize = 0;
}
static bool isFull(PSqlist Psq)
{
return Psq->usedsize == SIZE;
}
bool Insert(PSqlist Psq,int pos ,int val)//在 pos 位置插入 val 值
{
assert(Psq != NULL);
if(pos < 0 || pos > Psq->usedsize || isFull(Psq))//pos位置必须合法
{
return false;
}
//挪数据
for(int i = Psq->usedsize-1;i >= pos;i--)
{
Psq->elem[i+1] =Psq->elem[i];
}
Psq->elem[pos] = val;
Psq->usedsize++;
return true;
}
//从pos位置开始查找 key 值==>第一次出现==>返回下标 -1
static bool isEmpty(PSqlist Psq)
{
return Psq->usedsize == 0;
}
int Search(PSqlist Psq,int pos,int key)
{
assert(Psq != NULL);
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return -1;
}
for(int i = pos; i < Psq->usedsize;i++)
{
if(Psq->elem[i] == key)
{
return i;
}
}
return -1;
}
bool DeletePos(PSqlist Psq,int pos,int *rtv)//删除 pos 位置的值
{
assert(Psq != NULL );
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return false;
}
if(rtv != NULL)
{
*rtv = Psq->elem[pos];
}
for(int i = pos ; i < Psq->usedsize-1;i++)
{
Psq->elem[i] = Psq->elem[i+1];
}
Psq->usedsize--;
return true;
}
bool Delete(PSqlist Psq,int pos,int key)//删除一个 key 值
{
assert(Psq != NULL);
if(pos < 0 || pos >= Psq->usedsize)
{
return false;
}
int index = Search(Psq,pos,key);
if(index == -1)
{
return false;
}
return DeletePos(Psq,index,NULL);
}
bool GetElem(PSqlist Psq,int pos,int *rtv)//得到pos位置的值
{
assert(Psq != NULL );
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return false;
}
*rtv = Psq->elem[pos];
return true;
}
int GetLength(PSqlist Psq)
{
assert(Psq != NULL );
return Psq->usedsize;
}
void Clear(PSqlist Psq)
{
assert(Psq != NULL );
Psq->usedsize = 0;
}
void Show(PSqlist Psq)
{
assert(Psq != NULL );
for(int i = 0;i< Psq->usedsize;i++)
{
printf("%d ",Psq->elem[i]);
}
printf("\n");
}
源.cpp
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include"sqlist.h"
//#include<vld.h> //测内存泄漏 , 没有插件的可以屏蔽
int main()
{
DSqlist ds;
InitList(&ds);
for (int i = 0; i < 70; i++)
{
Insert(&ds,i,i);
}
Show(&ds);
Delete(&ds,0,2);
Delete(&ds,0,9);
Delete(&ds,0,0);
Show(&ds);
Destroy(&ds);
Show(&ds);
return 0;
}
具有动态扩容的顺序表的建立
为什么要引入动态扩容? 原因是在实际问题中 , 我们不知道到底需要开辟多大SIZE的内存 , 开的太大 , 会造成资源浪费 , 开的太小 , 则又不够使用. 所以为了合理利用资源 , 需要引进动态扩容.
1.顺序表的初始化
就是把顺序表初始化为空的顺序表 , 为数组动态开辟INIT_SIZE大小的数组.并将其usersize置为0
来看动态扩容版顺序表的结构代码:
#define INIT_SIZE 10 //最大数组长度
typedef struct DSqlist
{
int *elem;//存储数据的内存
int usedsize;//有效数据个数
int size;//总单元数
}DSqlist,*PDSqlist;
void InitList(PDSqlist psq)
{
assert(psq != NULL);
psq->elem = (int *)malloc(sizeof(int)*INIT_SIZE);
assert(psq->elem != NULL);
psq->size = INIT_SIZE;
psq ->usedsize = 0;
}
这里为什么要引进变量 size , 原因是用 size 来记录当前数组的总单元数 , 如果有效数据个数 == 总单元数 , 那么就说明需要动态扩容 .
2.为顺序表动态扩容
当顺序表被判为满的时候 , 需要对其进行二倍扩容.
static void Inc(PDSqlist psq)
{
assert(psq != NULL);
int *p = (int *)realloc(psq->elem,psq->size*sizeof(int)*2);
if(p != NULL)
{
psq->elem = p;
}
psq->size *=2;
}
在这个代码中存在一个优化 , 就是扩容时使用的是realloc()函数 , 而非malloc()或者calloc()函数.这样写的目的就是能够优化内存.
3.在 pos 位置插入 val == 插入操作
与之前Insert()函数不同的是 , 当顺序表被判为满的时候 , 需要调用动态扩容函数来为顺序表进行扩容.
bool Insert(PDSqlist psq,int pos,int val)
{
assert(psq != NULL);
if(isFull(psq))
{
Inc(psq);
}
for(int i = psq->usedsize-1;i >= pos;i--)
{
psq->elem[i+1] = psq->elem[i];
}
psq->elem[pos] = val;
psq->usedsize++;
return true;
}
4.释放动态开辟的存储空间
因为是在堆上开辟的内存 , 所以需要手动进行资源的释放 , 否则就会遭成资源泄漏.
void Clear(PDSqlist psq)
{
psq->usedsize = 0;
psq->size = 0;
}
void Destroy(PDSqlist psq)
{
free(psq->elem);
psq->elem = NULL;
Clear(psq);
}
以下是动态扩容版的顺序表完整代码
dsqlist.h
#pragma once
#define INIT_SIZE 10
typedef struct DSqlist
{
int *elem;//存储数据的内存
int usedsize;//有效数据个数
int size;//总单元数
}DSqlist,*PDSqlist;
void InitList(PDSqlist psq);
bool Insert(PDSqlist psq,int pos,int val);
int Search(PDSqlist Psq,int pos,int key);//查找 key 值
bool DeletePos(PDSqlist Psq,int pos,int *rtv);//删除 pos 位置的值
bool Delete(PDSqlist Psq,int pos,int key);//删除一个 key 值
void Clear(PDSqlist psq);
void Destroy(PDSqlist psq);
void Show(PDSqlist psq);
dsqlist.cpp
#include"dsqlist.h"
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
void InitList(PDSqlist psq)
{
assert(psq != NULL);
psq->elem = (int *)malloc(sizeof(int)*INIT_SIZE);
assert(psq->elem != NULL);
psq->size = INIT_SIZE;
psq ->usedsize = 0;
}
static bool isFull(PDSqlist psq)
{
if(psq->usedsize == psq->size)
{
return true;
}
return false;
}
static void Inc(PDSqlist psq)
{
assert(psq != NULL);
int *p = (int *)realloc(psq->elem,psq->size*sizeof(int)*2);
if(p != NULL)
{
psq->elem = p;
}
psq->size *=2;
}
bool Insert(PDSqlist psq,int pos,int val)
{
assert(psq != NULL);
if(isFull(psq))
{
Inc(psq);
}
for(int i = psq->usedsize-1;i >= pos;i--)
{
psq->elem[i+1] = psq->elem[i];
}
psq->elem[pos] = val;
psq->usedsize++;
return true;
}
static bool isEmpty(PDSqlist Psq)
{
return Psq->usedsize == 0;
}
int Search(PDSqlist Psq,int pos,int key)//查找 key 值
{
assert(Psq != NULL);
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return -1;
}
for(int i = pos;i < Psq->usedsize;i++)
{
if(Psq->elem[i] == key)
{
return i;
}
}
return -1;
}
bool DeletePos(PDSqlist Psq,int pos,int *rtv)//删除 pos 位置的值
{
assert(Psq != NULL);
if(pos < 0 || pos >= Psq->usedsize || isEmpty(Psq))
{
return false;
}
if(rtv != NULL)
{
*rtv = Psq->elem[pos];
}
for(int i = pos;i < Psq->usedsize-1;i++)
{
Psq->elem[i] = Psq->elem[i+1];
}
Psq->usedsize--;
return true;
}
bool Delete(PDSqlist Psq,int pos,int key)//删除一个 key 值
{
assert(Psq != NULL);
int index = Search(Psq,pos,key);
if(index == -1)
{
return false;
}
return DeletePos(Psq,index,NULL);
}
void Clear(PDSqlist psq)
{
psq->usedsize = 0;
psq->size = 0;
}
void Destroy(PDSqlist psq)
{
free(psq->elem);
psq->elem = NULL;
Clear(psq);
}
void Show(PDSqlist psq)
{
if(psq->usedsize == 0)
{
return;
}
for (int i = 0; i < psq->usedsize; i++)
{
printf("%d ",psq->elem[i]);
}
printf("\n");
}
源.cpp
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include"dsqlist.h"
//#include<vld.h>//测内存泄漏
int main()
{
DSqlist ds;
InitList(&ds);
for (int i = 0; i < 70; i++)
{
Insert(&ds,i,i);
}
Show(&ds);
Delete(&ds,0,2);
Delete(&ds,0,9);
Delete(&ds,0,0);
Show(&ds);
Destroy(&ds);
Show(&ds);
return 0;
}
求两个顺序表的交集
/*
求两个顺序表的交集
**思路:
**新开辟一个顺序表存放交集
**如果遍历两个顺序表,查找相同元素写入交集中,并记录长度
*/
void Intersection(PDSqlist sq1,PDSqlist sq2,PDSqlist sq3)
{
int k = 0; // 计数器
for (int i = 0; i < sq1->usedsize; i++)
{
int j = 0;
while (j < sq2->usedsize && sq2->elem[j] != sq1->elem[i])
{
j++;
}
if (j < sq2->usedsize)
{
Insert(sq3,k++,sq1->elem[i]);
}
}
}
求两个顺序表的并集
/*
求两个顺序表的并集
**思路:
**新开辟一个顺序表存放并集
**由于是并集,所以结果中至少包含了一个顺序表中的所有元素
**因此首先将第一个顺序表写入并集,然后遍历第二个的顺序表
**第二个顺序表中的该元素与第一个顺序表的所有元素均不相同,则写入新表
*/
void Union(PDSqlist sq1,PDSqlist sq2,PDSqlist sq4)
{
for (int i = 0; i < sq1->usedsize; i++)
{
Insert(sq4,i,sq1->elem[i]);
}
int k = sq4->usedsize;
for (int i = 0; i < sq2->usedsize; i++)
{
int j = 0;
while (j < sq1->usedsize && sq1->elem[j] != sq2->elem[i])
{
j++;
}
if (j >= sq1->usedsize)
{
Insert(sq4,k++,sq2->elem[i]);
}
}
}
源.cpp
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include"dsqlist.h"
//#include<vld.h>//测内存泄漏
int main()
{
DSqlist ds1;
DSqlist ds2;
DSqlist ds3;
DSqlist ds4;
InitList(&ds1);
InitList(&ds2);
InitList(&ds3);
InitList(&ds4);
for (int i = 0; i < 10; i++)
{
Insert(&ds1,i,i);
Insert(&ds2,i,i+3);
}
Show(&ds1);
Show(&ds2);
Intersection(&ds1,&ds2,&ds3);
Union(&ds1,&ds2,&ds4);
Show(&ds3);
Show(&ds4);
Destroy(&ds1);
Destroy(&ds2);
Destroy(&ds3);
Destroy(&ds4);
return 0;
}