【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表

线性表

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…。线性表在逻辑上是线性结构,也就是说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式的结构的形式存储。

线性表的概念:

  • 是n个具有相同元素特性的数据元素的有限序列。
  • 有穷性:一个线性表中的元素是有限的。
  • 一致性:一个线性表中所有的元素的性质相同。
  • 有序性:一个线性表中所有的元素之间的相对位置是线性的,即存在唯一的开始元素和终端元素,除此之外,每个元素都只有唯一的前驱元素和后继元素。各元素在线性表中的位置只取决于它们的序号,所以一个线性表中可以存在两个及以上的相同元素。

线性表的作用:

线性表一般用于存储有顺序关系的数列。最典型的例子就是为队列(先进先出)、栈(先进后出)。

线性表支持的操作:

  • **插入元素:**在线性表的指定位置插入一个元素,(insert、pushBack、pushFront等函数)。
  • **删除元素:**在线性表的指定位置删除一个元素,(pop、、popBack、popFront等函数)。
  • **访问元素:**访问线性表中指定位置的一个元素,(下标操作符[]等)。

线性表的逻辑结构图

【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第1张图片
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第2张图片

顺序表

顺序表的概念:

顺序表一般是用一段物理地址连续的存储单位依次存储元素的线性结构,一般情况下采用数组存储。在数组上完成增删查改。

顺序表的结构:

静态结构:使用定长数组存储

//静态顺序表
#define n 10

typedef int SLDataType;
typedef struct SeqList
{
	//定长数组
	SLDataType arr[n];
	//有效数据的个数
	int size;
}SeqList;

静态顺序表最大的缺陷就是,当数据存放满的时候就需要手动去增加,如果程序写成这样那会被骂死的,所以顺序有引入了动态顺序表。

动态结构:使用动态开辟的数组存储

//动态顺序表
typedef int SLDataType;

typedef struct SeqList
{
	//指向动态开辟的数组
	SLDataType* arr;
	//有效数据的个数
	int size;
	//容量空间的大小
	int capacity;
}SeqList;

当数组的空间满了,程序就会自动的扩容,这样相较与静态顺序表来说就灵活了很多,但是还是会造成空间浪费,后面我会讲一个更好的结构。

接口实现:

下面我们来用动态顺序表来实现对数据的增删查改。因为静态顺序表实用性不怎么强我这里就不演示了,直接开干动态顺序表:

首先就是初始化顺序表:

//顺序表的初始化
void SeqListInit(SeqList* ps)
{
	//断言传过来的指针是否为空
	assert(ps);
	//首先把指向数组的指针置空
	ps->arr = NULL;
	//在把有效数据个数置空
	ps->size = 0;
	//给容量付个初始值
	ps->capacity = 0;
}

对空间进行扩容:

// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps)
{
	//对指针进行断言
	assert(ps);
	//判断空间是否为空或者数据满了
	if (ps->size==ps->capacity)
	{
		//定义一个变量来接收容量的值
		//当容量的值为0的时候直接赋值为4,不为空的时候直接*2
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//创建一个指针来接收开辟的空间的地址
		SLDataType* pf = NULL;
		//使用realloc来开辟空间
		pf = (SLDataType*)realloc(ps->arr, newcapacity*sizeof(SLDataType));
		//检查是否开辟成功
		if (pf==NULL)
		{
			perror("realloc");
			//退出程序
			exit(-1);
		}
		//把开辟的空间的地址赋值个arr指针
		ps->arr = pf;
		//同时把新空间容量的大小赋值给结构中用来记录容量的值
		ps->capacity =newcapacity;
	}
}

打印顺序表:

// 顺序表打印
void SeqListPrint(SeqList* ps)
{
	//断言
	assert(ps);
	//通过循环打印
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	//换行
	printf("\n");
}

顺序表销毁:

// 顺序表销毁
void SeqListDestory(SeqList* ps)
{
	//断言
	assert(ps);
	//释放空间
	free(ps->arr);
	//然后把指针置空
	ps->arr = NULL;
	//把有效数据个数和容量空间的大小都置为0
	ps->capacity = 0;
	ps->size = 0;
}

顺序表尾插:

在尾插的时候要考虑两种情况:

  1. 当顺序表为空或者顺序表满的时候(调用空间扩容的接口)
  2. 正常的尾插
// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
	//断言
	assert(ps);
	//对空间进行扩容
	CheckCapacity(ps);
	//把要插入的值插入顺序表
	ps->arr[ps->size] = x;
	//把记录有效数据个数的变量加1
	ps->size++;
	
}

在主函数中的代码:

#include"List.h"

void List1()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2); 
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}

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

以下是测试结果:
image.png

顺序表尾删:

  • 只有顺序表中有数据的时候才能删除
  • 只有最后一个数据的时候就不删除了
// 顺序表尾删
void SeqListPopBack(SeqList* ps)
{
	//断言指针是否为空
	assert(ps);
	//当只有size大于0的时候才能尾删
	assert(ps->size > 0);
	//尾删
	ps->size--;
}

测试运行结果:
测试的代码

void List1()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2); 
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}

运行结果:
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第3张图片
这就是断言起到的作用,当删除的数据太多的时候就会中止程序。
下面是正常删除的演示
测试代码:

void List1()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2); 
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	
	SeqListDestory(&sl);
}

运行结果:
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第4张图片

顺序表头插:

  1. 首先对顺序表进行扩容
  2. 把数据向后移动空出头的位置
  3. 把数据插入进去同时有效数据个数要加1

代码如下:

// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
	//断言指针是否为空
	assert(ps);
	//扩容
	CheckCapacity(ps);
	//定义一个变量来接收顺序表中的有效个数
	int i = ps->size;
	//把数据向后移动空出头的位置
	while (i)
	{
		ps->arr[i] = ps->arr[i- 1];
		i--;
	}
	//给头的位置赋值
	ps->arr[0] = x;
	//然后有效数据个数加1
	ps->size++;
}

测试代码和运行结果:
测试代码:

void List2()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListPushFront(&sl, 0);
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}

运行结果:
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第5张图片

顺序表头删:

  1. 控制顺序表中的数据个数大于0
  2. 把后面的数据向前移动

接口代码如下:

//顺序表头删
void  SeqListPopFront(SeqList* ps)
{
	//断言
	assert(ps);
	//控制size的值大于0
	assert(ps->size > 0);
	//定义一个变量来接收顺序表中的有效个数
	int i = 1;
	//把后面的数据向前移动
	while (i<ps->size)
	{
		ps->arr[i - 1] = ps->arr[i];
		i++;
	}
	//把有效数据减1
	ps->size--;
}

测试代码和运行结果:
测试代码:

void List3()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);	
	SeqListDestory(&sl);
}

运行结果:
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第6张图片

顺序表查找:

  1. 遍历所有的数据
  2. 找到相同的数据
  3. 返回下标
int SeqListFind(SeqList* ps, SLDataType x)
{
	//断言
	assert(ps);
	//遍历数据
	for (int i = 0; i < ps->size; i++)
	{
		//找到数据
		if (ps->arr[i]==x)
		{
			//返回下标
			return i;
		}
	}
	//没有找到返回-1
	return -1;
}

顺序表在pos位置插入x:

  1. 扩容
  2. 把pos位置之后的位置向后移动
  3. 然后再pos位置把数据插入顺序表中
  4. 有效数据个数加1
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDataType x)
{
	assert(ps);
	//扩容
	CheckCapacity(ps);
	assert(0 <= pos && pos <= ps->size);
	int begin = ps->size;
	//把pos之后的数据向后移动
	while (begin>pos)
	{
		
		ps->arr[begin] = ps->arr[begin-1];
		begin--;
	}
	ps->arr[pos] = x;
	ps->size++;

}

测试代码和运行结果:
测试代码:

void List4()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	printf("请输入插入的位置和数据:");
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int pos = SeqListFind(&sl, a);
	if (pos!=-1)
	{
		SeqListInsert(&sl, pos, b);
	}
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}

运行结果:
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第7张图片

顺序表在pos位置删除:

  1. 控制顺序表中的数据个数
  2. 把pos位置之后的数据向前移动
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
	//断言
	assert(ps);

	//控制数据个数
	assert(ps->size > 0);
	assert(0 <= pos && ps->size > pos);
	int begin = pos;
	//把pos之后的数据向前移动
	while (begin<ps->size)
	{
		ps->arr[begin] = ps->arr[begin + 1];
		begin++;
	}
	ps->size--;
}

测试代码和运行结果:
测试代码:

void List5()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	printf("请输入删除的数据:");
	int a = 0;
	scanf("%d", &a);
	int pos = SeqListFind(&sl, a);
	if (pos != -1)
	{
		SeqListErase(&sl, pos);
	}
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}

运行结果:
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第8张图片

完整代码:

List.c文件代码:

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"
//顺序表的初始化
void SeqListInit(SeqList* ps)
{
	//断言传过来的指针是否为空
	assert(ps);
	//首先把指向数组的指针置空
	ps->arr = NULL;
	//在把有效数据个数置空
	ps->size = 0;
	//给容量付个初始值
	ps->capacity = 0;
}

// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps)
{
	//对指针进行断言
	assert(ps);
	//判断空间是否为空或者数据满了
	if (ps->size==ps->capacity)
	{
		//定义一个变量来接收容量的值
		//当容量的值为0的时候直接赋值为4,不为空的时候直接*2
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//创建一个指针来接收开辟的空间的地址
		SLDataType* pf = NULL;
		//使用realloc来开辟空间
		pf = (SLDataType*)realloc(ps->arr, newcapacity*sizeof(SLDataType));
		//检查是否开辟成功
		if (pf==NULL)
		{
			perror("realloc");
			//退出程序
			exit(-1);
		}
		//把开辟的空间的地址赋值个arr指针
		ps->arr = pf;
		//同时把新空间容量的大小赋值给结构中用来记录容量的值
		ps->capacity =newcapacity;
	}
}

// 顺序表打印
void SeqListPrint(SeqList* ps)
{
	//断言
	assert(ps);
	//通过循环打印
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	//换行
	printf("\n");
}

// 顺序表销毁
void SeqListDestory(SeqList* ps)
{
	//断言
	assert(ps);
	//释放空间
	free(ps->arr);
	//然后把指针置空
	ps->arr = NULL;
	//把有效数据个数和容量空间的大小都置为0
	ps->capacity = 0;
	ps->size = 0;
}

// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
	//断言
	assert(ps);
	//对空间进行扩容
	CheckCapacity(ps);
	//把要插入的值插入顺序表
	ps->arr[ps->size] = x;
	//把记录有效数据个数的变量加1
	ps->size++;
	
}

// 顺序表尾删
void SeqListPopBack(SeqList* ps)
{
	//断言指针是否为空
	assert(ps);
	//当只有size大于0的时候才能尾删
	assert(ps->size > 0);
	//尾删
	ps->size--;
}

// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
	//断言指针是否为空
	assert(ps);
	//扩容
	CheckCapacity(ps);
	//定义一个变量来接收顺序表中的有效个数
	int i = ps->size;
	//把数据向后移动空出头的位置
	while (i)
	{
		ps->arr[i] = ps->arr[i- 1];
		i--;
	}
	//给头的位置赋值
	ps->arr[0] = x;
	//然后有效数据个数加1
	ps->size++;
}

//顺序表头删
void  SeqListPopFront(SeqList* ps)
{
	//断言
	assert(ps);
	//控制size的值大于0
	assert(ps->size > 0);
	//定义一个变量来接收顺序表中的有效个数
	int i = 1;
	//把后面的数据向前移动
	while (i<ps->size)
	{
		ps->arr[i - 1] = ps->arr[i];
		i++;
	}
	//把有效数据减1
	ps->size--;
}

// 顺序表查找
int SeqListFind(SeqList* ps, SLDataType x)
{
	//断言
	assert(ps);
	//遍历数据
	for (int i = 0; i < ps->size; i++)
	{
		//找到数据
		if (ps->arr[i]==x)
		{
			//返回下标
			return i;
		}
	}
	//没有找到返回-1
	return -1;
}

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDataType x)
{
	assert(ps);
	//扩容
	CheckCapacity(ps);
	assert(0 <= pos && pos <= ps->size);
	int begin = ps->size;
	//把pos之后的数据向后移动
	while (begin>pos)
	{
		
		ps->arr[begin] = ps->arr[begin-1];
		begin--;
	}
	ps->arr[pos] = x;
	ps->size++;

}

// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
	//断言
	assert(ps);

	//控制数据个数
	assert(ps->size > 0);
	assert(0 <= pos && ps->size > pos);
	int begin = pos;
	//把pos之后的数据向前移动
	while (begin<ps->size)
	{
		ps->arr[begin] = ps->arr[begin + 1];
		begin++;
	}
	ps->size--;
}

List.h代码:

#pragma once

#include
#include
#include
#include

//静态顺序表
//#define n 10
//
//typedef int SLDataType;
//typedef struct SeqList
//{
//	//定长数组
//	SLDataType arr[n];
//	//有效数据的个数
//	int size;
//}SeqList;


//动态顺序表
typedef int SLDataType;

typedef struct SeqList
{
	//指向动态开辟的数组
	SLDataType* arr;
	//有效数据的个数
	int size;
	//容量空间的大小
	int capacity; 
}SeqList;

//顺序表的初始化
void SeqListInit(SeqList* ps);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps);
// 顺序表打印
void SeqListPrint(SeqList* ps);
// 顺序表销毁
void SeqListDestory(SeqList* ps);
// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* ps);
// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x);
//顺序表头删
void  SeqListPopFront(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);


test.c文件代码:

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

void List1()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2); 
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);

	SeqListDestory(&sl);
}

void List2()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListPushFront(&sl, 0);
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}

void List3()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);	
	SeqListDestory(&sl);
}

void List4()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	printf("请输入插入的位置和数据:");
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int pos = SeqListFind(&sl, a);
	if (pos!=-1)
	{
		SeqListInsert(&sl, pos, b);
	}
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}
void List5()
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);
	printf("请输入删除的数据:");
	int a = 0;
	scanf("%d", &a);
	int pos = SeqListFind(&sl, a);
	if (pos != -1)
	{
		SeqListErase(&sl, pos);
	}
	SeqListPrint(&sl);
	SeqListDestory(&sl);
}
int main()
{	
	List5();
	return 0;
}

力扣真题

移除元素:

题目如下:移除元素
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第9张图片
解题思路:

  1. 首先定义两个变量(用来当作下标)赋初始值为0;
  2. 然后要是不等于val
  3. 那么就把赋值给另一个下标的空间
  4. 否则就是直接把下标加1
  5. 返回的是另一个下标

代码如下:

int removeElement(int* nums, int numsSize, int val){
    //定义两个变量
    int src=0;
    int dest=0;
    //控制src的值不能越界
    while(src<numsSize)
    {
        //要是不等于就直接赋值
        if(nums[src]!=val)
        {
            nums[dest]=nums[src];
            dest++;
            src++;
        }
        else
        {
            src++;
        }
    }
    return dest;
}

题目如下:
删除有序数组中的重复项
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第10张图片
解题思路:
【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表_第11张图片

  1. 首先创建两个变量来充当下标
  2. 然后把其中一个下标的初始值赋值为1,另一个为0
  3. 当两个下标指向的值不相等的时候就先把为0的下标加1,然后再把初始值为1的下标的值赋值个另一个下标
  4. 反之直接就第一个下标加1
  5. 返回的是第一个下标加1

代码如下:

int removeDuplicates(int* nums, int numsSize){
    int n=numsSize;
    int dst=0;
    int src=1;
    while(src<n)
    {
        if(nums[dst]!=nums[src])
        {
            nums[dst+1]=nums[src];
            dst++;
            src++;
        }
        else
        {
            src++;
        }
    }
    return dst+1;

}

以上就是我关于线性表中的顺序表的总结主要从顺序表的结构、顺序表的接口实现和力扣真题这3个方面进行总结,在线性表中不止有顺序表还有链表,下篇主要就是围绕顺序表来总结,希望这篇博客对大家有用。

你可能感兴趣的:(数据结构)