DS线性表之顺序表

前言 

上一期我们介绍了什么是数据结构和算法,以及介绍了算法效率问题即什么是时空复杂度~和时空复杂度的计算方式以及常见的时空复杂度的例题详解,本期我们来介绍一下线性表中的顺序表~!

本期内容介绍

什么是线性表?

顺序表的概念以及分类

静态顺序表的实现

动态顺序表的实现

顺序表存在问题

目录

前言 

本期内容介绍

一、什么是线性表?

二、顺序表

顺序表的概念以及结构分类

三、静态顺序表的实现

静态顺序表的申明

静态顺序表的初始化

静态顺序表的销毁

静态顺序表的打印

静态顺序表的尾插

静态顺序表的尾删

静态顺序表的头插

静态顺序表的头删

静态顺序表的排序

静态顺序表在pos位置插入

静态顺序表在pos位置删除

静态顺序表查找指定元素x

静态顺序表中修改指定pos位置的元素

全部源码:

四、动态顺序表的实现

动态顺序表的申明

动态顺序表的初始化

动态顺序表的销毁

动态顺序表的打印

动态顺序表的尾插

动态顺序表的尾删

动态顺序表的头插

动态顺序表的头删

动态顺序表的排序

动态顺序表在pos位置插入

动态顺序表在pos位置删除

动态顺序表中查找指定元素x

动态顺序表中修改指定pos位置的元素

全部源码:

五、顺序表的问题


一、什么是线性表?

线性表(Linear List)是N个具有相同特性的数据元素的集合!线性表在逻辑上是线性结构,即是一条连续的线,但在其物理结构上是不一定连续的~!常见的线性表有:顺序表、链表、栈、队列、串等

为什么说是逻辑结构上是连续的,物理结构上是不一定连续的呢?我们在C语言自定义类型详解的那一期介绍过一个东西叫结构体的自引用,我们当时说过他就是链表,他的开辟在堆上要动态开辟(由操作系统随机分配)~!所以整个链表可能不是连续的!而在当时写通讯录的时候他那个实际上就是顺序表!我在来画个图解释一下上面这句话:

DS线性表之顺序表_第1张图片

DS线性表之顺序表_第2张图片

二、顺序表

顺序表的概念以及结构分类

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构!一般情况下使用数组存储,在数组上进行各种运算即增删查改(通过各个接口)!

顺序表可以分为:静态顺序表和动态顺序表

静态顺序表:存储数据的个数是固定的

动态顺序表:存储数据的个数不是固定的不够了可以扩容

OK,我们各写一个来look一look:

静态顺序表:

#define MAX 10
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType data[MAX];
	int size;//顺序表的元素个数
}SeqList;

动态顺序表:

typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* data;
	int size;//顺序表的元素个数
	int capacity;//顺序表的容量
}SeqList;

这里你可能还有一些疑问,例如不就是在数组上增删查改吗?我直接在数组上进行操作不就行啦吗?OK,那你在进行操作的时候你如何知道你的数组中目前有几个元素?用什么判定???好像没有办法吧!你只能在定义一个变量来记录他当当中的元素个数,那这样为什么不直接用顺序表呢???对吧,一步到位,你只管操作其他不用担心~!另外还有就是:顺序表的存储一定是连续的,你只能挨着存而数组不一定你可以在不同位置存!!!!

三、静态顺序表的实现

OK,我们还是采用多文件编程:申明等都放在.h文件,实现在.c文件,测试在test.c中

一开始我们得先有一个顺序表吧:

静态顺序表的申明

#define MAX 15
typedef int SeqDataType;

typedef struct SeqList
{
	SeqDataType data[MAX];
	int size;
}SL;

静态顺序表的初始化

这里它的底层是个数组嘛!我们可以用个循环做,也可以直接用memset来初始化,我们一开始把数组中的每个元素都置0,size也置0;

//初始化
void SLInit(SL* ps)
{
	assert(ps);

	memset(ps->data, 0, sizeof(int) * MAX);
	ps->size = 0;
}

这里你可能会想为什么形参要用指针呢?这里还是那个问题:形参和实参的问题,形参的改变不影响实参!要在外部改变实参则要要传指针!

静态顺序表的销毁

有初始化必有销毁嘛~!那这个静态顺序表如何销毁呢?由于它的开辟在栈上,不能用free所以我们只需要把它的数组内容置0,size置0即可~!(size置0也可以,不要让他访问了),这里直接调用初始化即可:

//销毁
void SLDestory(SL* ps)
{
	assert(ps);
	SLInit(ps);
}

为了后续的直接的看结果我们在这里可以再来实现一个打印函数:

静态顺序表的打印

//打印
void SLPrint(const SL* ps)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->data[i]);
	}

	printf("\n");
}

静态顺序表的尾插

尾插就比较简单了,因为size是记录数组的有效元素的个数的,在数组中加一个元素size就会++一下,所以size是下一个元素的下标,所以我么只需要在size位置插入即可,但由于存储的大小是固定的,你在插入前得判断能不能插万一顺序表已经满了,你还插什么插!

//尾插
void SLPushBack(SL* ps, SeqDataType x)
{
	assert(ps);
	
	if (ps->size == MAX)
	{
		printf("顺序表已满无法插入!\n");
		exit(-1);
	}
	//assert(MAX == ps->size);

	ps->data[ps->size] = x;
	ps->size++;
}

当然这里的判断满的情况也有两种,"温柔的检查"和"暴力检查",其实我个人比较喜欢暴力检查的,满了的话直接结束程序,而且我们前面也介绍过assert断言失败也会告诉你在哪个文件的第几行出问题了~!当然要是讲究人性化的话,if是更好的~!这里都可以!

静态顺序表的尾删

尾删和尾插一样简单,由于你访问的时候是小于size的所以你只需要,让size--,此时在访问就访问不到最后一个元素了~!这也就相当于尾删了~!当然你也要在删除时判断,如果顺序表已经为空了,你还删除、、、这好像不合理吧~!所以当顺序表为空时就不要删了,那什么时候是顺序表为空呢?我们说过size是下一个元素的下标,当下一个元素的下标为0时,就说明当前元素的下表为-1,但下标不可能为-1所以就只能说明当前有效元素的个数为0,size没有++,所以当szie == 0时就说明顺序表一空不能删除~!

void SLPopBack(SL* ps)
{
	assert(ps);

	if (ps->size  == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}

	ps->size--;
}

当然这里也可以用暴力检查~!

OK,我们还是养成好习惯,写两个测试两个:
DS线性表之顺序表_第3张图片

DS线性表之顺序表_第4张图片

尾插没有问题,再来看看尾删:

DS线性表之顺序表_第5张图片

静态顺序表的头插

既然是插入那就要考虑是否已经是满的情况,如果是满的情况直接退出不然插入~!

//头插
void SLPushFront(SL* ps, SeqDataType x)
{
	assert(ps);

	if (ps->size == MAX)
	{
		printf("顺序表已满无法插入!\n");
		exit(-1);
	}

	int i = 0;
	for (i = ps->size; i > 0; i--)
	{
		ps->data[i] = ps->data[i - 1];
	}

	ps->data[i] = x;
	ps->size++;
}

静态顺序表的头删

删除的时候和以前一样一定要考虑顺序表已经为空的情况~!为空直接退出不删了~!

//头删
void SLPopFront(SL* ps)
{
	assert(ps);

	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}
	//assert(ps->size);

	for (int i = 0; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i+1];
	}

	ps->size--;
}

OK,还是来测试一下:

DS线性表之顺序表_第6张图片

DS线性表之顺序表_第7张图片

OK,头插头删是没有问题了~!

静态顺序表的排序

我们这里的排序采用qsort当然用其他的排序也可以~!

int cmp(const void* a, const void* b)
{
	return *(int*)a - *(int*)b;
}

//排序
void SLSort(SL* ps)
{
	assert(ps);
	assert(ps->size);

	qsort(ps->data, ps->size, sizeof(int), cmp);
}

静态顺序表在pos位置插入

插入必须判断是否已经满的情况,这里还有注意pos的合理性,因为pos是位置不是下标所以pos和数组下标差1(如下图),如果pos的位置小于1,顺序表的位置是从1开始的怎么可能小于1?不合理直接退出,pos大于或等于顺序表的最大长度,这也不合理吧~!顺序表的位置和数组的下标差1,你都大于等于最大下标了(假设满了无法插入了,即使不满顺序表也不让随意插入)

DS线性表之顺序表_第8张图片

//在pos位置插入x
void SLInsert(SL* ps, int pos, SeqDataType x)
{
	assert(ps);

	if (pos < 1 || pos >= MAX || ps->size >= MAX)
	{
		printf("pos非法或顺序表已满!\n");
		exit(-1);
	}

	int i = 0;
	for (i = ps->size; i >= pos; i--)
	{
		ps->data[i] = ps->data[i-1];
	}
	ps->data[pos-1] = x;
	ps->size++;
}

静态顺序表在pos位置删除

删除必须判断是否已经是空的情况~!以及和上面一样pos不可能是小于1的,删除顺序表之外的东西直接退出,还有就是所删的位置是数组和法但不是顺序表的有效数据区这也不能删除~!

//删除pos位置
void SLErase(SL* ps, int pos)
{
	assert(ps);

	
	if (ps->size == 0 || pos < 1 || pos >= MAX || pos >= ps->size)
	{
		printf("pos非法或顺序表为空无法删除!\n");
		exit(-1);
	}

	for (int i = pos-1; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i + 1];
	}

	ps->size--;
}

OK,还是来测试一把:

DS线性表之顺序表_第9张图片

OK,排序以及在pos位置插入删除都正常~!我们下来在写几个:

静态顺序表查找指定元素x

这个接口就实现起来很简单了,只需要遍历一遍数组即可(在没有排序的情况下),如果是排了序的情况直接二分查找效率最高~!找到返回下标,找不到返回-1


//查找指定元素x
int SLFind(SL* ps, SeqDataType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (x == ps->data[i])
			return i;
	}

	return -1;
}

静态顺序表中修改指定pos位置的元素

这里要考虑好边界即可,你不能超过最大存储范围也不能超过顺序表的有效数据范围,以及pos本身的合法性~!

//修改顺序表pos位置的元素
void SLModif(SL* ps, int pos, SeqDataType x)
{
	assert(ps);

	if (pos >= 1 && pos <= MAX && pos <= ps->size)
	{
		ps->data[pos - 1] = x;
	}
}

OK,测试一下:

DS线性表之顺序表_第10张图片

全部源码:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include 
#include 
#include 
#include 

#define MAX 5
typedef int SeqDataType;

typedef struct SeqList
{
	SeqDataType data[MAX];
	int size;
}SL;

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

//销毁
void SLDestory(SL* ps);

//打印
void SLPrint(const SL* ps);

//尾插
void SLPushBack(SL* ps, SeqDataType x);

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

//头插
void SLPushFront(SL* ps, SeqDataType x);

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

//排序
void SLSort(SL* ps);

//在pos位置插入x
void SLInsert(SL* ps, int pos, SeqDataType x);

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

//查找指定元素x
int SLFind(SL* ps, SeqDataType x);

//修改顺序表pos位置的元素
void SLModif(SL* ps,int pos, SeqDataType x);
#include "SeqList.h"

//初始化
void SLInit(SL* ps)
{
	assert(ps);

	memset(ps->data, 0, sizeof(int) * MAX);
	ps->size = 0;
}

//销毁
void SLDestory(SL* ps)
{
	assert(ps);
	SLInit(ps);
}

//打印
void SLPrint(const SL* ps)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->data[i]);
	}

	printf("\n");
}


//尾插
void SLPushBack(SL* ps, SeqDataType x)
{
	assert(ps);
	
	if (ps->size == MAX)
	{
		printf("顺序表已满无法插入!\n");
		exit(-1);
	}
	//assert(MAX == ps->size);

	ps->data[ps->size] = x;
	ps->size++;
}


//尾删
void SLPopBack(SL* ps)
{
	assert(ps);

	if (ps->size  == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}

	ps->size--;
}


//头插
void SLPushFront(SL* ps, SeqDataType x)
{
	assert(ps);

	if (ps->size == MAX)
	{
		printf("顺序表已满无法插入!\n");
		exit(-1);
	}

	int i = 0;
	for (i = ps->size; i > 0; i--)
	{
		ps->data[i] = ps->data[i - 1];
	}

	ps->data[i] = x;
	ps->size++;
}

//头删
void SLPopFront(SL* ps)
{
	assert(ps);

	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}
	//assert(ps->size);

	for (int i = 0; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i+1];
	}

	ps->size--;
}


int cmp(const void* a, const void* b)
{
	return *(int*)a - *(int*)b;
}

//排序
void SLSort(SL* ps)
{
	assert(ps);
	assert(ps->size);

	qsort(ps->data, ps->size, sizeof(int), cmp);
}


//在pos位置插入x
void SLInsert(SL* ps, int pos, SeqDataType x)
{
	assert(ps);

	if (pos < 1 || pos >= MAX || ps->size >= MAX)
	{
		printf("pos非法或顺序表已满!\n");
		exit(-1);
	}

	int i = 0;
	for (i = ps->size; i >= pos; i--)
	{
		ps->data[i] = ps->data[i-1];
	}
	ps->data[pos-1] = x;
	ps->size++;
}

//删除pos位置
void SLErase(SL* ps, int pos)
{
	assert(ps);

	
	if (ps->size == 0 || pos < 1 || pos >= MAX || pos >= ps->size)
	{
		printf("pos非法或顺序表为空无法删除!\n");
		exit(-1);
	}

	for (int i = pos-1; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i + 1];
	}

	ps->size--;
}


//查找指定元素x
int SLFind(SL* ps, SeqDataType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (x == ps->data[i])
			return i;
	}

	return -1;
}


//修改顺序表pos位置的元素
void SLModif(SL* ps, int pos, SeqDataType x)
{
	assert(ps);

	if (pos >= 1 && pos <= MAX && pos <= ps->size)
	{
		ps->data[pos - 1] = x;
	}
}
#include "SeqList.h"

//测试尾插、尾删以及初始化、销毁、打印
void test1()
{
	SL s;
	SLInit(&s);

	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPrint(&s);
	SLPushBack(&s, 5);
	SLPrint(&s);

	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);

	//检查是否满的时候插入是否有Bug
	SLPopBack(&s);
	SLPrint(&s);

	SLDestory(&s);
}

//测试头插、头删
void test2()
{
	SL s;
	SLInit(&s);

	SLPushFront(&s, 1);
	SLPushFront(&s, 2);
	SLPushFront(&s, 3);
	SLPushFront(&s, 5);
	SLPushFront(&s, 6);
	SLPrint(&s);

	//检查是在满的情况下是否有Bug
	//SLPushFront(&s, 7);
	//SLPrint(&s);

	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);

	//检查是否空的时候删除是否有Bug
	SLPopFront(&s);
	SLPrint(&s);

	SLDestory(&s);
}

//测试排序、在pos位置插入、删除pos位置
void test3()
{
	SL s;
	SLInit(&s);

	SLPushFront(&s, 1);
	SLPushFront(&s, 2);
	SLPushFront(&s, 3);
	SLPushFront(&s, 5);
	SLPrint(&s);

	SLSort(&s);
	SLPrint(&s);

	SLInsert(&s, 3, 4);
	SLPrint(&s);

	SLErase(&s, 1);
	SLPrint(&s);
	SLErase(&s, 3);
	SLPrint(&s);

	SLDestory(&s);
}

//测试查找指定元素和修改pos位置的元素的值
void test4()
{
	SL s;
	SLInit(&s);

	SLPushFront(&s, 1);
	SLPushFront(&s, 2);
	SLPushFront(&s, 3);
	SLPushFront(&s, 5);
	SLPrint(&s);

	int ret = SLFind(&s, 4);
	if (ret != -1)
	{
		printf("找到了,下标为: %d ", ret);
	}
	else
	{
		printf("没找到\n");
	}

	SLModif(&s, 2, 20);
	SLPrint(&s);
	SLModif(&s, 1, 10);
	SLPrint(&s);

	SLModif(&s, 4, 40);
	SLPrint(&s);

	SLDestory(&s);
}


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

静态顺序表的缺点就是它的存储个数是固定的,这个缺陷导致他的应用场景并不多,你一开始给他开多大的空间?这个是不是很为难呀?开太大了浪费了,开太小了又不够......所以它的应用场景不是很多!针对这种情况,我们进行优化:一开始给一点空间(或者不开)用的时候或不够了进行扩容(每次扩1.5倍或2倍),这样就有效的降低了浪费内存的情况~!OK,下面我们就来look一look顺序表的动态版本

四、动态顺序表的实现

动态顺序表的申明

typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* data;
	int size;//顺序表的元素个数
	int capacity;//顺序表的容量
}SeqList;

动态顺序表的初始化

//初始化
void SLInit(SL* ps)
{
	assert(ps);
	ps->data = (SLDataType*)malloc(sizeof(SLDataType) * 4);
	if (!ps->data)
	{
		perror("malloc failed");
		exit(-1);//异常终止
	}

	ps->size = 0;
	ps->capacity = 4;
}

这里一定要在malloc开辟后检查,否则一旦开辟失败就玩完了~!你都数组都没开出来你还在那里增删查改啥???所以,如果开辟失败直接异常退出~!

动态顺序表的销毁

//销毁
void SLDestory(SL* ps)
{
	assert(ps);

	free(ps->data);
	ps->data = NULL;
	ps->size = ps->capacity = 0;
}

要记得在最后把ps-data置空,否则就是野指针~!

动态顺序表的打印


//打印
void SLPrint(SL* ps)
{
	assert(ps);

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

动态顺序表的尾插

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);

	//判断扩容
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->data, sizeof(SLDataType) * newcapacity);
		if (!tmp)
		{
			perror("realloc failed");
			exit(-1);
		}
		
		ps->data = tmp;
		ps->capacity = newcapacity;
	}

	ps->data[ps->size] = x;
	ps->size++;
}

由于是动态开辟的我们无需考虑满了插入不下的问题,我们只需要考虑是否扩容的问题,如果顺序表的有效数据个数==容量那就得扩容了~!

动态顺序表的尾删

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);

	//判断是否为空
	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}

	ps->size--;
}

尾删得考虑当前顺序表已经是空的情况~!这里你也可以用断言一旦为空直接终止,我这里稍微人性化了一点,提示了一下,异常退出了~!哈哈~

OK,老样子!写两个测两个:

DS线性表之顺序表_第11张图片

OK,尾插尾删是没有问题了~!Let' s go on ~!

动态顺序表的头插

这里判断是否扩容会出现多次,而且代码相对较长,所以我么可以考虑封装成一个函数~!

//判断扩容
void JudExpan(SL* ps)
{
	assert(ps);

	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->data, sizeof(SLDataType) * newcapacity);
		if (!tmp)
		{
			perror("realloc failed");
			exit(-1);
		}

		ps->data = tmp;
		ps->capacity = newcapacity;
	}
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);

	JudExpan(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->data[i] = ps->data[i - 1];
	}
	ps->data[0] = x;
	ps->size++;
}

动态顺序表的头删

头删判断为空时的判断,当然也可以封装成一个函数,但这里就两句所以我没有封装成函数~!

//头删
void SLPopFront(SL* ps)
{
	assert(ps);

	//判断是否为空
	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}

	for (int i = 0; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i + 1];
	}
	ps->size--;
}

OK,测试一下:

DS线性表之顺序表_第12张图片

OK,没有问题~!我们继续!

动态顺序表的排序

我们还是采用C语言库里面提供的qsort进行排序

//比较函数
int cmp(const void* a, const void* b)
{
	return *(int*)a - *(int*)b;
}

//排序
void SLSort(SL* ps)
{
	assert(ps);
	qsort(ps->data, ps->size, sizeof(SLDataType), cmp);
}

这个我们就把感刚的头插修改一下测试一下即可:

DS线性表之顺序表_第13张图片

OK,没问题!

动态顺序表在pos位置插入

//在pos位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);

	JudExpan(ps);
	//判断pos的合法性
	if (pos > ps->capacity || pos < 1)
	{
		printf("pos非法!");
		exit(-1);
	}

	for (int i = ps->size; i >= pos; i--)
	{
		ps->data[i] = ps->data[i - 1];
	}
	ps->data[pos - 1] = x;
	ps->size++;
}

记得判断pos的合法性~!!!

动态顺序表在pos位置删除

//删除pos位置的元素
void SLErase(SL* ps, int pos)
{
	assert(ps);

	//判断是否为空
	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}
	//判断pos的合法性
	if (pos < 1 || pos > ps->size)
	{
		printf("pos非法!");
		exit(-1);
	}

	for (int i = pos - 1; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i + 1];
	}
	ps->size--;
}

这里还是注意pos的合法性!!!

OK,测试一下:

DS线性表之顺序表_第14张图片

OK,没有问题~!

动态顺序表中查找指定元素x

这个还是和上面的一样找到了返回下标,否则返回-1(如果是已经有序的就可以采用二分查找)

//查找指定元素x
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (x == ps->data[i])
		{
			return i;
		}
	}

	return -1;
}

动态顺序表中修改指定pos位置的元素

//修改pos位置的值
void SLModif(SL* ps, int pos, SLDataType x)
{
	assert(ps);

	if (pos >=1 && pos <= ps->size)
	{
		ps->data[pos - 1] = x;
	}
	else
	{
		printf("pos非法!\n");
		exit(-1);
	}
}

这里也是注意判断pos的合法性~!!!否则会出问题!!!

OK,测试一下:

DS线性表之顺序表_第15张图片

OK,没有问题~!

全部源码:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 


typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* data;
	int size;//顺序表的元素个数
	int capacity;//顺序表的容量
}SL;

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

//销毁
void SLDestory(SL* ps);

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

//尾插
void SLPushBack(SL* ps, SLDataType x);

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

//头插
void SLPushFront(SL* ps, SLDataType x);

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

//排序
void SLSort(SL* ps);

//在pos位置插入
void SLInsert(SL* ps, int pos, SLDataType x);

//删除pos位置的元素
void SLErase(SL* ps, int pos);

//查找指定元素x
int SLFind(SL* ps, SLDataType x);

//修改pos位置的值
void SLModif(SL* ps, int pos, SLDataType x);
#include "SeqList.h"

//初始化
void SLInit(SL* ps)
{
	assert(ps);
	ps->data = (SLDataType*)malloc(sizeof(SLDataType) * 4);
	if (!ps->data)
	{
		perror("malloc failed");
		exit(-1);//异常终止
	}

	ps->size = 0;
	ps->capacity = 4;
}

//销毁
void SLDestory(SL* ps)
{
	assert(ps);

	free(ps->data);
	ps->data = NULL;
	ps->size = ps->capacity = 0;
}

//打印
void SLPrint(SL* ps)
{
	assert(ps);

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

//判断扩容
void JudExpan(SL* ps)
{
	assert(ps);

	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->data, sizeof(SLDataType) * newcapacity);
		if (!tmp)
		{
			perror("realloc failed");
			exit(-1);
		}

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

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);

	JudExpan(ps);
	ps->data[ps->size] = x;
	ps->size++;
}

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);

	//判断是否为空
	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}

	ps->size--;
}

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);

	JudExpan(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->data[i] = ps->data[i - 1];
	}
	ps->data[0] = x;
	ps->size++;
}

//头删
void SLPopFront(SL* ps)
{
	assert(ps);

	//判断是否为空
	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}

	for (int i = 0; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i + 1];
	}
	ps->size--;
}

//比较函数
int cmp(const void* a, const void* b)
{
	return *(int*)a - *(int*)b;
}

//排序
void SLSort(SL* ps)
{
	assert(ps);
	qsort(ps->data, ps->size, sizeof(SLDataType), cmp);
}

//在pos位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);

	JudExpan(ps);
	//判断pos的合法性
	if (pos > ps->capacity || pos < 1)
	{
		printf("pos非法!");
		exit(-1);
	}

	for (int i = ps->size; i >= pos; i--)
	{
		ps->data[i] = ps->data[i - 1];
	}
	ps->data[pos - 1] = x;
	ps->size++;
}

//删除pos位置的元素
void SLErase(SL* ps, int pos)
{
	assert(ps);

	//判断是否为空
	if (ps->size == 0)
	{
		printf("顺序表为空无法删除!\n");
		exit(-1);
	}
	//判断pos的合法性
	if (pos < 1 || pos > ps->size)
	{
		printf("pos非法!");
		exit(-1);
	}

	for (int i = pos - 1; i < ps->size; i++)
	{
		ps->data[i] = ps->data[i + 1];
	}
	ps->size--;
}


//查找指定元素x
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (x == ps->data[i])
		{
			return i;
		}
	}

	return -1;
}

//修改pos位置的值
void SLModif(SL* ps, int pos, SLDataType x)
{
	assert(ps);

	if (pos >=1 && pos <= ps->size)
	{
		ps->data[pos - 1] = x;
	}
	else
	{
		printf("pos非法!\n");
		exit(-1);
	}
}
#include "SeqList.h"

//测试尾插、尾删
void test()
{
	SL s;
	SLInit(&s);
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPrint(&s);
	//检查扩容
	SLPushBack(&s, 5);
	SLPrint(&s);

	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);
	SLPopBack(&s);
	SLPrint(&s);

	//检查是否已经为空是的删除是否有Bug
	SLPopBack(&s);
	SLPrint(&s);

	SLDestory(&s);
}

//测试头插、头删
void test2()
{
	SL s;
	SLInit(&s);

	SLPushFront(&s, -1);
	SLPushFront(&s, 3);
	SLPushFront(&s, 0);
	SLPushFront(&s, 1);
	SLPrint(&s);
	//判断扩容
	SLPushFront(&s, 10);
	SLPrint(&s);

	SLSort(&s);
	SLPrint(&s);

	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);
	SLPopFront(&s);
	SLPrint(&s);
	//检查为空时删除是否有Bug
	SLPopFront(&s);
	SLPrint(&s);

	SLDestory(&s);
}

//测试在pos位置插入、删除
void test3()
{
	SL s;
	SLInit(&s);
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPrint(&s);

	SLInsert(&s, 4, 10);
	SLPrint(&s);
	SLInsert(&s, 4, -1);
	SLPrint(&s);
	//SLInsert(&s, 20, 7);
	//SLPrint(&s);

	SLErase(&s, 1);
	SLPrint(&s);
	SLErase(&s, 5);
	SLPrint(&s);
	//SLErase(&s, -0);
	//SLPrint(&s);

	SLDestory(&s);
}

//测试查找指定元素和修改pos位置的值
void test4()
{
	SL s;
	SLInit(&s);
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPrint(&s);

	int ret = SLFind(&s, -1);
	if (ret != -1)
	{
		printf("找到了,下标是: %d  顺序表位置是: %d ", ret, ret + 1);
	}
	else
	{
		printf("没找到!\n");
	}

	SLModif(&s, 1, -10);
	SLPrint(&s);
	SLModif(&s, -1, -10);
	SLPrint(&s);
}

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

OK~!两个版本的顺序表就完成了~!

五、顺序表的问题

1、在顺序表中间或头部进行插入或删除时的时间复杂度为O(N)

2、扩容时如果到数据多的时候会异地扩容,会增加空间复杂度~!

3、无论静态还是动态顺序表其实都会都空间浪费的现象,这是避免不了的~!

关于这些问题如何解决,请期待下一期:链表~!!!

OK,本期内容就分享到这里~!好兄弟,我们下期再见!!!

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