【数据结构】线性表--顺序表

【数据结构】线性表之顺序表

一.知识补充

1.什么是数据结构

数据结构是相互之间存在一种或多种特定关系的数据元素的集合。

2.数据元素的四类基本结构

①.集合:结构中的数据元素除了同属于一个集合外,无其他关系。

②.线性结构:结构中的数据元素存在一对一的关系。

③.树形结构:结构中的数据元素存在一对多的关系。

④.图状结构或网状结构:结构中的数据存在多对多的关系。

3.数据结构表示

数据结构在计算机中的表示(又称映像)称为数据的物理结构,又称存储结构。

数据元素之间的关系在计算机中有两种不同的表示方法:顺序映像和非顺序映像,并因此得出两种不同的存储结构:顺序存储结构和链式存储结构。

顺序映像的特点是借助元素在存储器中的相对位置表示数据元素之间的逻辑关系。非顺序映像的特点是借助指示元素存储地址的指针表示数据元素之间的逻辑关系。

4.线性表

线性表是n个具有相同特性的数据元素的有限序列。

线性表在逻辑上是线性结构,在物理结构上不一定是。

线性表在物理上存储时,通常以数组和链式结构的形式存储。

5.线性结构特点

线性结构的特点是:①.存在唯一一个被称为"第一个"的元素;②.存在唯一一个被称为"最后一个"的元素;③.除第一个元素外,集合每个元素均只有一个前驱;④.除最后一个元素外,集合每个元素均只有一个后继。

6.常见线性表

常见线性表包括顺序表,链表,栈,队列和串等等。

二.顺序表

1.顺序表概念

顺序表即线性表的顺序实现,就是使用一段物理地址连续的存储单元依次存取数据元素的线性结构。

顺序表通常是对数组进行封装再加上增删查改等各种操作。

顺序表可分为静态顺序表(顺序表长度固定)和动态顺序表(使用动态开辟的数组存储) 。

2.顺序表的实现(C语言实现)

由于静态顺序表的长度不够灵活,空间小了可能不够用,空间大了造成浪费,因此实际中更常用的是动态顺序表,所以在此只写出动态顺序表的实现。

①动态顺序表结构定义:

cpp 复制代码
//使用typedef为类型起别名,存储其他类型的数据时也方便修改
//例如存储double类型的数据时直接将int修改成double
typedef int SQDataType;

typedef struct SeqList
{
	SQDataType* arr;
	int size;    //记录当前实际存储数据的个数
	int capacity;//顺序表容量大小
}SL;//将struct SeqList简写为SL

②初始化函数:

cpp 复制代码
//初始化函数定义
void SLInit(SL* ps)//使用指针接收,否则形参的改变不会影响实参
{
	ps->arr = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

③销毁函数:

cpp 复制代码
//销毁函数定义
void SLDestory(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

④容量检查函数:

cpp 复制代码
//检查空间是否充足(其他插入函数也需要检查空间是否充足,因此直接封装为一个函数)
void SLCheckCapacity(SL* ps)
{
	//处理数组满了的情况
	if (ps->size == ps->capacity)
	{
		//如果当前顺序表容量大小为0,那么乘2也为0,所以用三目操作符判断。一般是二倍扩容
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;

		//防止出现扩容失败造成数据丢失,因此先用tmp暂存
		SQDataType* tmp = (SQDataType*)realloc(ps->arr, NewCapacity * sizeof(SQDataType));
		if (tmp == NULL)
		{
			printf("realloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = NewCapacity;
	}
}

⑤尾插函数:

cpp 复制代码
//尾插函数定义
//时间复杂度为O(1)
void SLPushBack(SL* ps, SQDataType x)
{
	//使用断言检查指针是否为空
	assert(ps);
	SLCheckCapacity(ps);
	//尾插时当前数据个数与要插入的位置下标是相同的
	ps->arr[ps->size] = x;
	ps->size++;
}

⑥头插函数:

cpp 复制代码
//头插函数定义
//时间复杂度为O(n)
void SLPushFront(SL* ps, SQDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);

	//头插时先要把数据整体后移(先移动后面的数据)
	for (int i = ps->size - 1; i >= 0; i--)
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[0] = x;
	ps->size++;
}

⑦指定位置插入函数:

cpp 复制代码
//指定位置插入函数定义
//时间复杂度为O(n)
void SLInsert(SL* ps, SQDataType x, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	//要将指定位置及之后的数据整体往后移
	for (int i = ps->size - 1; i >=pos;i--)
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[pos] = x;
	ps->size++;
}

⑧尾删函数:

cpp 复制代码
//尾删函数定义
//时间复杂度为O(1)
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//数据个数为0时不能删除
	ps->size--;
}

⑨头删函数:

cpp 复制代码
//头删函数定义
//时间复杂度为O(n)
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//所有数据整体向前移动一位即头删
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

⑩删除指定位置的数据函数:

cpp 复制代码
//删除指定位置的数据
//时间复杂度为O(n)
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(ps->size);
	assert(pos >= 0 && pos < ps->size);

	//pos位置之后的数据整体向前移即完成删除
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

⑪查找指定数据函数:

cpp 复制代码
//查找指定数据
//时间复杂度为O(n)
int SLFind(SL* ps, SQDataType x)
{
	assert(ps);
	assert(ps->size);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
			return i;//查找成功返回下标
	}
	//循环结束仍未返回,则代表没有该数据,查找失败返回-1
	return -1;
}

三.总结

上述是动态顺序表的各个函数操作实现,以下是动态顺序表全部实现。

小tips:尽量写完一部分代码就调试一部分代码,否则可能出现代码全部写完,但出现很多报错无从下手的情况。

1.头文件(声明动态顺序表的结构,操作等,起到目录作用):

SeqList.h

cpp 复制代码
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>

//使用typedef为类型起别名,存储其他类型的数据时也方便修改
//例如存储double类型的数据时直接将int修改成double
typedef int SQDataType;

typedef struct SeqList
{
	SQDataType* arr;
	int size;    //记录当前实际存储数据的个数
	int capacity;//顺序表容量大小
}SL;//将struct SeqList简写为SL

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

//销毁函数声明
void SLDestory(SL* ps);

//打印顺序表(方便调试)
void SLPrint(SL* ps);

//检查空间是否充足
void SLCheckCapacity(SL* ps);

//尾插函数声明
void SLPushBack(SL* ps, SQDataType x);

//头插函数声明
void SLPushFront(SL* ps, SQDataType x);

//指定位置插入函数声明
void SLInsert(SL* ps, SQDataType x, int pos);

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

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

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

//查找指定数据
int SLFind(SL* ps, SQDataType x);

2.源文件(具体实现各种操作):

SeqList.c

cpp 复制代码
//包含自己写的头文件需用""
#include"SeqList.h"

//初始化函数定义
void SLInit(SL* ps)//使用指针接收,否则形参的改变不会影响实参
{
	ps->arr = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

//销毁函数定义
void SLDestory(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

//打印顺序表(方便调试)
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

//检查空间是否充足(其他插入函数也需要检查空间是否充足,因此直接封装为一个函数)
void SLCheckCapacity(SL* ps)
{
	//处理数组满了的情况
	if (ps->size == ps->capacity)
	{
		//如果当前顺序表容量大小为0,那么乘2也为0,所以用三目操作符判断。一般是二倍扩容
		int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;

		//防止出现扩容失败造成数据丢失,因此先用tmp暂存
		SQDataType* tmp = (SQDataType*)realloc(ps->arr, NewCapacity * sizeof(SQDataType));
		if (tmp == NULL)
		{
			printf("realloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = NewCapacity;
	}
}

//尾插函数定义
//时间复杂度为O(1)
void SLPushBack(SL* ps, SQDataType x)
{
	//使用断言检查指针是否为空
	assert(ps);
	SLCheckCapacity(ps);
	//尾插时当前数据个数与要插入的位置下标是相同的
	ps->arr[ps->size] = x;
	ps->size++;
}


//头插函数定义
//时间复杂度为O(n)
void SLPushFront(SL* ps, SQDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);

	//头插时先要把数据整体后移(先移动后面的数据)
	for (int i = ps->size - 1; i >= 0; i--)
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[0] = x;
	ps->size++;
}

//指定位置插入函数定义
//时间复杂度为O(n)
void SLInsert(SL* ps, SQDataType x, int pos)
{
	assert(ps);
	//检查指定位置的有效性
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);

	//要将指定位置及之后的数据整体往后移
	for (int i = ps->size - 1; i >=pos;i--)
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[pos] = x;
	ps->size++;
}

//尾删函数定义
//时间复杂度为O(1)
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//数据个数为0时不能删除
	ps->size--;
}

//头删函数定义
//时间复杂度为O(n)
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//所有数据整体向前移动一位即头删
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

//删除指定位置的数据
//时间复杂度为O(n)
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(ps->size);
	assert(pos >= 0 && pos < ps->size);

	//pos位置之后的数据整体向前移即完成删除
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

//查找指定数据
//时间复杂度为O(n)
int SLFind(SL* ps, SQDataType x)
{
	assert(ps);
	assert(ps->size);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
			return i;//查找成功返回下标
	}
	//循环结束仍未返回,则代表没有该数据,查找失败返回-1
	return -1;
}

3.测试文件(测试各个函数的功能)

test.c

cpp 复制代码
#include"SeqList.h"

//测试初始化函数
void test01()
{
	SL sl;
	SLInit(&sl);

	SLDestory(&sl);
}

//测试尾插函数
void test02()
{
	SL sl;
	SLInit(&sl);

	for (int i = 0; i < 10; i++)
	{
		SLPushBack(&sl, i);
	}
	SLPrint(&sl);

	SLDestory(&sl);
}

//测试头插函数
void test03()
{
	SL sl;
	SLInit(&sl);
	for (int i = 0; i < 10; i++)
	{
		SLPushFront(&sl, i);
	}
	SLPrint(&sl);

	SLDestory(&sl);
}

//测试指定位置插入
void test04()
{
	SL sl;
	SLInit(&sl);
	//先尾插五个数据
	for (int i = 0; i < 5; i++)
	{
		SLPushBack(&sl, i);
	}
	SLPrint(&sl);
	SLInsert(&sl, 87, 3);
	SLPrint(&sl);
	SLInsert(&sl, 56, 0);
	SLPrint(&sl);
	SLInsert(&sl, 16, 7);
	SLPrint(&sl);

	SLDestory(&sl);
}

//测试尾删函数
void test05()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLPopBack(&sl);
	SLPrint(&sl);
	SLPopBack(&sl);
	SLPrint(&sl);
	SLPopBack(&sl);
	SLPrint(&sl);
	SLPopBack(&sl);
	SLPrint(&sl);
	SLPopBack(&sl);
	SLPrint(&sl);

	SLDestory(&sl);
}

//测试头删函数
void test06()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLPopFront(&sl);
	SLPrint(&sl);
	SLPopFront(&sl);
	SLPrint(&sl);
	SLPopFront(&sl);
	SLPrint(&sl);
	SLPopFront(&sl);
	SLPrint(&sl);
	SLPopFront(&sl);
	SLPrint(&sl);

	SLDestory(&sl);
}

//测试指定位置删除
void test07()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLErase(&sl, 1);
	SLPrint(&sl);
	SLErase(&sl, 0);
	SLPrint(&sl);
	SLErase(&sl, 2);
	SLPrint(&sl);

	SLDestory(&sl);
}

//测试查找函数
void test08()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	printf("%d\n", SLFind(&sl, 3));
	printf("%d\n", SLFind(&sl, 1));
	printf("%d\n", SLFind(&sl, 5));
	printf("%d\n", SLFind(&sl, 79));

	SLDestory(&sl);
}

int main()
{
	//调用测试函数
	//test01();
	//test02();
	//test03();
	//test04();
	//test05();
	//test06();
	//test07();
	test08();
	return 0;
}
相关推荐
qq_447429411 小时前
数据结构与算法:图论——并查集
数据结构·算法·图论
兮山与1 小时前
数据结构4.0
java·开发语言·数据结构
算法歌者5 小时前
[C]基础14.字符函数和字符串函数
c语言
照海19Gin8 小时前
从括号匹配看栈:数据结构入门的实战与原理
数据结构
奔跑的乌龟_12 小时前
L3-040 人生就像一场旅行
数据结构·算法
佛大第一深情13 小时前
ESP32 在Platform Arduino平台驱动外部PSAM,进行内存管理
c语言·单片机·嵌入式硬件
一匹电信狗13 小时前
【数据结构】堆的完整实现
c语言·数据结构·c++·算法·leetcode·排序算法·visual studio
浅安的邂逅13 小时前
Linux进程7-signal信号处理方式验证、可重入函数举例、信号集函数验证、信号集阻塞验证
linux·c语言·vim·进程通信·gcc
Le_ee16 小时前
数据结构6 · BinaryTree二叉树模板
数据结构·c++·算法