数据结构——顺序表的实现

呀哈喽,我是结衣。
提到数据结构,最最基础的当然是数据表了,今天我来教大家如何实现C语言中的数据表。

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使
⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储。
案例:蔬菜分为绿叶类、⽠类、菌菇类。线性表指的是具有部分相同特性的⼀类数据结构的集合

今天我们要实现的就是动态的顺序表。

文件的创建

在实现顺序表前,我们要先创建3个文件,头文件Sealist.h用来放函数的声明,源文件Seqlist.c用来实现函数,源文件test.c用来放主函数,也用来调用函数。
数据结构——顺序表的实现_第1张图片

结构体的创建

要实现顺序表我们首先要创建一个经结构体,在这个结构中,我们要存放,数据,有效数据的个数,以及有效空间的大小
那么我们就可以这样写

struct SeqList
{
	int* a;
	int size;
	int capacity;
};

如果用这个来存放数据,是不是只能存放整形的数据,如果我们要存放其他类型的数据不就要重写了吗?为了避免这个情况,我们就要用到typedef

typedef int SLDataType;
// 动态顺序表
typedef struct SeqList
{
	SLDataType* a;
	int size;
	int capacity;
}SL;

这样的话,我们如果再下次要存放其他类型的数据只需要,把typedef int SLDataType;中的int改为相应的数据就可以了。

函数的声明

我们应该怎么实现顺序表,实现顺序表需要哪几个函数呢?
让我们来想想,顺序表就是要插入数据,删除数据,那我们是不是就要写实现插入和删除的数据的函数呢,还是我们是不是应该给顺序表来初始化呢,然后再顺序表结束后是不是应该销毁顺序表呢?想到这些后我们就可以来写函数的声明了。
首先就是初始化然后把这些都敲上去,名字的话建议和我写一样的,因为以后的c++也是这么命名的。

//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//尾插
void SLpushBack(SL* ps, SLDataType x);
//头插
void SLpushFront(SL* ps, SLDataType x);
//尾删
void SLpopBack(SL* ps);
//头删
void SLpopFront(SL* ps);

当然写完这些其实还是没有把函数的声明写完,在后面我会一一补充的

函数的实现

写完了函数的声明,我们就要来进行函数的实现了。
首先就是初始化

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

我这里把它初始化为空,你们也可以不初始化为空。

写完初始化我们来写销毁

//销毁
void SLDestroy(SL* ps)
{
	if (ps->a != NULL)
	{
		free(ps->a);
		ps->a = NULL;
		ps->size = 0;
		ps->capacity = 0;
	}
}

在里要注意的就是如果不为NULL才销毁,还有就是ps->a free后要记得赋值为NULL。

接下来我们就来首先尾插,顾名思义就是从后面插入数值。
我们先假设空间足够的情况下,我们要把x插入到最后面,我们应该怎么做呢?
数据结构——顺序表的实现_第2张图片
首先我们肯定要想到size,它是有效数据的个数,图中这个情况就size等于4,4的化不就是数组元素4后面的下标位置吗?为此我们就要利用这个特性,直接把ps->a[size] = x;这样就顺利的将x插入到顺序表最后的位置了。

考虑的空间足够的情况,我们还要来考虑空间不足的情况下,当size == capecity的时候就说明空间不足了,那么我们就要来扩容了。
扩容就要用到realloc这个函数了。
数据结构——顺序表的实现_第3张图片
了解完了我们就来敲代码了。

//尾插
void SLpushBack(SL* ps,SLDataType x)
{
	assert(ps);
	//空间不足,扩容
	if (ps->size == ps->capacity)
	{
		ps->a = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
	}
	ps->a[ps->size++] = x;
	ps->capacity = ps->capacity * 2;
}

如果我们这样写是不是还会有一些错误呢?答案肯定是有的,比如因为我们的在初始化的时候给capacity赋值为0.那么在第一次尾插的时候就出现了空间不足,在扩容的时候,capacity*2还是为0,问题出现了,我们就要来修复问题。
为了修复这个问题我们就要再加一个判断条件,我们可以使用一个三目操作符“?:”,来解决问题
下面看代码

//尾插
void SLpushBack(SL* ps,SLDataType x)
{
	assert(ps);
	//空间不足,扩容
	if (ps->size == ps->capacity)
	{
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
		//防止扩容失败
		if (tmp == NULL)
		{
			perror("realloc fail!\n");
			return 1;
		}
		ps->a= tmp;
		ps->capacity = NewCapacity;
	}
	ps->a[ps->size++] = x;
	
}

写到这尾插就结束了,我们来试试效果吧。
为此我们要写一个打印函数。

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

数据结构——顺序表的实现_第4张图片
没问题,那么下面就是头插。
数据结构——顺序表的实现_第5张图片
我们要把x插到最前面,那么我们就要哪后面的数据都往后移上一位。怎么移就是我们该考虑的问题了。
要把数据往后移,是从前往后移呢还是从后往前移呢,如果我们从前往后移,那么后面的数据就会被覆盖,所以我们一个从前往后移。
把4往后移一位,再把3移到4原来的位置。最后再把ps->a[0] = x;想到了这些,我们就可以用代码来实现了。
首先还是经典的判断空间是否足够,和尾插时的判断一模一样。

void SLpushFront(SL* ps, SLDataType x)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!\n");
			return 1;
		}
		ps->a = tmp;
		ps->capacity = NewCapacity;
	}
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;
	ps->size++;
	
}

既然判断的条件一模一样,那样的话我们就可以用一个函数来实现判断的条件,这样代码就精简化了。

void SLCheckCapcity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!\n");
			return 1;
		}
		ps->a = tmp;
		ps->capacity = NewCapacity;
	}
}

用这个函数来实现空间是否足够
那么我们的头尾插就变得很简洁了。

//尾插
void SLpushBack(SL* ps,SLDataType x)
{
	assert(ps);
	//空间不足,扩容
	SLCheckCapcity(ps);
	ps->a[ps->size++] = x;
	
}
//头插
void SLpushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapcity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;
	ps->size++;
	
}

下面我们来运行看看
数据结构——顺序表的实现_第6张图片
也是没有问题的。

下面就是尾删了,尾删就比较简单了。
我们只需要吧ps->size–;就可以了,要解释原因的话,我们把ps->size–后有效的数据就减少了,那么后续的打印也打印不到减少了的元素,如果我们后续还要插入元素的话,直接覆盖就可以了,如果有同学想到把它赋值为0的话也是没有必要的,如果我们要插入的元素就是0呢?还要同学会想到free,足够更是没有必要。

//尾删
void SLpopFront(SL* ps)
{
	ps->size--;
}

下面我们来运行一下
数据结构——顺序表的实现_第7张图片

也是没问题的。
最后我们来实现头删吧
如果我们要实现头部的删除我们就要,我第一个值给覆盖了,画一个图。
数据结构——顺序表的实现_第8张图片
我们要把1给去掉,那么就要把后面的值覆盖前面的数,还是那个从前往后,和从后往前的问题。
在这里是哪个呢,答案是从前往后。为什么呢?如果我们从后往前的话,前面的值就被覆盖了,所以这里我们要用从前往后的思路来写代码。

//头删
void SLpopBack(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size-1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

我们来运行一下。
数据结构——顺序表的实现_第9张图片
看起来是不是没有什么问题可是如果我删多了呢?
那样的话ps->size岂不是变成负数了吗,
数据结构——顺序表的实现_第10张图片
为了避免这种情况我们还得再判断一下size是否为0,为0就不删了。

//头删
void SLpopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	for (int i = 0; i < ps->size-1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

数据结构——顺序表的实现_第11张图片
这样的话就可以避免ps->size减成负数了。
前面的头删我就忘记加了。大家写代码的时候要记得补上哦。
除了这些函数,还有指定位置的插入和删除,这里我就不写了,其实也大差不差,大家可以自己来补充这些函数。

代码

最后我把全部的代码都贴过来
Seqlist.h

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
typedef int SLDataType;
// 动态顺序表
typedef struct SeqList
{
	SLDataType* a;
	int size;
	int capacity;
}SL;

//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//尾插
void SLpushBack(SL* ps, SLDataType x);
//头插
void SLpushFront(SL* ps, SLDataType x);
//尾删
void SLpopBack(SL* ps);
//头删
void SLpopFront(SL* ps);
//打印
void SLPrint(SL* ps);
//判断空间是否足够
void SLCheckCapcity(SL* ps);

Seqlist.c

#define _CRT_SECURE_NO_WARNINGS
#include "Seqlist.h"
//初始化
void SLInit(SL* ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}
//销毁
void SLDestroy(SL* ps)
{
	if (ps->a != NULL)
	{
		free(ps->a);
		ps->a = NULL;
		ps->size = 0;
		ps->capacity = 0;
	}
}
void SLCheckCapcity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!\n");
			return 1;
		}
		ps->a = tmp;
		ps->capacity = NewCapacity;
	}
}
//尾插
void SLpushBack(SL* ps,SLDataType x)
{
	assert(ps);
	//空间不足,扩容
	SLCheckCapcity(ps);
	ps->a[ps->size++] = x;
	
}
//头插
void SLpushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapcity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;
	ps->size++;
	
}
void SLPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
//尾删
void SLpopBack(SL* ps)
{
	assert(ps);
	ps->size--;
}
//头删
void SLpopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	for (int i = 0; i < ps->size-1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

tset.c

#define _CRT_SECURE_NO_WARNINGS
#include "Seqlist.h"
void test1()
{
	SL sl;
	SLInit(&sl);
	SLpushBack(&sl, 1);
	SLpushBack(&sl, 2);
	SLpushBack(&sl, 3);
	SLpushBack(&sl, 4);
	SLPrint(&sl);
	SLpushFront(&sl, 4);
	SLpushFront(&sl, 5);
	SLPrint(&sl);
	/*SLpopBack(&sl);
	SLpopBack(&sl);
	SLpopBack(&sl);*/
	SLpopFront(&sl);
	SLpopFront(&sl);
	SLpopFront(&sl);
	SLpopFront(&sl);
	SLpopFront(&sl);
	SLpopFront(&sl);
	SLpopFront(&sl);
	SLpopFront(&sl);
	SLPrint(&sl);
	SLDestroy(&sl);
}
int main()
{
	test1();
	return 0;
}

你可能感兴趣的:(数据结构,算法,c语言,笔记)