1.概念及结构

顺序表就是数组,但是在数组的基础上,他还要求数据是连续存储的,不能跳跃间隔。 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改

代码演示

这里我们不做过多复述,直接上手写代码演示

这里我们先写一个结构体

静态方式存储数据

#pragma once 
#define N 1000
typedef int SLDataType;

//静态顺序表
typedef struct SeqList
{
	SLDataType a[N];
	int size;		//表示数组中存贮了多少个数据
}SL;

//接口函数 
void SeqListInit(SL* ps);
void SeqListPushBack(SL* ps, SLDataType x);
void SeqListPopBack(SL* ps);
void SeqListPushFront(SL* ps, SLDataType x);
void SeqListPopFront(SL* ps);

缺点--如果满了就不让插入 n给小了不够用,n给打了浪费 所以改成动态内存存储,来扩容,如果空间不够,可以增加新的空间,或者找一块可以容纳这些数据的空间,运用指针,不需要数组了


这里我们先将空间初始化

void SeqListInit(SL ps)
{
	ps.a = NULL;
	ps.capacity = ps.size = 0;
}
void TestSeqList1()
{
	SL sl;
	SeqLIstInit(sl);
}

int main()
{
	TestSeqList1();

	return 0;
}

捕获.PNG 捕获.PNG 这里我们打开监视发现sl并没有被初始化,只有函数里面形参的被初始化。这是因为,函数传参,形参是实参的拷贝,形参的改变不会影响实参 所以在这里,我们将参数改为指针,将地址传给形参,通过地址来改变实参的数据

void SeqListInit(SL* ps)
{
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}
void TestSeqList1()
{
	SL sl;
	SeqListInit(&sl);
}

尾插

初始化完成,下面开始我们的第一个操作,在结尾插入数据

void SeqListPushBack(SL* ps, SLDataType x)
{
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity*2;//这里有两种情况,如果开始的大小为0,也就是初始化,我们给他一个四个int的空间,如果已经有大小,且需要扩容,我们将大小扩大二倍,采用了三目运算符
//扩大几倍都可以,这里看我们自身的需求,二倍只是一个比较合适的大小
		SLDataType* tmp = realloc(ps->a, newcapacity*sizeof(SLDataType));//把扩容赋给tmp,看是否扩容成功,扩容成功再赋给a
//这里注意realloc后面的参数需要的是字节数大小,而不是扩容个数
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);//扩容失败,出现异常,直接退出
		}
		ps->a = tmp;
		ps->capacity = newcapacity;//将新的最大可用数赋给capacity
	}

	ps->a[ps->size] = x;//将要插入的数据插入到结尾
	ps->size++;//插入完成后,size要++,记录个数
}

写完尾插,我们可以写一个接口来打印插入数字 在SeqList.h声明

void SeqListPrint(SL* ps);

函数的实现,我们可以写一个循环,就能将插入数据逐个打印出来

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

打印数据之后,我们写一个接口来销毁数据,malloc的扩容需要free释放

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

尾删

尾插函数写完后,当然需要尾删函数,思路很简单,当我们指针指向最后一个时,直接将有效个数size减减,数据被删除

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

但是当我们在test.c 加入数据后

SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);

此时size的个数变为-2,我们需要判断一下,size大小是否合法,以便程序继续运行

void SeqListPopBack(SL* ps)
{
	/*
	//温柔的方法
	if (ps->size > 0)
	{
		ps->size--;
	}*/
	//暴力的方法
	assert(ps->size > 0);
	ps->size--;
}

前插

同样在前面插入数据,我们将前一个数据挪到后面,然后再在前面插入数据x,但是如果从前面开始覆盖数据,后面的数据都会被以前的数据覆盖,无法得到想要的数据覆盖到后面,所以从后面开始挪数据

void SeqListPushFront(SL* ps, SLDataType x)
{
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;
}

在前面插入数据时我们要面临一个问题就是,空间不够时需要扩容,这跟尾插一样,需要扩容,所以我们将扩容代码封装成一个函数可直接调用 SeqListCheckCapacity(SL* ps);

void SeqListCheckCapacity(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)
		{
			printf("realloc fail\n");
			exit(-1);
		}

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

头删

头删需要断言一下数据是否大于0个,然后开始删除,将后一个数据覆盖到前一个,如果我们从后面开始覆盖,则原来的数据被新数据覆盖,无法获得想要的数据,所以我们从前面开始向前覆盖,然后将指针begin++,然后有效数据个数减减

void SeqListPopFront(SL* ps)
{
	assert(ps->size > 0);

	int begin = 0;
	while (begin < ps->size)
	{
		ps->a[begin] = ps->a[begin + 1];
		++begin;
	}
	ps->size--;
}

我们在test.c中测试

void TestSeqList2()
{
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);

	SeqListPushFront(&sl, 10);
	SeqListPushFront(&sl, 20);
	SeqListPushFront(&sl, 30);
	SeqListPushFront(&sl, 40);
	SeqListPrint(&sl);

	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);

	SeqListDestory(&sl);

}

发现如果删除多个数据时它已经越界但是并没有出现错误,在写SeqLsitDestory()后错误出现 ,这是因为realloc扩容后,将空间释放掉会出现错误,所以我们在头删前断言一下有效数据size大小是否大于0个,避免错误出现

找到指定数据的位置

如果找到指定数据,找到了返回数据x的位置 函数声明

int SeqListFind(SL* ps, SLDataType x);

这里我们直接写一个循环,遍历一下整个数组,如果找到了返回数组的下标,也就是位置i,找不到返回-1。

intSeqListFind(SL* ps, SLDataType x)//查找指定位置
{
		for (int i = 0; i < ps->size; ++i)
		{
			if (x == ps->a[i])
			{
				return i;
			}	
		}
		return -1;
}

在指定位置插入数据

函数声明

void SeqListInsert(SL* ps, int pos, SLDataType x);

这个函数,我们知道pos是我们想要插入数据的位置,根据线性表的性质,线性表是连续的,但是他的位置可以在线性表最后插入一个数据,这样它的大小就是size,通常我们的下标是size-1,他可以插入到size的位置,但是他不能越界,所以pos的位置既不能超过size,也不能小于0。所以我们需要判断一下,我们可以写if条件句来判断,这是一种温柔的方式,但是它的报错不是显而易见的,我们也可以用assert断言,这种方式如果出错,直接报错,非常的nice。

void SeqListInsert(SL* ps, int pos, SLDataType x)
{
	/*
	温柔的方法
	if (pos > ps->size || pos < 0)
	{
		printf("pos invalid\n");
		return;
	}*/
	//暴力的方法
	assert(pos >= 0 && pos <= ps->size);
	SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end+1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

我们来测试一下

void SeqListTest5()
{
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListInsert(&sl, 2, 30);
	SeqListPrint(&sl);
SeqListDestory(&sl);
}

启动调试 捕获.PNG 成功了。 这里再次强调一下,在测试函数之后必须调SeqListDestory()函数: 第一、必须要保证内存用完之后正常释放 第二、如果free之后你有越界,它是可以帮助你检查 我们也可以根据前两个函数,结合运用一下,如果我们不知道指定数字的位置,我们可以用SeqListFind()函数查找.

int pos = SeqListFind(&sl, 4);
	if (pos != -1)
	{
		SeqListInsert(&sl, pos, 40);
SeqListPrint(&sl);
	}

捕获.PNG 写完SeqListInsert()函数,既然是可以在任意位置插入数据,那么我们不妨将头插和尾插函数也相应改变一下 头插

void SeqListPushFront(SL* ps, SLDataType x)
{
	SeqListInsert(ps, 0, x);
}

尾插

void SeqListPushBack(SL* ps, SLDataType x)
{
	SeqListInsert(ps, ps->size, x);
}

在指定位置删除数据

void SeqListErase(SL* ps, int pos)
{
	assert(pos>=0&&pos<ps->size);

	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}
	ps->size--;
}

测试一下

SeqListErase(&sl, 1);
	SeqListPrint(&sl);

捕获.PNG 测试之后发现测试成功 通过上述代码我们可以像前插,后插一样复用一下前删和后删 前删

void SeqListPopFront(SL* ps)
{
	SeqListErase(ps, 0);
}

后删

void SeqListPopBack(SL* ps)
{
	SeqListErase(ps, ps->size - 1);
}

测试一下

SeqListPopBack(&sl);
SeqListPopBack(&sl);
SeqListPopFront(&sl);
SeqListPopFront(&sl);
SeqListPrint(&sl);

捕获.PNG


我们来写一个菜单

建议不要一上来就写菜单,最好先写单元测试 等你把程序函数接口测试没问题,再写菜单,菜单不方便调试

void Menu()
{
	printf("***************************\n");
	printf("请选择你的操作:>\n");
	printf("1、头插  2、头删\n");
	printf("3、尾插  4、尾删\n");
	printf("5、打印  -1、退出\n");
	printf("***************************\n");
}

void MenuTest()
{
	SL sl;
	SeqListInit(&sl);
	int input = 0;
	int x;
	while (input != -1)
	{
		Menu();
		printf("请输入你要的选项:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入你要头插的数据,以-1结束:");
			scanf("%d", &x);
			while (x != -1)
			{
				SeqListPushFront(&sl,x);
				scanf("%d", &x);
			}
			break;
		case 2:
			SeqListPopFront(&sl);
			break;
		case 3:
			printf("请输入你要尾插的数据,以-1结束:");
			scanf("%d", &x);
			while (x != -1)
			{
				SeqListPushBack(&sl, x);
				scanf("%d", &x);
			}
			break;
		case 4:
			SeqListPopBack(&sl);
			break;
		case 5:
			SeqListPrint(&sl);
			break;
		default:
			printf("无此选项,请重新输入\n");
			break;
		}
	}
	SeqListDestory(&sl);
}

int main()
{
	MenuTest();

	return 0;
}

调试走起 捕获.PNG 这里将前面的代码给出 SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"

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

void SeqListPushBack(SL* ps, SLDataType x)
{
	////如果没有空间或空间不足,我们就扩容
	//SeqListCheckCapacity(ps);
	//ps->a[ps->size] = x;
	//ps->size++;

	SeqListInsert(ps, ps->size, x);
}

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

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

void SeqListCheckCapacity(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)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
}

void SeqListPopBack(SL* ps)
{
	///*if (ps->size > 0)
	//{
	//	ps->size--;
	//}*/
	//assert(ps->size > 0);
	//ps->size--;
	SeqListErase(ps, ps->size - 1);
}

void SeqListPushFront(SL* ps, SLDataType x)
{
	/*SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;*/

	SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SL* ps)
{
	/*assert(ps->size > 0);

	int begin = 0;
	while (begin < ps->size)
	{
		ps->a[begin] = ps->a[begin + 1];
		++begin;
	}
	ps->size--;*/
	SeqListErase(ps, 0);
}

int SeqListFind(SL* ps, SLDataType x)//查找指定位置
{
		for (int i = 0; i < ps->size; ++i)
		{
			if (x == ps->a[i])
			{
				return i;
			}	
		}
		return -1;
}

void SeqListInsert(SL* ps, int pos, SLDataType x)
{
	/*if (pos > ps->size || pos < 0)
	{
		printf("pos invalid\n");
		return;
	}*/
	assert(pos >= 0 && pos <= ps->size);
	SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end+1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

void SeqListErase(SL* ps, int pos)
{
	assert(pos>=0&&pos<ps->size);

	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}
	ps->size--;
}

SeqList.h

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

#pragma once 
#define N 1000
typedef int SLDataType;

////静态顺序表
//typedef struct SeqList
//{
//	SLDataType a[N];
//	int size;		//表示数组中存贮了多少个数据
//}SL;

typedef struct SeqList
{
	SLDataType* a;
	int size;		//表示数组中存贮了多少个数据
	int capacity;	//数组是技能存数据的空间容量是多大

}SL;

//接口函数 
void SeqListInit(SL* ps);
void SeqListPushBack(SL* ps, SLDataType x);
void SeqListPopBack(SL* ps);
void SeqListPushFront(SL* ps, SLDataType x);
void SeqListPopFront(SL* ps);

void SeqListPrint(SL* ps);
void SeqListDestory(SL* ps);
void SeqListCheckCapacity(SL* ps);

//找到了返回x位置
int SeqListFind(SL* ps, SLDataType x);
//指定pos下标位置插入
void SeqListInsert(SL* ps, int pos, SLDataType x);

//删除pos位置的数据
void SeqListErase(SL* ps, int pos);