数据结构02:万字详解顺序表附原码

目录

顺序表原码

SeqList.h头文件:

SeqList.c实现文件:

test.c测试文件:

1. 线性表

2.顺序表

2.1 概念与结构

2.2分类

2.2.1静态顺序表

2.2.2动态顺序表

2.3动态顺序表的实现

2.3.1初始化函数

2.3.2尾插函数

2.3.3头插函数

2.3.4尾删函数

2.3.5头删函数

2.3.6查找函数

2.3.7指定位置之前插入函数

2.3.8指定位置删除函数

2.3.8销毁函数


顺序表原码

SeqList.h头文件:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//定义动态顺序表
typedef int SLTDataType;

typedef struct SeqList
{
	SLTDataType* arr;//数组首元素地址
	int size;//有效数据个数
	int capacity;//空间容量
}SL;


//初始化函数
void SLInit(SL *ps);

//尾插函数
void SLPushBack(SL* ps, SLTDataType x);//向ps插入x数据

//头插函数
void SLPushFront(SL* ps, SLTDataType x);

//尾删函数
void SLPopBack(SL* ps);

//头删函数
void SLPopFront(SL* ps);

//打印函数
void SLPrint(SL* ps);

//查找函数
int SLFind(SL* ps, SLTDataType x);

//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLTDataType x);

//指定位置删除函数
void SLErase(SL* ps, int pos);

//销毁函数
void SLDestroy(SL* ps);

SeqList.c实现文件:

#include "SeqList.h"

//实现初始化函数
void SLInit(SL *ps)//初始化函数涉及修改实参的内容,因此需要传值调用
{
	ps->arr = NULL;
	ps->size = ps->capacity  = 0;
}

//实现销毁函数
void SLDestroy(SL* ps)
{
	if (ps->arr != NULL)
	{
		free(ps->arr);
	}
	ps->size = ps->capacity = 0;
}

//实现动态开辟内存空间函数
void SLCheckCapacity(SL* ps)
{
	//空间不够
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
		//如果初始空间为0则默认设定为4,如果空间不为0则2倍扩容

		//为什么选择2倍扩容,因为从概率论的角度来说,当你存入的数据越多的时候,则下一次所需要存入的数据也就越多
		//因此2倍扩容虽然会造成一定的空间浪费,但是利大于弊

		SLTDataType* tmp = (SLTDataType*)realloc(ps->arr, sizeof(int) * newcapacity);
		//不直接使用ps->arr,而是用一个中间变量tmp存储空间大小,是防止空间开辟失败导致原空间也损坏
		//另外realloc函数的第2个参数应该是字节大小,我们现在想要存入newcapacity个整形数据,而一个整形占4个字节,因此需要再*sizeof(int)

		if (tmp == NULL)
		{
			perror("realloc fail!");//如果空间开辟失败,则提示报错,退出程序
			exit(1);//非0值异常退出
		}

		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}


//实现尾插函数
void SLPushBack(SL* ps, SLTDataType x)
{
	assert(ps != NULL);
	//assert表达式如果为真,则继续执行,为假,则报错退出
	//防止传入空指针

	//空间不够
	SLCheckCapacity(ps);
	//空间足够
	ps->arr[ps->size] = x; 
	(ps->size)++;
}


//实现头插函数
void SLPushFront(SL* ps, SLTDataType x)
{
	assert(ps != NULL);
	//assert表达式如果为真,则继续执行,为假,则报错退出
	//防止传入空指针


	//空间不够
	SLCheckCapacity(ps);
	//空间足够
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
		//将现有的数据整体向后移动一位
	}
	ps->arr[0] = x;
	ps->size++;
}

//实现尾删函数
void SLPopBack(SL* ps)
{
	assert(ps != NULL);
	//防止传入空指针
	assert(ps->size != NULL);
	//防止顺序表为空
	ps->size--;
}

//实现头删函数
void SLPopFront(SL* ps)
{
	assert(ps && ps->size);
	//等价于:
	//assert(ps != NULL);
	//防止传入空指针
	//assert(ps->size != NULL);
	//防止顺序表为空
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}


//实现打印函数
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

//实现查找函数
int SLFind(SL* ps, SLTDataType x)//查找完返回对应的下标
{
	assert(ps != NULL);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;//未找到则返回一个小于0的数
}

//实现指定位置之前插入数据函数
void SLInsert(SL* ps, int pos, SLTDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	if (ps->size == ps->capacity)
	{
		//空间不足则扩容
		SLCheckCapacity(ps);
	}
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

//实现指定位置删除函数
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

test.c测试文件:

#include "SeqList.h"

void test01()
{
	SL s1;

	//测试初始化函数
	SLInit(&s1);

	////测试尾插函数
	//SLPushBack(&s1, 1);
	//SLPushBack(&s1, 2);
	//SLPushBack(&s1, 3);
	//SLPushBack(&s1, 4);
	//SLPushBack(&s1, 5);
	//SLPushBack(&s1, 6);
	//SLPushBack(&s1, 7);
	//SLPushBack(&s1, 8);

	////测试头插函数
	SLPushFront(&s1, 1);
	SLPushFront(&s1, 2);
	SLPushFront(&s1, 3);
	SLPushFront(&s1, 4);
	//SLPushFront(&s1, 5);
	//SLPushFront(&s1, 6);
	//SLPushFront(&s1, 7);
	//SLPushFront(&s1, 8);

	////测试尾删函数
	//SLPopBack(&s1);
	//SLPrint(&s1);
	//SLPopBack(&s1);
	//SLPrint(&s1);
	//SLPopBack(&s1);
	//SLPrint(&s1);

	//测试头删函数
	//SLPrint(&s1);
	//SLPopFront(&s1);
	//SLPrint(&s1);
	//SLPopFront(&s1);
	//SLPrint(&s1);
	//SLPopFront(&s1);
	//SLPrint(&s1);


	////测试查找函数
	//int pos = SLFind(&s1, 3);
	//if (pos >= 0)
	//{
	//	printf("找到了,下标为:%d\n", pos);
	//}
	//else
	//{
	//	printf("未找到\n");
	//}

	////测试在指定位置之前插入数据函数
	//int pos = SLFind(&s1, 1);
	////在数值为1的位置之前插入100
	//SLInsert(&s1, pos, 100);
	//SLPrint(&s1);

	////测试在指定位置删除函数
	//int pos = SLFind(&s1, 1);
	//SLErase(&s1, pos);
	//SLPrint(&s1);

	//测试销毁函数
	SLDestroy(&s1);
}

int main()
{
	test01();
	return 0;
}

1. 线性表

        线性表(linearlist)是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中⼴泛使⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性 表在物理上存储时,通常以数组和链式结构的形式存储。

        逻辑结构,简单来说就是人们想象的结构,例如正在排队的人群,人们整齐的排在一起,我们往往会将他们抽象成一条直线,但是他们事实并不是直线,而是一个一个的点。        

        物理结构,这里说的其实是数据的存储形式,如果数据存储的连续的,一个接一个的存储,例如数组,数组中的元素是连续存储在内存中的,我通过数组首元素的地址就可以依次找到数组中全部元素。

2.顺序表

2.1 概念与结构

        概念:顺序表是⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采⽤数组 存储。

        顺序表也是线性表的一种,线性表在逻辑结构上是连续的,物理结构上不一定是连续的,而顺序表在逻辑结构上是连续的,物理结构上同样是连续的。为什么物理结构是连续的呢?因为顺序表的底层结构是用数组实现的。

        啥叫顺序表的底层结构是用数组实现的,意思就是说顺序表存储数据都是在数组里存储数据,对数据进行增删改查也是对数组进行相应的操作。

        顺序表和数组的关系是啥?其实顺序表就是将数组进行了精美的包装,举个不太恰当的例子,就像一道普通的水煮白菜只是一道简单的菜,但是国宴级别的水煮白菜就不可能单单只是一道白菜,它一定是经过多道工序多种食材包装而成的。顺序表就是这样,它的底层是数组,但是不单单只是数组,还包含了对数组增删改查的一系列操作。

2.2分类

        顺序表的底层是数组,如果我们明确这个数组的空间大小,在编译的时候就可以设置对应的值。如果我们不确定我们这个数组的空间大小,就可以动态申请我们的内存空间,当我们需要的时候再开辟空间。这样就将顺序表分为了静态顺序表和动态顺序表。

2.2.1静态顺序表

        当我们使用静态顺序表的时候,初始数组中肯定是没有数据的,当我们往里面插入数据的时候,数组中的有效数据才会增长,接着数据插着插着肯定会有插满的时候,什么时候插满呢?这就要根据我们的空间容量来判断。通过上面的分析,我们可以知道要创建一个静态顺序表需要一个定长的数组a[N],因为是定长数组,所以最大容量就是N,知道有效数据个数size,这就要求我们使用一个结构将这3个信息存放在一起,这就会用到我们的结构体struct。

#define N 7
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType a[N];
	int size;
}SL;

        在上述代码中我们定义了SeqList这个静态顺序表,宏定义N是为了方便对数组的长度进行修改,重命名int是为了控制数组中的数据类型,当我们的代码量达到一定程度是方便批量修改数组的数据类型,而避免修改整个工程中的int。

2.2.2动态顺序表

        动态顺序表和静态顺序表的区别在于动态顺序表是我们需要存储数据的时候我们才向内存申请空间,也就是说我们初始情况下是不知道我们的空间大小的,当我们后面要插入数据的时候,我们才申请对应大小的空间。因为一次可能会申请多个空间,就会导致数组的空间容量和实际的有效数据个数不一样,这就要求我们定义一个capacity变量来实时记录空间容量。

//定义动态顺序表
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;
	int size;
	int capacity;
}SL;

        静态顺序表和动态顺序表那个更好呢?这都要根据具体的场景来使用,如果我们明确了只需要一定的空间大小,比方说只需要存储10个元素,那么我们就可以使用静态顺序表开辟10个元素的内存空间,这样就不会有空间浪费。但是一般情况下,我们存储的数据很少给定了要存储多少数据,开辟多少空间,因此动态顺序表的使用场景更加广泛,所以接下来我们主要讲解的是动态顺序表。

2.3动态顺序表的实现

        我们要创建动态顺序表需要下面这3个文件,SeqList.h头文件,将所需要的头文件以及函数声明都包含在这个文件中,这样其他文件只需要引用SeqList.h这个头文件就够了。SeqList.c文件用于实现对应的函数。test.c测试文件,我们写完对应的函数就需要进行测试,test.c文件可以通过SeqList.h头文件的函数声明找到在SeqList.c文件中的函数定义,实现调用对应函数。

2.3.1初始化函数

        在使用动态顺序表之前我们需要对其进行初始化,初始的动态顺序表没有指向的空间就将arr指针指向NULL,有效数据个数和空间容量都定为0。

        SeqList.h头文件:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//定义动态顺序表
typedef int SLTDataType;

typedef struct SeqList
{
	SLTDataType* arr;//数组首元素地址
	int size;//有效数据个数
	int capacity;//空间容量
}SL;

//初始化函数
void SLInit(SL *ps);

        SeqList.c实现文件:

#include "SeqList.h"

//实现初始化函数
void SLInit(SL *ps)//初始化函数涉及修改实参的内容,因此需要传值调用
{
	ps->arr = NULL;
	ps->size = ps->capacity  = 0;
}

        test.c测试文件:

//测试初始化函数
void test01()
{
	SL s1;
	SLInit(&s1);
}
int main()
{
	test01();
	return 0;
}

        这里需要注意的就是初始化函数需要对传递的实参进行操作,因此需要进行传值调用,传递的是实参的地址,这样才能对实例化的s1进行修改,如果是传值调用,则初始化函数接收到的s1其实是实参的一份临时拷贝,对这份临时拷贝的操作是无法影响到实参的。

        所以,之后再遇到要对实例化后的对象进行修改时,就要想到我们应该使用传值调用。

2.3.2尾插函数

        往数据表中插入元素既可以头部插入,也可以尾部插入,我们这里先讲尾部插入。

         SeqList.h头文件:

//尾插函数
void SLPushBack(SL* ps, SLTDataType x);//向ps插入x数据

        SeqList.c实现文件:

//实现尾插函数
void SLPushBack(SL* ps, SLTDataType x)
{
    assert(ps != NULL);
    //assert表达式如果为真,则继续执行,为假,则报错退出
    //防止传入空指针

	//空间不够
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
		//如果初始空间为0则默认设定为4,如果空间不为0则2倍扩容

		//为什么选择2倍扩容,因为从概率论的角度来说,
        //当你存入的数据越多的时候,则下一次所需要存入的数据也就越多
		//因此2倍扩容虽然会造成一定的空间浪费,但是利大于弊

		SLTDataType* tmp = (SLTDataType*)realloc(ps->arr, sizeof(int)*newcapacity);
		//不直接使用ps->arr,而是用一个中间变量tmp存储空间大小,
        //是防止空间开辟失败导致原空间也损坏
		//另外realloc函数的第2个参数应该是字节大小,
        //我们现在想要存入newcapacity个整形数据,
        //而一个整形占4个字节,因此需要再*sizeof(int)

		if (tmp == NULL)
		{
			perror("realloc fail!");//如果空间开辟失败,则提示报错,退出程序
			exit(1);//非0值异常退出
		}

		ps->arr = tmp;
		ps->capacity = newcapacity;

	}

	//空间足够
	ps->arr[ps->size] = x; 
	(ps->size)++;

}

        test.c测试文件:

void test01()
{
	SL s1;

	//测试初始化函数
	SLInit(&s1);

	//测试尾插函数
	SLPushBack(&s1, 1);
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPushBack(&s1, 5);
	SLPushBack(&s1, 6);
	SLPushBack(&s1, 7);
	SLPushBack(&s1, 8);
}

        这里最需要说的就是如何实现尾插函数的代码,我们需要用图来了解,往顺序表中插入数据分为2种情况,第1种较为简单的情况就是数组中有效数据个数没有满,只需要在数组尾端继续插入就行,示意图如下:

        图中已经存放了4个有效数据,所以size就等于4,这时候插入数据只需要向arr数组中下标为size的位置插入,接着size++就行。

        第2种情况就是数组已经满了,如下图,这时有效数据个数就等于空间容量,需要我们额外开辟空间,接着就是按照空间容量未满时插入数据就行。开辟空间的注意事项详见代码注释。另外,我们调用尾插函数需要提供一个地址,但是如果我们给一个NULL空指针还能正常插入数据吗?当然是不可以的,因为尾插函数中涉及到对指针进行解引用操作,而NULL空指针是无法解引用的,因此,程序会异常退出。所以尾插函数一进来就需要判断传入的指针是否为空,为空则报错退出。

2.3.3头插函数

        讲完了尾部插入,接下来我们讲头部插入。

        怎么实现头插函数呢?这个会比尾插函数稍微复杂一点点,我们用图像来理解。

        当空间容量足够的时候,我们可以让现有的数据整体向后移动一位,即最后一个数据移动到下标为size的位置,之前的数据依次向后移动,最后size++,这样第一位即下标为0的这一位就空了出来,用于插入新的数据。

	//空间足够
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
		//将现有的数据整体向后移动一位
	}
	ps->arr[0] = x;
    ps->size++;

        当空间不够时,就增容,再进行空间足够时的操作就行。

         SeqList.h头文件:

void SLPushFront(SL* ps, SLTDataType x);

        SeqList.c实现文件:

//实现头插函数
void SLPushFront(SL* ps, SLTDataType x)
{
    assert(ps != NULL);
	//assert表达式如果为真,则继续执行,为假,则报错退出
	//防止传入空指针

	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLTDataType* tmp = (SLTDataType*)realloc(ps->arr, sizeof(int) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	//空间足够
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
		//将现有的数据整体向后移动一位
	}
	ps->arr[0] = x;
	ps->size++;
}

        test.c测试文件:

void test01()
{
	SL s1;

	//测试初始化函数
	SLInit(&s1);

	//测试头插函数
	SLPushFront(&s1, 1);
	SLPushFront(&s1, 2);
	SLPushFront(&s1, 3);
	SLPushFront(&s1, 4);
	SLPushFront(&s1, 5);
	SLPushFront(&s1, 6);
	SLPushFront(&s1, 7);
	SLPushFront(&s1, 8);
}

        这里我们可以看到,无论是头插还是尾插,当空间不足时,都需要动态开辟内存空间,而且二者开辟空间的代码都是一样的,因此我们可以将动态开辟内存空间这部分的代码单独封装承一个函数,叫做void SLCheckCapacity(SL* ps)。

//实现动态开辟内存空间函数
void SLCheckCapacity(SL* ps)
{
	//空间不够
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
		//如果初始空间为0则默认设定为4,如果空间不为0则2倍扩容

		//为什么选择2倍扩容,因为从概率论的角度来说,
        //当你存入的数据越多的时候,则下一次所需要存入的数据也就越多
		//因此2倍扩容虽然会造成一定的空间浪费,但是利大于弊

		SLTDataType* tmp = (SLTDataType*)realloc(ps->arr, sizeof(int)*newcapacity);
		//不直接使用ps->arr,而是用一个中间变量tmp存储空间大小,
        //是防止空间开辟失败导致原空间也损坏
		//另外realloc函数的第2个参数应该是字节大小,
        //我们现在想要存入newcapacity个整形数据,
        //而一个整形占4个字节,因此需要再*sizeof(int)

		if (tmp == NULL)
		{
			perror("realloc fail!");//如果空间开辟失败,则提示报错,退出程序
			exit(1);//非0值异常退出
		}

		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}

        因此,修改过后的尾插函数与头插函数的代码分别为:

//实现尾插函数
void SLPushBack(SL* ps, SLTDataType x)
{
	assert(ps != NULL);
	//assert表达式如果为真,则继续执行,为假,则报错退出
	//防止传入空指针

	//空间不够
	SLCheckCapacity(ps);
	//空间足够
	ps->arr[ps->size] = x; 
	(ps->size)++;
}
//实现头插函数
void SLPushFront(SL* ps, SLTDataType x)
{
	assert(ps != NULL);
	//assert表达式如果为真,则继续执行,为假,则报错退出
	//防止传入空指针

	//空间不够
	SLCheckCapacity(ps);
	//空间足够
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
		//将现有的数据整体向后移动一位
	}
	ps->arr[0] = x;
	ps->size++;
}

2.3.4尾删函数

        既然我们可以选择头部、尾部插入数据,自然也可以选择头部、尾部删除数据,我们先来讲尾部删除数据。

        要我们删除数据首先得是我们顺序表中有数据才行,因此,当顺序表为空,即size不等于0。那么删除数据我们应该如何删除呢?是通过free函数释放空间呢?还是用一个特殊值来代替呢?如果使用free函数释放可不可行?不行,为什么?因为顺序表的底层是数组,数组的内存空间是连续的,在这个连续的空间里是不能只释放一个空间的,要释放数组的空间只能一起释放,因此free函数不行。那特殊值呢?也不行,为什么?因为比起我们费尽心思找一个特征值来代替,我们还有更简洁的方式,即直接size--。因为size表示的是数组中有效数据个数,size--则最后面的数据就直接不用管了,将它当成随机数就行。

        为了方便测试时在屏幕上打印顺序表中的值,我们可以写一个SLPrint(SL* ps)函数用于打印顺序表中的值,代码如下:

        SeqList.h头文件:

//尾删函数
void SLPopBack(SL* ps);

        SeqList.c实现文件:

//实现尾删函数
void SLPopBack(SL* ps)
{
	assert(ps != NULL);
	//防止传入空指针
	assert(ps->size != NULL);
	//防止顺序表为空
	ps->size--;
}

        test.c测试文件:

void test01()
{
	SL s1;

	//测试初始化函数
	SLInit(&s1);

	////测试头插函数
	SLPushFront(&s1, 1);
	SLPushFront(&s1, 2);
	SLPushFront(&s1, 3);
	SLPushFront(&s1, 4);

	//测试尾删函数
	SLPrint(&s1);
	SLPopBack(&s1);
	SLPrint(&s1);
	SLPopBack(&s1);
	SLPrint(&s1);
	SLPopBack(&s1);
	SLPrint(&s1);
}

2.3.5头删函数

        既然写完了尾删函数,那么接下来就是我们的头删函数了,尾删函数是通过size--将最后的数据舍掉,那么头删函数应该怎么删呢?头部数据删除,要求我们删除第一个数据,那我们是不是可以将现有的数据整体向前挪动一位,这样第一个数据就溢出了,自然就删除了。我们可以通过图像来理解。

        我们让 i 初始值为数组下标为0的位置,然后依次将 i+1 的值赋值给 i ,当i = size - 1时就停止赋值,最后size--。

        SeqList.h头文件:

//头删函数
void SLPopFront(SL* ps);

        SeqList.c实现文件:

//实现头删函数
void SLPopFront(SL* ps)
{
	assert(ps && ps->size);
	//等价于:
	//assert(ps != NULL);
	//防止传入空指针
	//assert(ps->size != NULL);
	//防止顺序表为空
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

        test.c测试文件:

void test01()
{
	SL s1;

	//测试初始化函数
	SLInit(&s1);

	////测试头插函数
	SLPushFront(&s1, 1);
	SLPushFront(&s1, 2);
	SLPushFront(&s1, 3);
	SLPushFront(&s1, 4);

	//测试头删函数
    SLPrint(&s1);
	SLPopFront(&s1);
	SLPrint(&s1);
	SLPopFront(&s1);
	SLPrint(&s1);
	SLPopFront(&s1);
	SLPrint(&s1);
}

2.3.6查找函数

        当我们想要查找顺序表中的某个值时,我们可以单独封装一个查找函数,查找到了就返回对应值的下标,未查找到则返回-1。

        SeqList.h头文件:

//查找函数
int SLFind(SL* ps, SLTDataType x);

         SeqList.c实现文件:

//实现查找函数
int SLFind(SL* ps, SLTDataType x)//查找完返回对应的下标
{
	assert(ps != NULL);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;//未找到则返回一个小于0的数
}

        test.c测试文件:

	//测试查找函数
	int pos = SLFind(&s1, 3);
	if (pos >= 0)
	{
		printf("找到了,下标为:%d\n", pos);
	}
	else
	{
		printf("未找到\n");
	}

2.3.7指定位置之前插入函数

        有了查找函数我们就可以在顺序表中找到我们想要查找的元素,那自然可以实现在指定元素位置之前插入数据以及删除数据了,我们先讲在指定位置之前插入数据函数方法。

        想要插入数据,则我们传入的指针不能为空,另外对于调用查找函数返回的下标也有要求,必须得是大于等于0且小于我们有效元素个数size的。那么接下来的实现我们用图形来理解。

        现在假设我们想要在值为1之前插入数据,则pos为1,在插入数据之前,必须要判断顺序表中空间是否足够,如果不够需要调用扩容函数,接着我们让 i 初始化为size,循环将 i-1的值放到 i 的位置上,直到 i 等于pos的时候跳出循环,然后插入数据,最后size++。

        SeqList.h头文件:

//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLTDataType x);

         SeqList.c实现文件:

//实现指定位置之前插入数据函数
void SLInsert(SL* ps, int pos, SLTDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	if (ps->size == ps->capacity)
	{
		//空间不足则扩容
		SLCheckCapacity(ps);
	}
	for (int i = ps->size; i < pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

        test.c测试文件:

	//测试在指定位置之前插入数据函数
	int pos = SLFind(&s1, 1);
	//在数值为1的位置之前插入100
	SLInsert(&s1, pos, 100);
	SLPrint(&s1);

2.3.8指定位置删除函数

        我们能够指定位置插入数据,自然可以指定位置删除数据,现在我们来实现该函数,先用图像理解一下。

        我们想要删除数据1,pos存储了数据1的下标,我们初始时将 i 等于pos,依次将 i+1 的值赋给i,接着i++,当 i 等于size-1时,挑出循环。

        SeqList.h头文件:

//指定位置删除函数
void SLErase(SL* ps, int pos);

         SeqList.c实现文件:

//实现指定位置删除函数
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

        test.c测试文件:

	//测试在指定位置删除函数
	int pos = SLFind(&s1, 1);
	SLErase(&s1, pos);
	SLPrint(&s1);

2.3.8销毁函数

        当我们使用完顺序表过后,应该顺序表是realloc动态开辟的空间,使用完后需要我们自行释放,有借有还再见不难。

        SeqList.h头文件:

//销毁函数
void SLDestroy(SL* ps);

         SeqList.c实现文件:

//实现销毁函数
void SLDestroy(SL* ps)
{
	if (ps->arr != NULL)
	{
		free(ps->arr);
	}
	ps->size = ps->capacity = 0;
}

        test.c测试文件:

	//测试销毁函数
	SLDestroy(&s1);

        好了,截至目前为止我们的顺序表相关的函数实现已经基本写完了,下一次我们将使用顺序表来解决一些实际问题,我们下次再见。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值