顺序表(第二节)实现和解析

目录

1.顺序表中的头文件 (每一种函数方法)

 2.关于typedef 的用法

3.初始化和销毁表

3.1初始化表

3.2销毁表 

4.打印表

5.自动扩容表!!!(重点)

6.头部插入表和尾部插入表

6.1尾部插入表 

6.2头部插入表 

7.头部删除表和尾部删除表

7.1判断表是否为空

7.2尾部删除表

7.3头部删除表

8. 指定位置的插入和删除

8.1指定位置的插入

8.2指定位置的删除 

大家有什么不懂的可以私信问我,我也是刚学会!! 


1.顺序表中的头文件 (每一种函数方法)

#define INIT_CAPACITY 4
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
    SLDataType* a;
    int size; // 有效数据个数
    int capacity; // 空间容量
}SL;
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//指定位置之前插⼊/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);

我的讲述方法是对这几行代码一一进行讲解,建议复制一份在VS中,边看博客边看头文件,不然真的看不懂。

 2.关于typedef 的用法

1.typedef用来给类型重命名,这里为什么要把int 重命名改为SLDataType呢?

因为如果用int的话后续的代码如果要更改,那就得把全部的int 都更改,如果提前typedef了就可以直接改 上面的类型就好了

2.下面把struct SeqList 改为SL仅仅是因为前面那一串太长了,给他缩短一点,增加代码的可读性

3.初始化和销毁表

3.1初始化表

 初始化表,用于初始化变量。变量的使用前都是要初始化的。

a的指针的意思是指向 要填充的变量

capacity的意思是目前开辟的内存的量,size表示现在被占用的内存的量

void SLInit(SL* ps)
{
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

3.2销毁表 

判断ps->a的原因是,判断这个指针是不是为空,如果为空,会不执行任何操作直接返回。

然后再给ps a 置空,capacity和size 置为0;

void SLDestory(SL* ps)
{
	if (ps->a)
	{
		free(ps->a);
	}
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

4.打印表

 打印表很简单,就用for循环遍历 realloc开辟的空间就好了(在下一部分自动扩容表讲解)

注意打印结束后换行,避免下次打印连在一起。

ps->a[ i ] 的意思就和数组一样。用来表示每一块开辟空间里的存储的值。

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

5.自动扩容表!!!(重点)

1. 首先扩容表的前提是,你的capacity(现在总共开辟的内存量)和size(已有数据的内存)这两个变量已经相等,说明这时候的内存已经不够了,需要添加。

2.所以判断语句是 ps capacity == ps size(这里不写箭头了)。

3.看到下面的int newcapacity 其实是用来判断 ps->capacity是否为0.如果为0则就直接等于4

不等于就等于 2 * ps->capacity, (0 ?4 :2*ps capacity就是判断是否为0,真就为前,假就为后)。

4.为什么要判断是否为0呢?因为如果为0,你下面开辟的代码 realloc开辟出来的空间还是0

5.之和在判断是否为NULL开辟失败的情况。

6.最后将开辟的空间 再次赋予 ps a。

7.ps capacity = newcapacity。

void SLCheckCapacity(SL* ps)
{
	if (ps->capacity == ps->size)
	{
	    /*if (ps->capacity == 0)  判断capacity的情况,和下面newcapacity意思相同
		{
			ps->capacity == 4;
		}
		else
		{
			ps->capacity == ps->capacity * 2;
		}*/
		int newCapacity = ps->capacity == 0 ? 4 : 2*ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc失败\n");
			return;
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
}

6.头部插入表和尾部插入表

6.1尾部插入表 

 很简单,1.首先需要断言 ps 是否为空 NULL,因为为空指针,进行解引用的时候会变成野指针。所以要保证传进来的指针不为空。

2.给一个上面的自动扩容表的函数,用来判断空间是否足够。

3.如果足够 则将 ps size 这个空间的位置赋值为x 。(因为是数组,所以第一个数的下标是0.所以ps size 表示被占用的空间数量,比如说有三个空间被占用,ps size =3,而被占空间的下标是0 1 2,所以3这个空间是没数值的,直接赋值即可。)

4.记得每次插入表给一个size ++;

void SLPushBack(SL* ps,SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    ps->a[ps->size] = x;
    ps->size++;
}

6.2头部插入表 

一样的先断言看看ps是否为空。

1.与尾部插入不同的点就是,在头部插入,怎么在头部插入呢?

可以用for循环从后往前遍历一遍,将每一个数值都往后移一个内存。

2.然后再将 0 下标位置的内存赋值为 x。

3.插入表一定要size++

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];

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

7.头部删除表和尾部删除表

7.1判断表是否为空

为什么要进行多一步判断表为空?

可以想象如果一个表都为空了,那它还删除什么?那肯定报错啊

所以在进行删除表的时候要进行判断表的有无。

bool 表达式只有两个值 真就是为true 为1,假就是 false 为0.

所以return的值要是一个表达式,用来判断是否为真或假。

bool SLIsEmpty(SL* ps)
{
	assert(ps);
	return ps->size == 0;
}

7.2尾部删除表

 超简单的删除尾表。

1.一样的判断ps是否为空

2.判断表是否为空

3.对于一个开辟的表的内存,如果size--就说明有数据的空间-1. 下次在插入表数据的时候,直接使用这个空间,这样就是类似一个抽象的删除,因为下次添加数据还是用的这块内存,所以你不用给这块空间的数据清空也可以使用。

可以想象一个二手汽车,前一个拥有人想把它卖了,这时候这车就是无主车,等下一个车主来了,直接把前一个车主的名字改为下一个车主的名字即可。

void SLPopBack(SL* ps)
{
	assert(ps);
	assert(!(SLIsEmpty(ps)));
	ps->size--;
}

(!SLIsEmpty( ps )) 这个表达式的意思就是 上面用bool函数判断 表中有没有数据,

如果表中没有数据 就返回了一个1 ,用!来取反 所以这里就是 如果为1则断言程序终止。

如果为0就说明 有数据 程序继续进行。

7.3头部删除表

1.首先还是判断ps是否为空,和表是否为空

2.用for循环遍历把后一个的值赋予前一个。

为什么是 i < ps size - 1.因为我们只要有数据的空间,如果是 ps size不减一 那就会把 有一个空着的数据传到 最后一个位置。这是不需要的,因为是删除表,最后一个位置要被放弃的。

3.删除表 要 ps size --

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(!(SLIsEmpty(ps)));
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

8. 指定位置的插入和删除

8.1指定位置的插入

1.首先判断 ps 是否为空,判断 pos 是否超出表的范围。

2.因为是插入表所以要判断空间是否足够

3.用for循环 从后往前遍历 将前一个的数值赋值给后一个

为什么是 i = ps size -1,因为 ps size 的值 是表示 有数据的空间个数,而对于空间的下标来说,ps size 这个数字的空间下表指向的反而是下一个无数据的空间。我们看第一组的转换

ps a【i + 1】= ps a【i】如果把i 换了 就变成了ps a 【ps size】 = ps a 【ps size -1】。

这样就很好理解了,把下一个空数据的内存赋值为 最后一个内存的值。

pos -1,和上面类似,可以自己思考一下。(不懂可以私聊我,看到必回)

4.最后在pos 那个位置插入数据 x

5.插入表 ps size++

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	for (int i = ps->size - 1; i > pos - 1; i--)
	{
		ps->a[i + 1] = ps->a[i];
	}
	ps->a[pos] = x;
	ps->size++;
}

8.2指定位置的删除 

1.因为是删除首先判断 ps 是否为空 表是否为空,在判断 要查找的数字是否在表数据的范围内。

2.这个删除和上面类似,就是要把前一个数据的值改为下一个数据的值即可,从pos的位置开始,因为要把pos位置的值给占领了。

3.ps size--

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(!SLIsEmpty(ps));
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];

	}
	ps->size--;
}

大家有什么不懂的可以私信问我,我也是刚学会!! 

你可能感兴趣的:(c语言顺序表篇,数据结构,c语言,c++,开发语言)