数据结构之动态顺序表(含游戏菜单)

Posted 芒果再努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之动态顺序表(含游戏菜单)相关的知识,希望对你有一定的参考价值。

      上一篇文章我们谈了数据结构的静态顺序表,以及实现了静态顺序表,具体可以看我的上一篇文章->>>数据结构之静态顺序表.

    我们可以知道,静态顺序表的缺点是:  因为使用的是定长数组,所以空间给少了不够用,给多了,就会造成空间的浪费.  

        由此,我们引入一个新的顺序表----动态顺序表.  


目录

      一.什么是动态顺序表

            什么是逻辑结构和物理结构

 二.动态顺序表的优缺点

三.创建项目

  四.接口实现

     1.定义顺序表

     2.结构体初始化

     3.打印数据

      4.顺序表增容

       5.头插数据

        6.尾插数据

       7.头删数据

       8.尾删数据

       9.查找数据、

     10.某一位置插入数据

     11.删除某一位置

     12.修改某一位置数据

    13.顺序表的元素个数

    14.销毁顺序表

  精华总结:

附:原码链接




       一.什么是动态顺序表?

 动态顺序表:使用动态开辟的数组存储,使用指针指向动态开辟的数组,可以进行扩容。可以队数组内容进行增删查改等操作。

 

        这里顺便提一下什么是逻辑结构和物理结构

  • 逻辑结构: 人为想象出来的,实际并不存在.
  • 物理结构: 实际存在,可以被观察到

二.动态顺序表的优缺点

优点:空间连续,支持随机访问。

缺点:1.中间或前面部分的插入时间复杂度是O(N),

            2.增容的代价比较大    


三.创建项目

工程文件存放的内容
SeqList.h函数声明,宏定义
SeqList.c实现顺序表函数接口的定义
test.c测试函数接口是否能实现功能

  四.接口实现

        1.定义顺序表

        为了后续我们想更改数据类型时方便,我们一般对类型进行typedef操作。

typedef int SeqlistType;

//创建结构体
typedef struct Seqlist
{
	SeqlistType* arr;	//指向动态开辟的数组
	SeqlistType size;	//有效个数
	SeqlistType capacity;//容量
}SL;


     2.结构体初始化

传值:因为形参是实参的一份临时拷贝,对形参的改变并不会改变实参。所以我们应该把结构体变量的地址传过去,用结构体指针接收!为了防止传过来的是空指针,我们可以用assert进行断言!

void SeqlistInit(SL* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

3.打印数据

我们只需要遍历顺序表就能完成打印

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

      4.顺序表增容

        我们插入数据时,应该要先检查顺序表的容量是否满了(ps->size == ps->capacity)

因为一开始我们时没有给容量的(capacity = 0),所以我们可以先给4个空间,后续不够空间了再两倍的扩容

       

//检查容量
void SeqlistCheckCapacity(SL* ps)
{
	assert(ps);
	//当size == capacity时要扩容了
	int newcapacity = 0;
	if (ps->size == ps->capacity)
	{
		newcapacity = (ps->capacity == 0 ? 4 : ps->capacity * 2);
		//如果一开始容量为0,就给4个空间
		//不为0就扩为原来的2倍
		//扩容还要重新开辟空间->realloc
		SeqlistType* tmp = 
			(SeqlistType*)realloc(ps->arr, sizeof(SeqlistType) * newcapacity);
		//注意:要判断是否开辟成功
		if (tmp == NULL)
		{
			printf("扩容失败\\n");
			//exit(-1);
			return;
		}
		else
		{
			ps->arr = tmp;
			ps->capacity = newcapacity;
		}
	}
}

        5.头插数据

        头插数据:1.要先检查顺序表容量,不够则扩容

                           2.头插即为把数据放到第一个位置,那我们把数组原来的数据向后移动即可!要注意从最右边的开始移动(i = ps->size-1),否则前面的数据会把后面的数据覆盖!

                           3.插入数据后,标记有效数字的size+1;

       

//头插
void SeqlistPushFront(SL* ps, SeqlistType x)
{
	assert(ps);
	//注意先检查容量!!!!
	SeqlistCheckCapacity(ps);
	int i = 0;
	for (i = ps->size-1; i >=0; i--)
	{
		ps->arr[i+1] = ps->arr[i];
	}
	ps->arr[0] = x;
	ps->size++;
}

     6.尾插数据

        尾插数据:1.尾插数据也要先检查容量

                          2.在数组的位置上插入数据即:ps->size位置

                           3.插入数据,szie++;

       

//尾插
void SeqlistPushBack(SL* ps, SeqlistType x)
{
	assert(ps);
	//注意先检查容量!!!!
	SeqlistCheckCapacity(ps);
	ps->arr[ps->size] = x;
	ps->size++;
}

       7.头删数据

  头删数据:1.把后面的数据往前面覆盖,要注意从左边开始(i=0),否则会造成数据丢失!

                     2.删除了数据,size--

void SeqlistPopFront(SL* ps)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i+1];
	}
	ps->size--;
}

       8.尾删数据

       只需让有效数字-1即可,数据虽然还在数组里,但是我们以及访问不到了!

void SeqlistPopBack(SL* ps)
{
	assert(ps);
	ps->size--;
}

       9.查找数据、

1查找元素是否在顺序表内,如果元素在数组中的下标.

2.如果不在,返回-1  (-1为无效坐标)

3.返回类型为int  若返回类型写成我们之前typedef的类型要注意typedef的类型是什么!

int SeqlistFind(SL* ps, SeqlistType x)
{
	assert(ps);
	//遍历查找
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
			return i;
	}
	return -1;	//-1为无效坐标
}

10.某一位置插入数据

     函数接口实现内容:给一个下标,在该下标前位置插入数据.

 1.要保证位置在数组有效范围内(ps->size-1范围内)

       2.把要插入位置的数据及后面的数据向后移动,注意要从最后的数据开始移动(i=ps->size-1),否则会造成前面的数据覆盖后面的数据。

        3.最后把数据插入该位置,size++

//插入
void SeqlistInsert (SL* ps, SeqlistType pos, SeqlistType x)
{
	assert(ps);
	//要保证在有效区域内插入
	assert(pos < ps->size);
	//把pos位置及其后面的数据后移  
	// 要从最后面开始移动
	//要保证容量
	SeqlistCheckCapacity(ps);
	for (int i = ps->size-1; i >=pos;i--)
	{
		ps->arr[i+1] = ps->arr[i];
	}
	ps->arr[pos] = x;
	ps->size++;
}

     11.删除某一位置

     函数接口实现内容:给一个下标,删除该位置。

     1.要保证要删除的位置的数组在数据范围内((ps->size-1范围内))

      2.把该位置后面的数据向前移动,把要删除位置的数据覆盖即可。

      3.删除掉元素->size--

//删除
void SeqlistErase(SL* ps, SeqlistType pos)
{
	assert(ps);
    assert(pos<ps->size);
	//把pos后面的东西前移动
	for (int i = pos + 1; i < ps->size; i++)
	{
		ps->arr[i-1] = ps->arr[i];
	}
	ps->size--;
}

       12.修改某一位置数据

       函数接口实现内容:给一个下标,修改该下标对应的值

               要保证该下标在数组的数据范围内(ps->size-1范围)

//更改数据
void SeqlistModify(SL* ps, SeqlistType pos, SeqlistType x)
{
	//要保证在有效数据范围内更改
	assert(pos < ps->size);
	ps->arr[pos] = x;
}

13.顺序表的元素个数

               size标识的就是顺序表的有效数字个数

//统计顺序表元素数量
int  SeqListSize(SL* ps)
{
	assert(ps);
	return ps->size;
}

14.销毁顺序表

释放掉动态开辟的数组即可,free和指针置空要同时使用!不然就可能造成野指针!

//销毁空间
void SeqlistDestory(SL* ps)
{
	//free和NULL要配合使用
	free(ps->arr);
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

   精华总结:

               1. 有些童鞋理解不了size和数组下标的关系!下面请看图

 size是有效数字的个数,即数组有多少个元素。数组的下标是从0开始的,所以ps->arr[ps->size]是数组最后一个元素后面的位置。数组最后一个元素下标为ps->arr[ps->size-1]

        其他注意事项:

               !搞清楚传值还是传址问题!

传值:形参是实参的一份临时拷贝,对形参的改变并不会改变实参

        1.插入数据时要检查容量!

        2.在某个下标,修改/删/插入数据,要保证该下标在数组下标的范围内(ps->size-1)

        3.插入/删除数据后,size也要记得++ /-- !

        4.头插头删以及在某个下标位置前插入,要搞清楚从哪边元素开始移动!


当我们将全部接口实现时,我们就可以设计成一个游戏菜单了!

效果展示:

 

 具体游戏代码以及本文章中的所有代码都在附录!

     如果感觉本篇文章对你有所帮助,请给笔者一个小小的点赞,评论和关注吧!你们的支持就是我的动力!


附:原码链接

     动态顺序表代码

以上是关于数据结构之动态顺序表(含游戏菜单)的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之单链表

数据结构之顺序表的增删查改等操作详解

数据结构之动态顺序表(C实现)

数据结构之顺序表

线性表之顺序存储结构(顺序表)

C数据结构单链表接口函数逻辑解析与代码实现(含详细代码注释)