[数据结构基础]顺序表详解

一. 什么是顺序表

顺序表本质上为数组,但是,在数组的基础上,顺序表要求数据是连续存储的,不能跳跃间隔。顺序表分分为静态顺序表和动态顺序表两种。

1.1 静态顺序表

静态顺序表的容量是固定不变的,当存储的数据量达到上限时,无法进行扩容。

静态顺序表的定义:

typedef struct SeqList

{

        int a[1000];   //存储数据的数组

        int size;  //当前存储数据的个数

}SL;

该静态顺序表最多存储1000个整型数据

静态顺序表的灵活性较差,很难判断应该开辟多大的内存空间,容易造成空间不足或浪费。

1.2 动态顺序表

动态顺序表在存储数据达到容量上限时,可以进行扩容,可以有效避免静态顺序表中易出现的内存空间开辟不足的情况。

动态顺序表的定义:

typedef struct SeqList

{

        int* a;  //指向为存储数据而动态开辟的内存空间

        int size;  //动态顺序表中已经存储的数据的个数

        int capacity;  //该动态顺序表当前存储数据的上限

}

图1.1  顺序表物理结构示意图

 

二. 顺序表接口函数

由于实际工程应用中多采用动态顺序表,因此,本文以动态顺序表为例,对顺序表接口函数进行讲解(以1.2中定义的动态顺序表为例)。

注:演示代码中DataType全部表示为int(在头文件中使用了#define进行重定义)

2.1 顺序表初始化函数SeqListInit

假设要将顺序表中的int* a初始化为NULL,将当前存储数据量size和顺序表容量capacity均初始化为0,具体实现见演示代码2.1。

演示代码2.1:

//初始化顺序表函数
void SeqListInit(SL* ps)
{
	ps->a = NULL;  //数据存储空间
	ps->size = 0;  //初始状态没有存储数据
	ps->capacity = 0;  //初始状态顺序表容量为0
}

2.2 尾插数据函数SeqListBackPush

尾插数据函数实现的功能是将一个特定的数据x插到顺序表的尾部,SeqListBackPush函数首先调用容量检查函数SeqListCheckCapacity,判断顺序表是否有剩余的空间可以容纳数据x,如果没有剩余空间,则调用realloc函数扩大顺序表的空间。在确保顺序表获得足够空间后,首先使用ps->a[ps->size] = x语句在尾部插入x,然后使用ps->size++语句来记录顺序表中数据量+1。具体实现见演示代码2.2。

演示代码2.2:

//容量检查函数
void SeqListCheckCapacity(SL* ps)
{
	if (ps->capacity == 0 || ps->capacity == ps->size)
	{
		int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity);  //新开辟的空间容量
		DataType* tmp = (DataType*)realloc(ps->a, newcapacity * sizeof(DataType));
		if (NULL == tmp)  //检验是否开辟成功
		{
			perror("realloc");
			exit(-1);
		}
		ps->a = tmp;  //ps->a指向新的空间
		ps->capacity = newcapacity; //容量更新
	}
}

//尾插函数
void SeqListBackPush(SL* ps, DataType x)
{
	//检验顺序表存储空间是否已满
	SeqListCheckCapacity(ps);   //检验函数,若满了就扩容

	ps->a[ps->size] = x; //在顺序表尾部插入数据
	ps->size++;  //数据量+1
}

2.3 尾删数据函数SeqListBackPop

尾删函数实现的功能是删除顺序表尾部的一个数据。只需要顺序表中数据个数size减一,即可得到与在尾部删除一个数据相同的效果。在删除数据前应判断顺序表中是否存在数据,ps->size==0表示顺序表中无数据,不进行任何操作。尾删函数的具体实现见演示代码2.3。

演示代码2.3:

//尾删函数
void SeqListBackPop(SL* ps)
{
	//如果顺序表中有数据,就将数据量-1,效果等同于尾删
	//如果顺序表中没有数据,不进行任何操作
	if (ps->size > 0)  
	{
		ps->size--;
	}
}

2.4 头插数据函数SeqListFrontPush

首先检查顺序表是否有剩余空间,没剩余空间就扩容。要在头部插入数据,首先应当把顺序表中现存的数据均向后移动一个单位(向后移动数据应先从顺序表尾部的数据开始,若先移动顺序表起始位置的数据,则会造成数据在移动之前被覆盖的问题),移动完数据后,将ps->a[0]赋值为x,ps->size加一。头插函数的具体实现见演示代码2.4。

演示代码2.4:

void SeqListFrontPush(SL* ps, DataType x)
{
	SeqListCheckCapacity(ps);  //检验顺序表存储空间是否已满

	//顺序表中已有数据向后移动
	int end = ps->size;  //尾部数据位置
	while (end--)
	{
		ps->a[end + 1] = ps->a[end];
	}

	ps->a[0] = x; //在头部插入数据x
	ps->size++;  //容量扩大
}

2.5 头删数据函数SeqListFrontPop

头删数据函数实现的功能是删除顺序表中的第一个元素。首先判断顺序表中是否存在数据,若存在,再分为两种情况讨论:

  1. 顺序表中只有一个数据,直接使用ps->size--删除数据
  2. 顺序表中含有两个及以上数据,首先将第二个数据往后的每个数据向前移动一个单位,然后用ps->size-- 指令完成头删操作。

头删数据函数的具体实现见演示代码2.5。

演示代码2.5:

void SeqListFrontPop(SL* ps)
{
	//检查顺序表中是否存在数据
	//不存在数据则不执行任何操作
	if (ps->size > 0)
	{
		//如果顺序表中只有一个数据,则直接将其删除
		if (ps->size == 1)
		{
			ps->size--;
		}
		else
		{
			//有两个及以上数据,从前往后移动数据,数据量-1
			int begin = 1;
			while (begin < ps->size)
			{
				ps->a[begin - 1] = ps->a[begin];
				begin++;
			}
			ps->size--;
		}	
	}
}

2.6 数据查找函数SeqListFind

依次遍历顺序表中每个数据,与待查找的数据x进行比较,发现与x相同的值就打印这是顺序表的第几个数据,退出函数。若遍历完顺序表中所有数据还没找到x,就打印找不到。详见演示代码2.6。

演示代码2.6:

void SeqListFind(SL* ps, DataType x)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			printf("找到了,%d位于顺序表的第%d个位置\n", x, i + 1);
			return;
		}
	}
	printf("找不到\n");
}

2.7 在指定位置插入数据函数SeqListInsert

该函数有三个参数,其中pos为插入数据的位置,x为插入的值。函数首先判断pos是否超出范围,若超出范围就报错,函数终止执行。当确定pos为超出范围后,先检查容量,顺序表中若无剩余空间就扩容,再将第pos个数据开始的每个数据向后移动一个单位,最后将第pos个数据重新赋值为x,数据量ps->size加一。具体实现见演示代码2.7。

演示代码2.7:

void SeqListInsert(SL* ps, int pos, DataType x)
{
	//检验插入位置是否超出范围
	//若超出范围,则提示错误信息,退出函数
	if (pos <= 0 || pos > ps->size)
	{
		printf("插入数据的位置越界!\n");
		return;
	}

	//检验容量是否足够
	SeqListCheckCapacity(ps);

	//从pos位置开始,每个数据向后移动一位
	int end = ps->size;
	while (end-- >= pos)
	{
		ps->a[end + 1] = ps->a[end];
	}

	ps->a[pos - 1] = x;
	ps->size++;
}

2.8 删除指定位置数据函数SeqListErase

该函数首先判断pos是否超出范围,在确定pos在合法范围内后,将pos之后的数据全都向前移动一位即可。详见演示代码2.8。

演示代码2.8:

void SeqListErase(SL* ps, int pos)
{
	//pos位置往后的数据全部向前移动一个单位
	int begin = pos;
	for (begin = pos; begin < ps->size; begin++)
	{
		ps->a[begin - 1] = ps->a[begin];
	}
	ps->size--;
}

2.9 顺序表内容打印函数SeqListPrint

依次遍历顺序表中的每个数据,使用printf进行打印即可,详见演示代码2.9。

演示代码2.9:

void SeqListPrint(SL* ps)
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

2.10 释放动态内存函数SeqListDestory

该函数在顺序表使用全部结束后调用,该函数实现的功能有:释放动态内存、将顺序表中包含数据个数size和顺序表容量capacity均置零。详见演示代码2.10。

演示代码2.10:

void SeqListDestory(SL* ps)
{
	free(ps->a);
	ps->a = NULL;  //释放动态开辟的内存空间
	ps->size = ps->capacity = 0; //顺序表的内容量和容量均清零
}

三. 顺序表的缺陷

  1. 顺序表在空间不足时需要扩容,而扩容需要付出一定的代价。
  2. 为了避免频繁扩容,我们在顺序表无剩余空间时一般都是扩容2倍,这可能导致空间浪费。
  3. 顺序表要求数据从开始位置连续存储,那么我们在头部或者中间部分插入数据、删除数据时,就需要大量挪动数据,效率不高。

链表就是针对线性表的缺陷而设计的,我的下篇博客会对链表进行详解。

全文结束,感谢大家的阅读,敬请批评指正。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
静态顺序是一种使用数组实现的线性,它的大小在编译时确定,不可动态改变。下面是静态顺序的C语言实现: ```c #define MAX_SIZE 100 // 定义静态顺序的最大容量 typedef struct { int data[MAX_SIZE]; // 用数组存储数据元素 int length; // 当前顺序的长度 } SeqList; // 初始化顺序 void init(SeqList *list) { list->length = 0; } // 插入元素 int insert(SeqList *list, int index, int value) { if (index < 0 || index > list->length || list->length == MAX_SIZE) { return 0; // 插入位置不合法或顺序已满,插入失败 } for (int i = list->length - 1; i >= index; i--) { list->data[i + 1] = list->data[i]; // 将插入位置后的元素后移一位 } list->data[index] = value; // 在插入位置插入新元素 list->length++; // 长度加1 return 1; // 插入成功 } // 删除元素 int remove(SeqList *list, int index) { if (index < 0 || index >= list->length) { return 0; // 删除位置不合法,删除失败 } for (int i = index + 1; i < list->length; i++) { list->data[i - 1] = list->data[i]; // 将删除位置后的元素前移一位 } list->length--; // 长度减1 return 1; // 删除成功 } // 获取元素 int get(SeqList *list, int index) { if (index < 0 || index >= list->length) { return -1; // 获取位置不合法,返回-1示失败 } return list->data[index]; } // 修改元素 int modify(SeqList *list, int index, int value) { if (index < 0 || index >= list->length) { return 0; // 修改位置不合法,修改失败 } list->data[index] = value; return 1; // 修改成功 } ``` 以上是静态顺序的简单实现,包括初始化、插入、删除、获取和修改等基本操作。你可以根据需要进行调用和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值