【数据结构】顺序表(sequential list)

目录

一、顺序表的介绍

二、顺序表的实现

[1. 静态顺序表](#1. 静态顺序表)

(1)初始化顺序表

(2)建立顺序表

(3)判空操作

(4)求顺序表的长度

(5)遍历操作

(6)增---插入操作

(7)删---按位删除&按值删除

(8)查---按位查找&按值查找

(9)销毁操作

[2. 动态顺序表](#2. 动态顺序表)

(1)扩容操作

(2)初始化顺序表

(3)建立顺序表

(4)增---插入操作

(5)销毁操作

三、总代码

[1. 静态顺序表](#1. 静态顺序表)

[2. 动态顺序表](#2. 动态顺序表)

三、总结


一、顺序表的介绍

线性表的顺序存储结构称为顺序表 (sequential list)。顺序表是用一段地址连续的存储单元依次存储线性表的数据元素的线性结构,一般情况下采用一维数组存储。在数组上完成数据的增删查改。

而顺序表又分为静态顺序表动态顺序表

二、顺序表的实现

1. 静态顺序表

静态顺序表是使用固定长度的数组空间来储存数据元素,它的长度大小是不能改变的。

下面是静态顺序表的储存结构定义

复制代码
#define MaxSize 100            //假设顺序表最多存储100个元素
typedef int DataType;          //定义顺序表的数据类型,假设为int
typedef struct
{
	DataType data[MaxSize];    //存放数据元素的数组
	int length;                //线性表的长度
}SeqList;

图示:

其中length和MaxSize的含义是不同的。

(1)初始化顺序表

初始化顺序表,只需将顺序表的长度length初始化为0。

复制代码
void InitList(SeqList* L)
{
	L->length = 0;
}

(2)建立顺序表

假设要建立一个长度为n的顺序表,那么就需要将给定数组中的数据元素传入到顺序表中,并将传入的元素个数作为顺序表的长度。假设这里有一个数组a[10] = { 1,2,3,4,5,6,7,8,9,10 },那么就需要判断建立顺序表操作是否成功,如果顺序表的储存空间小于给定的元素个数,则就无法建立。

建立的操作示意图:

则C语言实现如下:

复制代码
void CreatList(SeqList* L, DataType a[], int n)
{
	if (n > MaxSize)	//判断顺序表的存储空间是否足够
	{
		printf("顺序表的空间不够,无法建立顺序表!\n");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		L->data[i] = a[i]; 
	}
	L->length = n;
}

(3)判空操作

顺序表的判空参数只需判断长度length是否为零,如果是空则返回1,如果不是空就返回0。C语言实现如下:

复制代码
int IsEmpty(SeqList* L)
{
	if (L->length == 0)
	{
		return 1; //为空,则返回1
	}
	else
	{
		return 0; //不为空,则返回0
	}
}

(4)求顺序表的长度

由于顺序表储存结构定义中的结构体成员length已经保存了顺序表的长度,所以求顺序表长度时只需要返回成员length的值就可以了。C语言实现如下:

复制代码
int Length(SeqList* L)
{
	return L->length;
}

(5)遍历操作

遍历操作就是依次输出顺序表中的各个元素。C语言实现如下:

复制代码
void PrintList(SeqList* L)
{
	for (int i = 0; i < L->length; i++)
	{
		printf("%d ", L->data[i]);
	}
}

(6)增---插入操作

插入操作就是在表的第 i 个位置插入一个新元素 x 的操作。根据不同的插入位置,可以将插入操作分为头插,中间插,尾插三种情况。由于在顺序表中,各个元素的位置都是确定了,他们无法移动。但是当插入一个元素之后,顺序表中的有些元素位置会发生变化。同时他们所对应的下标也会发生变化。就会发生这种情况:当在第 i 个位置插入一个元素后,在 i 这个位置的后面的所有元素的下标都会出现加1的对应情况。换一种说法就是发生了元素的向后移动

移动的操作示意图:

同时还需要判断插入位置的合理性。则C语言实现如下:

复制代码
void Insert(SeqList* L, int i, DataType x)
{
	if (L->length >= MaxSize)
	{
		printf("表满了,插入失败!\n");
		return 0;
	}
	if (i<1 || i>L->length)
	{
		printf("插入位置不合理,插入失败!\n");
		return 0;
	}
	for (int j = L->length; j >= i; j--)
	{
		L->data[j] = L->data[j - 1];//后移操作
	}
	L->data[i - 1] = x;//插入值
	L->length++;//长度加一
}

(7)删---按位删除&按值删除

按位删除即将顺序表中的 i 个位置的元素删除。子长度为n的顺序表变成长度为n-1的顺序表。

删除的操作示意图:

当然,这也需要判断删除位置的合理性,则C语言实现如下:

复制代码
void DeleteByLocate(SeqList* L, int i)
{
	if (L->length == 0)
	{
		printf("表空,删除失败!\n");
		return;
	}
	if (i < 1 || i >= L->length)
	{
		printf("删除位置不合理,删除失败!\n");
		return;
	}
	for (int j = i; j < L->length; j++)
	{
		L->data[j - 1] = L->data[j];//前移操作
	}
	L->length--;//长度减一
}

按值删除需要对顺序表中的元素值进行比较。如果遇到相同的元素时就将该元素删除,即将该元素后面的所有元素都向前移一位。C语言实现如下:

复制代码
void DeleteByElem(SeqList* L, DataType x)
{
	int sign = 0;
	for (int i = 0; i < L->length; i++)
	{
		if (L->data[i] == x)
		{
			sign = i;//说明表中有该元素,修改sign,并存记录位置
			break;
		}
	}
	if (sign != 0)
	{
		for (int j = sign + 1; j < L->length; j++)
		{
			L->data[j - 1] = L->data[j];//前移操作
		}
		L->length--;//长度减一
	}
	else
	{
		printf("表中没有元素,删除失败!\n");
	}
}

(8)查---按位查找&按值查找

按位查找, 就是当查找第 i 个元素时,返回**i - 1 这个下标所对应的元素值,**这里需要注意,不能直接返回,因为有时会造成歧义,所以需要借助一个指针将对应的元素值存到指针所指向的地址中去。由此来获得该元素值。这就需要查找位置的合理性。如果查找失败,则返回查找失败的标志零,查找成功则返回1。C语言实现如下:

复制代码
int GetElem(SeqList* L, int i, DataType* p)
{
	if (i<1 || i>L->length)
	{
		printf("查找位置不合理,查找失败!\n");
		return 0;
	}
	else
	{
		*p = L->data[i - 1];
		return 1;
	}
}

按值查找 需要对顺序表中的元素值 进行依次比较。如果查找成功返回元素的序号(注意这不是下标),否则就返回查找失败的标志0。C语言实现如下:

复制代码
int GetLocate(SeqList* L, DataType x)
{
	for (int i = 0; i < L->length; i++)
	{
		if (L->data[i] == x)
		{
			return i + 1;//返回序号
		}
	}
	return 0;   //退出循环,说明查找失败
}

(9)销毁操作

静态顺序表是静态储存分配,在顺序表变量退出作用域时自动释放该变量所在内存单元,因此静态顺序表无需销毁。


2. 动态顺序表

动态顺序表是通过使用动态开辟的数组存储数据来实现的,它的长度大小是能够改变的。

下面是动态顺序表的储存结构定义

复制代码
typedef int DataType;       //定义数据类型
typedef struct
{
	DataType* data;         //指向动态开辟的数组
	int length;             //有效数据个数
	int capicity;           //容量空间的大小
}SeqList;

图示:

与静态顺序表相比,动态顺序表就更加的灵活。静态顺序表只适用于确定知道需要存储多少数据的场景。这会导致静态顺序表的空间开多了浪费,开少了又不够用。但动态顺序表则会根据需要的场景来动态的分配空间大小。

其中静态顺序表和动态顺序表他们的代码实现大部分是相同的。即判空操作,求长度操作,遍历操作,删除操作,查找操作这几种操作和静态的顺序表的操作都是一模一样的

  • 其中不同的是动态顺序表在初始化时需要动态开辟空间;
  • 在进行建立和插入的操作时,需要判断当下空间大小是否足够。如果不够就需要增容;
  • 再就是动态顺序表的销毁操作,由于动态顺序表示动态申请的空间,所以在销毁时就需要全部释放。
  • 动态顺序表必须要有销毁操作。

下面介绍的便是与静态顺序表不同的操作。

(1)扩容操作

简单的扩容操作就是将容量扩大为原来的两倍即可。首先是判断当有效数据个数等于容量空间大小时就需要扩容。扩容需要将容量开辟为原来的两倍,而这里这里使用relloc来开辟空间更加方便。C语言实现如下:

复制代码
void IncreaseCapacity(SeqList* L)
{
	if (L->length == L->capicity)
	{
		DataType* tmp = (DataType*)realloc(L->data, sizeof(DataType) * L->capicity * 2);
		if (tmp == NULL)
		{
			perror("relloc fail");
			return;
		}	
		L->data = tmp;
		L->capicity *= 2;
	}
}

(2)初始化顺序表

由于需要动态开辟空间,所以可以在开辟空间之前断言传递的指针L非空,再用malloc开辟空间。然后需要将有效数据个数字置零,并设置初始容量空间大小。C语言实现如下:

复制代码
void InitList(SeqList* L)
{
	assert(L);//断言L非空指针
	L->data = (DataType*)malloc(sizeof(DataType) * InitCapicity);//开辟初始空间
	if (L->data == NULL)
	{
		perror("malloc fail");
		return;
	}
	L->length = 0;//有效数据个数置零
	L->capicity = InitCapicity;//设置初始容量
}

(3)建立顺序表

建立动态存储的顺序表示的逻辑和建立静态顺序表时的逻辑基本上是一样的。其中不同的是当我们在将一个数组中的数据赋值到动态顺序表中时,也许会遇到所需要赋值的数据个数大于此时的容量的情况,这时候就需要扩容。因此只需要在建立顺序表的函数的前一个语句运行扩容操作的函数就行了。C语言实现如下:

复制代码
void CreatList(SeqList* L, DataType a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		IncreaseCapacity(L);//扩容操作
		L->data[i] = a[i];
		L->length++;//每加一个数据,有效数据个数就加一
	}
}

(4)增---插入操作

插入操作即在第 i 个位置插入值 x 的数据 ,大部分和静态顺序表的操作是一样的,不同的是动态顺序表没有表满就不能插入的插入失败情况了,并且需要检查空间,如果容量空间被占满了,就需要扩大容量。C语言实现如下:

复制代码
void Insert(SeqList* L, int i, DataType x)
{
	if (i < 1 || i > L->length)
	{
		printf("插入位置不合理,插入失败!\n");
		return;
	}
	IncreaseCapacity(L);
	for (int j = L->length; j >= i; j--)
	{
		L->data[j] = L->data[j - 1];//后移操作
	}
	L->data[i - 1] = x;//插入值
	L->length++;//有效个数加一
}

当然,由此可以引申出头插尾插的操作了。

(5)销毁操作

销毁操作需要将动态申请的所有空间都释放。并且其余变量都置为零。C语言实现如下:

复制代码
void Destory(SeqList* L)
{
	free(L->data);
	L->data = NULL;
	L->length = 0;
	L->capicity = 0;
}

三、总代码

1. 静态顺序表

头文件:SeqList.h

复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#define MaxSize 100            //假设顺序表最多存储100个元素
typedef int DataType;          //定义数据类型
typedef struct
{
	DataType data[MaxSize];    //存放数据元素的数组
	int length;                //线性表的长度
}SeqList;


void InitList(SeqList* L);//初始化

void CreatList(SeqList* L, DataType a[], int n);//建立顺序表

int IsEmpty(SeqList* L);//判空操作

int Length(SeqList* L);//求顺序表的长度

void PrintList(SeqList* L);//遍历操作

void Insert(SeqList* L, int i, DataType x);//插入操作

void DeleteByLocate(SeqList* L, int i);//按位删除

void DeleteByElem(SeqList* L, DataType x);//按值删除

int GetLocate(SeqList* L, DataType x);//按值查找

int GetElem(SeqList* L, int i, DataType* p);//按位查找

源文件:SeqList.c

复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
void InitList(SeqList* L)
{
	L->length = 0;
}

void CreatList(SeqList* L, DataType a[], int n)
{
	if (n > MaxSize)	//判断顺序表的存储空间是否足够
	{
		printf("顺序表的空间不够,无法建立顺序表!\n");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		L->data[i] = a[i];
	}
	L->length = n;
}

int IsEmpty(SeqList* L)
{
	if (L->length == 0)
	{
		return 1; //为空,则返回1
	}
	else
	{
		return 0; //不为空,则返回0
	}
}

int Length(SeqList* L)
{
	return L->length;
}

void PrintList(SeqList* L)
{
	for (int i = 0; i < L->length; i++)
	{
		printf("%d ", L->data[i]);
	}
}

void Insert(SeqList* L, int i, DataType x)
{
	if (L->length >= MaxSize)
	{
		printf("表满了,插入失败!\n");
		return;
	}
	if (i < 1 || i > L->length)
	{
		printf("插入位置不合理,插入失败!\n");
		return;
	}
	for (int j = L->length; j >= i; j--)
	{
		L->data[j] = L->data[j - 1];//后移操作
	}
	L->data[i - 1] = x;//插入值
	L->length++;//长度加一
}

void DeleteByLocate(SeqList* L, int i)
{
	if (L->length == 0)
	{
		printf("表空,删除失败!\n");
		return;
	}
	if (i < 1 || i >= L->length)
	{
		printf("删除位置不合理,删除失败!\n");
		return;
	}
	for (int j = i; j < L->length; j++)
	{
		L->data[j - 1] = L->data[j];//前移操作
	}
	L->length--;//长度减一
}

void DeleteByElem(SeqList* L, DataType x)
{
	int sign = 0;
	for (int i = 0; i < L->length; i++)
	{
		if (L->data[i] == x)
		{
			sign = i + 1;//说明表中有该元素,修改sign,并存记录位置
			break;
		}
	}
	if (sign != 0)
	{
		for (int j = sign; j < L->length; j++)
		{
			L->data[j - 1] = L->data[j];//前移操作
		}
		L->length--;//长度减一
	}
	else
	{
		printf("表中没有元素,删除失败!\n");
	}
}

int GetLocate(SeqList* L, DataType x)
{
	for (int i = 0; i < L->length; i++)
	{
		if (L->data[i] == x)
		{
			return i + 1;//返回序号
		}
	}
	printf("表中没有该元素,查找失败!\n");
	return 0;   //退出循环,说明查找失败
}

int GetElem(SeqList* L, int i, DataType* p)
{
	if (i < 1 || i > L->length)
	{
		printf("查找位置不合理,查找失败!\n");
		return 0;
	}
	else
	{
		*p = L->data[i - 1];
		return 1;
	}
}

源文件:test.c

复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
int main()
{
	int a[] = { 10,12,15,25,8,16,20 };
	int sz = sizeof(a) / sizeof(a[0]);
	SeqList L;
	InitList(&L);
	CreatList(&L, a, sz);
	if (IsEmpty(&L))
	{
		printf("该顺序表为空\n");
	}
	printf("***********构造顺序表并遍历打印***********\n");
	PrintList(&L);
	printf("\n该顺序表的长度为%d\n", Length(&L));

	int i, x;
	printf("\n**************增---插入操作***************\n");
	printf("请输入要插入的位置和要插入的值:");
	scanf("%d%d", &i, &x);
	Insert(&L, i, x);
	PrintList(&L);
	printf("\n该顺序表的长度为%d\n", Length(&L));

	printf("\n**************删---按位删除***************\n");
	printf("请输入要删除的位置:");
	scanf("%d", &i);
	DeleteByLocate(&L, i);
	PrintList(&L);

	printf("\n\n**************删---按值删除***************\n");
	printf("请输入要删除的元素值:");
	scanf("%d", &x);
	DeleteByElem(&L, x);
	PrintList(&L);

	printf("\n\n**************查---按值查找***************\n");
	printf("请输入要查找的元素值:");
	scanf("%d", &x);
	int ret = GetLocate(&L, x);
	if(ret != 0)
	{
		printf("元素%d的位置是%d\n", x, ret);
	}

	printf("\n**************查---按位查找***************\n");
	printf("请输入要查找的元素位置:");
	scanf("%d", &i);
	if (GetElem(&L, i, &x) != 0)
	{
		printf("第%d个位置的元素是%d\n", i, x);
	}
	return 0;
}

运行结果:

2. 动态顺序表

头文件:SeqList.h

复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define InitCapicity 4      //设置初始容量
typedef int DataType;       //定义数据类型
typedef struct
{
	DataType* data;         //指向动态开辟的数组
	int length;             //有效数据个数
	int capicity;           //容量空间的大小
}SeqList;

void IncreaseCapacity(SeqList* L);//扩容操作

void InitList(SeqList* L);//初始化

void CreatList(SeqList* L, DataType a[], int n);//建立顺序表

int IsEmpty(SeqList* L);//判空操作

int Length(SeqList* L);//求顺序表的长度

void PrintList(SeqList* L);//遍历操作

void Insert(SeqList* L, int i, DataType x);//插入操作

void DeleteByLocate(SeqList* L, int i);//按位删除

void DeleteByElem(SeqList* L, DataType x);//按值删除

int GetLocate(SeqList* L, DataType x);//按值查找

int GetElem(SeqList* L, int i, DataType* p);//按位查找

void Destory(SeqList* L);//销毁操作

源文件:SeqList.c

复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
void IncreaseCapacity(SeqList* L)
{
	if (L->length == L->capicity)
	{
		DataType* tmp = (DataType*)realloc(L->data, sizeof(DataType) * L->capicity * 2);
		if (tmp == NULL)
		{
			perror("relloc fail");
			return;
		}	
		L->data = tmp;
		L->capicity *= 2;
		printf("扩容成功!\n");
	}
}

void InitList(SeqList* L)
{
	assert(L);//断言L非空指针
	L->data = (DataType*)malloc(sizeof(DataType) * InitCapicity);//开辟初始空间
	if (L->data == NULL)
	{
		perror("malloc fail");
		return;
	}
	L->length = 0;//有效数据个数置零
	L->capicity = InitCapicity;//设置初始容量
}

void CreatList(SeqList* L, DataType a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		IncreaseCapacity(L);//扩容操作
		L->data[i] = a[i];
		L->length++;//每加一个数据,有效数据个数就加一
	}
}

int IsEmpty(SeqList* L)
{
	if (L->length == 0)
	{
		return 1; //为空,则返回1
	}
	else
	{
		return 0; //不为空,则返回0
	}
}

int Length(SeqList* L)
{
	return L->length;
}

void PrintList(SeqList* L)
{
	for (int i = 0; i < L->length; i++)
	{
		printf("%d ", L->data[i]);
	}
}

void Insert(SeqList* L, int i, DataType x)
{
	if (i < 1 || i > L->length)
	{
		printf("插入位置不合理,插入失败!\n");
		return;
	}
	IncreaseCapacity(L);
	for (int j = L->length; j >= i; j--)
	{
		L->data[j] = L->data[j - 1];//后移操作
	}
	L->data[i - 1] = x;//插入值
	L->length++;//长度加一
}

void DeleteByLocate(SeqList* L, int i)
{
	if (L->length == 0)
	{
		printf("表空,删除失败!\n");
		return;
	}
	if (i < 1 || i >= L->length)
	{
		printf("删除位置不合理,删除失败!\n");
		return;
	}
	for (int j = i; j < L->length; j++)
	{
		L->data[j - 1] = L->data[j];//前移操作
	}
	L->length--;//长度减一
}

void DeleteByElem(SeqList* L, DataType x)
{
	int sign = 0;
	for (int i = 0; i < L->length; i++)
	{
		if (L->data[i] == x)
		{
			sign = i + 1;//说明表中有该元素,修改sign,并存记录位置
			break;
		}
	}
	if (sign != 0)
	{
		for (int j = sign; j < L->length; j++)
		{
			L->data[j - 1] = L->data[j];//前移操作
		}
		L->length--;//长度减一
	}
	else
	{
		printf("表中没有元素,删除失败!\n");
	}
}

int GetLocate(SeqList* L, DataType x)
{
	for (int i = 0; i < L->length; i++)
	{
		if (L->data[i] == x)
		{
			return i + 1;//返回序号
		}
	}
	printf("表中没有该元素,查找失败!\n");
	return 0;   //退出循环,说明查找失败
}

int GetElem(SeqList* L, int i, DataType* p)
{
	if (i < 1 || i > L->length)
	{
		printf("查找位置不合理,查找失败!\n");
		return 0;
	}
	else
	{
		*p = L->data[i - 1];
		return 1;
	}
}

void Destory(SeqList* L)
{
	free(L->data);
	L->data = NULL;
	L->length = 0;
	L->capicity = 0;
}

源文件:test.c

复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
int main()
{
	int a[] = { 10,12,15,25,8,16,20 };
	int sz = sizeof(a) / sizeof(a[0]);
	SeqList L;
	InitList(&L);
	printf("***********构造顺序表并遍历打印***********\n");
	CreatList(&L, a, sz);
	if (IsEmpty(&L))
	{
		printf("该顺序表为空\n");
	}
	PrintList(&L);
	printf("\n该顺序表的长度为%d\n", Length(&L));

	int i, x;
	printf("\n**************增---插入操作***************\n");
	printf("请输入要插入的位置和要插入的值:");
	scanf("%d%d", &i, &x);
	Insert(&L, i, x);
	PrintList(&L);
	printf("\n该顺序表的长度为%d\n", Length(&L));

	printf("\n**************删---按位删除***************\n");
	printf("请输入要删除的位置:");
	scanf("%d", &i);
	DeleteByLocate(&L, i);
	PrintList(&L);

	printf("\n\n**************删---按值删除***************\n");
	printf("请输入要删除的元素值:");
	scanf("%d", &x);
	DeleteByElem(&L, x);
	PrintList(&L);

	printf("\n\n**************查---按值查找***************\n");
	printf("请输入要查找的元素值:");
	scanf("%d", &x);
	int ret = GetLocate(&L, x);
	if (ret != 0)
	{
		printf("元素%d的位置是%d\n", x, ret);
	}

	printf("\n**************查---按位查找***************\n");
	printf("请输入要查找的元素位置:");
	scanf("%d", &i);
	if (GetElem(&L, i, &x) != 0)
	{
		printf("第%d个位置的元素是%d\n", i, x);
	}

	Destory(&L);
	return 0;
}

四、总结

静态顺序表与动态顺序表作为线性表中顺序表的两种存储结构,均以连续的内存存储空间实现数据的访问,但也存在许多差异。

  1. 静态顺序表编译时是确定的固定容量,具有内存分配简单,但空间利用率低且无法动态扩展,适用于数据规模已知且稳定的场景;
  2. 动态顺序表则通过动态内存分配在运行时按需调整表的大小,更适合数据规模变化频繁或难以预估的场景。
相关推荐
nbsaas-boot1 小时前
Java 正则表达式白皮书:语法详解、工程实践与常用表达式库
开发语言·python·mysql
岁忧1 小时前
(LeetCode 面试经典 150 题 ) 11. 盛最多水的容器 (贪心+双指针)
java·c++·算法·leetcode·面试·go
chao_7891 小时前
二分查找篇——搜索旋转排序数组【LeetCode】两次二分查找
开发语言·数据结构·python·算法·leetcode
风无雨1 小时前
GO 启动 简单服务
开发语言·后端·golang
斯普信专业组1 小时前
Go语言包管理完全指南:从基础到最佳实践
开发语言·后端·golang
秋说3 小时前
【PTA数据结构 | C语言版】一元多项式求导
c语言·数据结构·算法
Maybyy3 小时前
力扣61.旋转链表
算法·leetcode·链表
我是苏苏3 小时前
C#基础:Winform桌面开发中窗体之间的数据传递
开发语言·c#
斐波娜娜3 小时前
Maven详解
java·开发语言·maven
小码氓4 小时前
Java填充Word模板
java·开发语言·spring·word