数据结构----线性表之顺序表

目录

  • 前言
  • 1.线性表
  • 2.顺序表
  • 3.相关功能接口
    • 3.1起始结构体的定义‍
    • 3.2初始化‍
    • 3.3插入与删除‍
      • 3.3.1判断数组容量
      • 3.3.2尾部插入
      • 3.2.3头部插入
      • 3.2.4尾部删除
      • 3.3.5头部删除
    • 3.4查找‍
    • 3.5删除‍
    • 3.6插入‍
    • 3.6修改与打印‍
  • 4.整体代码
  • Ending

前言

C语言已经基本完成了,接下来我会更新C语言初阶的数据结构与算法以及C语言相关题解;

而本次主要介绍的内容是线性表中的顺序表。
数据结构----线性表之顺序表_第1张图片

1.线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串;

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

2.顺序表

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

其一般具有两种结构,一种是静态顺序表,一种是动态顺序表;

静态顺序表:

typedef int DataType;
#define MAX 10
typedef struct SeqList
{
	DataType a[MAX];
	int size;
}SeqList;

上述顺序表中包括一个数组,以及元素个数size,当然我们给里面存储的也可以是其他数据,比如结构体等也是可以的。

动态顺序表:

typedef int DataType;
typedef struct SeqList
{
	DataType* arr;
	int size;
	int capacity;
}SeqList;

此时开辟的是动态数组,通过malloc进行动态内存开辟,size是有效数据的个数,capacity是顺序表的容量;

可以看出,动态的顺序表比之静态的顺序表更加灵活,而静态的顺序表则主要用于确定知道存多少数据的场景,而在一般情景下我们是不能得知需要存储多少数据的;所以大多情况下我们使用动态的顺序表!

3.相关功能接口

我们知道,利用顺序表解决问题时,大多数情况下都避不开增、
删、改、查等几种操作,所以接下来就这几种操作我们分开讨论!

3.1起始结构体的定义‍

typedef int DataType;
typedef struct SeqList
{
	DataType* arr;
	int size;
	int capacity;
}SeqList;

3.2初始化‍

void InitSqList(SeqList* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

分析:断言保证ps不是空指针,对ps->arr以及容量和有效数据的个数进行初始化!

3.3插入与删除‍

3.3.1判断数组容量

插入存在两种方式,一种是尾部插入,一种是头部插入;换句话说就是从顺序表后边进行数据的插入,从顺序表的表头进行数据的插入!

在插入前,我们应该知道数组的容量是否可以使数据存入,所以我们需要进行判断,并且有必要的话需要进行扩容!

我们先看从判断是否需要进行扩容:

static CheckCapacity(SeqList* ps)
{
	if (ps->capacity == ps->size)
	{
		int newcapacity = ps->capacity == 0 ? 4 : (ps->capacity * 2);
		DataType* temp = (DataType*)realloc(ps->arr, (newcapacity * sizeof(int)));
		if (temp == NULL)
		{
			printf("%s", strerror(errno));
			return;
		}
		ps->arr = temp;
		ps->capacity = newcapacity;
	}
}

分析:
由于我们实现的是动态数组,所以需要先判断动态数组的容量是否为0或者是不是已经满了,根据条件表达式 ps->capacity == 0 ? 4 : (ps->capacity * 2)---- 如果容量为0,则置容量为4,否则将容量*2;接下来利用realloc进行扩容,扩容的大小是字节得大小,具体的realloc函数的使用我在动态内存管理中已经介绍过,在此不进行赘述。

3.3.2尾部插入

void SeqListPushBack(SeqList* ps,DataType x)
{
	assert(ps);
	CheckCapacity(ps);
	ps->arr[ps->size] = x;
	ps->size++;
}

分析:
判断容量,容量不足时进行扩容;之后对数组进行赋值,并且有效数据的个数++。

3.2.3头部插入

void SeqListPushFront(SeqList* ps, DataType x)
{
	assert(ps);
	CheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	ps->arr[0] = x;
	ps->size++;
}

分析:
同样检查容量,与尾部插入不同的是,头部插入需要将前面数据依次向后挪到,所以 ps->arr[end + 1] = ps->arr[end],最后对头部进行赋值即可,有效数据个数++。

3.2.4尾部删除

void PopSeqListBack(SeqList* ps)
{
	assert(ps);
	assert(ps->arr>0);
	ps->size--;
}

分析:尾部删除思路很简短,将有效数据个数–即可;

3.3.5头部删除

void PopSeqListFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);
	for (int i = 0; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

分析:头部删除,就意味着需要将原有数据依次向前挪动,其原理与头部插入刚好相反,所以i最大取到ps->size-2,ps->arr[i + 1]索引ps->size-1的位置,对有效数据个数–;

3.4查找‍

前面提到过,数据的基本操作是增删改查,而插入与删除某一具体元素这两个操作是离不开查找这一功能的,因为逻辑简单,在此不进行赘述!

int FindSeqList(SeqList* ps, DataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
			return i;
	}
	printf("无该元素!\n");
	return -1;
}

3.5删除‍

void DeleteSeqList(SeqList* ps, DataType x)
{
	assert(ps);
	int pos=FindSeqList(ps, x);
	if (pos != -1)
	{
		for (int i = pos; i < ps->size-1; i++)
		{
			ps->arr[i] = ps->arr[i + 1];
		}
	}
	ps->size--;
}

分析:删除时,需要对删除的这一元素进行位置的查找,当存在时,则进行删除,同理,将数据依次向前进行覆盖即可;当需要删除最后一个元素时,此时for循环的条件不满足,直接有效数据个数–就意味着删除了!

3.6插入‍

void InsertSeqList(SeqList* ps, DataType x, size_t pos)
{
	assert(ps);
	CheckCapacity(ps);

	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	
	ps->arr[pos] = x;
	ps->size++;
}

可以从函数参数看出,我们插入时需要对传递插入的数据以及插入的位置,而进行插入就以为着元素增加,其就需要判断容量是否以及满了;
其次,将插入的位置的元素依次向后进行插入,原理与头部插入一致;

❗❗❗
然而,运行上述代码时,如果我们要在头部插入时(插入下标为0的位置时),程序会直接崩溃;这是因为当将下标为0的数据向后传递完成后,end–,此时end是-1,在剖析数据存储时我提到过算术类型转换,而end是int型数据,pos是无符号整型,因此两者进行比较时,int会提升转换至无符号整型,系统会将-1的补码当成原码,也就是将其当成无符号数,这是一个很大的数,所以会造成死循环,此时程序崩溃!

此时,我们只需要将pos强制类型转换成int即可;

int end = ps->size - 1;
while (end >= (int)pos)
{
	ps->arr[end + 1] = ps->arr[end];
	end--;
}

当然,还有一种方法,就是将end变为无符号数即可

size_t end = ps->size;
while (end > pos)
{
	ps->arr[end] = ps->arr[end - 1];
	end--;
}

下面给出正确代码:

//插入
void InsertSeqList(SeqList* ps, DataType x, size_t pos)
{
	assert(ps);
	CheckCapacity(ps);

	//一
	/*int end = ps->size - 1;
	while (end >= (int)pos)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}*/

	//二、
	size_t end = ps->size;
	while (end > pos)
	{
		ps->arr[end] = ps->arr[end - 1];
		end--;
	}

	ps->arr[pos] = x;
	ps->size++;
}

3.6修改与打印‍

//修改
void ModifySeqList(SeqList* ps, DataType x)
{
	assert(ps);
	int pos=FindSeqList(ps, x);
	if (pos != -1)
	{
		ps->arr[pos] = x;
	}
}
//打印
void PrintSeqList(SeqList* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

4.整体代码

#include
#include
#include
#include
#include

typedef int DataType;
typedef struct SeqList
{
	DataType* arr;
	int size;
	int capacity;
}SeqList;
//初始化
void InitSeqList(SeqList* ps);
//销毁顺序表
void DestorySeqList(SeqList* ps);
//尾部插入
void PushSeqListBack(SeqList* ps, DataType x);
//尾部删除
void PopSeqListBack(SeqList* ps);
//头部插入
void PushSeqListFront(SeqList* ps, DataType x);
//头部删除
void PopSeqListFront(SeqList* ps);
//查找
int FindSeqList(SeqList* ps, DataType x);
//删除
void DeleteSeqList(SeqList* ps, DataType x);
//插入
void InsertSeqList(SeqList* ps, DataType x, size_t pos);
//修改
void ModifySeqList(SeqList* ps, DataType x);
//打印
void PrintSeqList(SeqList* ps);



//初始化
void InitSeqList(SeqList* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
//顺序表的销毁
void DestorySeqList(SeqList* ps)
{
	assert(ps);
	if (ps->arr)
	{
		free(ps->arr);
		ps->capacity = ps->size = 0;
	}
}
//检查容量
static void CheckCapacity(SeqList* ps)
{
	if (ps->capacity == ps->size)
	{
		int newcapacity = ps->capacity == 0 ? 4 : (ps->capacity * 2);
		DataType* temp = (DataType*)realloc(ps->arr, newcapacity * (sizeof(DataType)));
		if (temp == NULL)
		{
			printf("%s", strerror(errno));
			return;
		}
		ps->arr = temp;
		ps->capacity = newcapacity;
	}
}
//尾部插入
void PushSeqListBack(SeqList* ps,DataType x)
{
	assert(ps);
	CheckCapacity(ps);
	ps->arr[ps->size] = x;
	ps->size++;
}
//尾部删除
void PopSeqListBack(SeqList* ps)
{
	assert(ps);
	assert(ps->arr>0);
	ps->size--;
}
//头部插入
void PushSeqListFront(SeqList* ps, DataType x)
{
	assert(ps);
	CheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	ps->arr[0] = x;
	ps->size++;
}
//头部删除
void PopSeqListFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);
	for (int i = 0; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
//查找
int FindSeqList(SeqList* ps, DataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
			return i;
	}
	printf("无该元素!\n");
	return -1;
}
//删除
void DeleteSeqList(SeqList* ps, DataType x)
{
	assert(ps);
	int pos=FindSeqList(ps, x);
	if (pos != -1)
	{
		for (int i = pos; i < ps->size-1; i++)
		{
			ps->arr[i] = ps->arr[i + 1];
		}
	}
	ps->size--;
}
//插入
void InsertSeqList(SeqList* ps, DataType x, size_t pos)
{
	assert(ps);
	CheckCapacity(ps);

	//一
	/*int end = ps->size - 1;
	while (end >= (int)pos)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}*/

	//二、
	size_t end = ps->size;
	while (end > pos)
	{
		ps->arr[end] = ps->arr[end - 1];
		end--;
	}

	ps->arr[pos] = x;
	ps->size++;
}
//修改
void ModifySeqList(SeqList* ps, DataType x)
{
	assert(ps);
	int pos=FindSeqList(ps, x);
	if (pos != -1)
	{
		ps->arr[pos] = x;
	}
}
//打印
void PrintSeqList(SeqList* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

Ending

数据结构中的第一个线性结构就更新完成了,就这样吧,下次见 ‍♂️
数据结构----线性表之顺序表_第2张图片

你可能感兴趣的:(数据结构与算法,数据结构,链表,算法)