数据结构之顺序表

Posted 北川_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之顺序表相关的知识,希望对你有一定的参考价值。

目录

顺序表概念

线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为顺序表(Sequential List)。其特点是,逻辑上相邻的数据元素,其物理次序也是相邻的
线性表的顺序存储结构是一种随机存取的存储结构,一般情况下采用数组存储。
解释下黑体字:假设现在已经实现了每个元素类型为int型的顺序表,并且插入了1、2、3、4、5五个元素。

64位平台下一个整型(int)占4个字节,元素1的后面是2,2的后面是3,我们可以清晰的看到元素1的地址后面就是2的地址,2的地址后面就是3的地址,每两个地址之间就相差它们自身占用空间字节数。
就像这样:

顺序表实现

静态顺序表的结构定义

#define N 500
typedef int SQDataType;

typedef struct SeqList

	SQDataType a[N];
	int size;
SLT;

静态顺序表在实际中是非常不适用的,空间给小了不够用,给多了可能会浪费,所以最好定义成动态的。

动态顺序表的结构定义

typedef int SQDatatype;

typedef struct SeqList

	SQDatatype* a;		//指向动态开辟数组的指针
	int size;			//有效数据的个数
	int capacity;		//容量空间大小
SLT;

typedef的作用是可以为某一类型自定义名称。编译器会将带有typedef关键字的SQDatatype解释成一个类型的标识符,即int型。这样做的好处是当顺序表中的存储元素是char类型时,只需要将int改为char即可,提高了程序的灵活性。
接下来定义一个结构体,结构体当中有三个成员。
第一个结构体成员为一个指向数组的指针,因为顺序表一般情况下采用数组存储。
第二个结构体成员变量size为数组中有效数据的个数。
假设数组动态开辟了4个整型空间,先插入1,2两个元素,此时有效数据2个,size = 2。

再插入3,4两个元素。

这时会发现我们再想插入数据时数组就越界了,空间不够了,所以需要第三个变量capacity,用来表示容量空间的大小。当有效数据个数size < 容量capacity时,随便插入数据,因为数组还有空间;当size == capacity时说明空间满了,再想插入数据需要扩容。

顺序表初始化

void SeqListInit(SLT* psl)

	assert(psl);	//psl不可能为NULL,对其进行断言
	psl->a = NULL;
	psl->size = psl->capacity = 0;

将指针a赋值为NULL,有效数据个数size和容量capacity都赋值为0即可。
为什么函数参数是结构体指针呢?
先看一个简单的例子:

void Swap1(int x, int y)

	int temp = x;
	x = y;
	y = temp;


int main(void)

	int a = 10;      //变量a
	int b = 20;      //变量b
	Swap1(a, b);      //Swap()函数将两个数字交换
	printf("a = %d, b = %d\\n", a, b);
	return 0;


可以看到打印在屏幕上的结果,两个变量的值并没有交换。

调试发现形参x和y确实交换了,但是a和b的值没有变化,为什么呢?

void Swap2(int* x, int* y)

	int temp = *x;
	*x = *y;
	*y = temp;


int main(void)

	int a = 10;
	int b = 20;
	Swap2(&a, &b);
	printf("a = %d, b = %d\\n", a, b);
	return 0;


这样a和b的值才会交换。
因为形参只是实参的一份临时拷贝,可以比较一下两次变量的地址。
这是Swap1()函数实参和形参的地址:

可以清晰的看到实参a,b与形参x,y使用的不是同一空间。
这是Swap2()函数实参和形参的地址:

发现实参a,b和形参x,y所用的是同一块空间,所以解引用取到x和y的值并且交换,自然也就交换了a和b的值。
上面改变了两个整型的值,需要传地址参数,结构体也一样,想改变结构体中的内容需要传递结构体的地址,用一个结构体指针接收。
断言<assert.h>
ANSI C实现了一个assert宏,当它被执行时,这个宏对表达式参数进行测试。如果它的值为假(零),它就向标准错误打印一条诊断信息并终止程序。
这条信息的格式是由编译器定义的,它将包含这个表达式和源文件的名字以及断言所在的行号。如果表达式为真(非零),将不会打印任何东西,程序继续执行。
初始化函数参数为结构体指针,指向结构体地址,这个地址不可能为空,所以可以对函数参数psl进行断言,如果psl为NULL,就会出现下面的结果:

顺序表打印

void SeqListPrint(SLT* psl)

	assert(psl);
	for (int i = 0; i < psl->size; ++i)
	
		printf("%d ", psl->a[i]);
	
	printf("\\n");

用一个for循环输出数组中每一个有效数据。

检查空间进行扩容

void CheckCapacity(SLT* psl)

	assert(psl);
	if (psl->size == psl->capacity)
	
		size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SQDatatype* temp = (SQDatatype*)realloc(psl->a, sizeof(SQDatatype) * newCapacity);
		if (temp == NULL)		//如果temp为NULL说明开辟空间失败
		
			printf("realloc fail");
			exit(-1);		//退出程序
		
		psl->a = temp;
		psl->capacity = newCapacity;
	

有效数据个数size和容量capacity相等需要扩容,如果容量capacity等于0,为其开辟4个整型空间,否则容量变为原来的二倍。
将新空间赋值给a,新容量赋值给capacity。

顺序表尾插

void SeqListPushBack(SLT* psl, SQDatatype x)

	assert(psl);
	CheckCapacity(psl);		//检查是否需要扩容
	psl->a[psl->size] = x;
	psl->size++;	//有效数据个数加一

要在顺序表尾部进行插入操作,插入元素下标的值和顺序表有效数据个数的值是相等的。

此时有效数据size为2,在2后面尾插个3,那么3的下标就是2,就是size的值。所以psl->a[psl->size] = x,插入完成后有效数据个数变为3个,让size的值加1。

顺序表尾删

void SeqListPopBack(SLT* psl)

	assert(psl);
	assert(psl->size > 0);
	psl->size--;

顺序表有元素的时候才可以进行删除操作,所以对顺序表的有效数据个数进行断言。如果顺序表中有元素,让有效数据个数减一即可。

顺序表头插

void SeqListPushFront(SLT* psl, SQDatatype x)

	assert(psl);
	CheckCapacity(psl);		//检查是否需要扩容
	for (int i = psl->size; i > 0; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[0] = x;		//将元素插入数组第一个位置
	psl->size++;		//有效数据个数加一

假设顺序表有1、2、3、4四个元素,此时头插一个x的值为6。

此时size == capacity,需要扩容。
扩容后:

将数组中的每个元素从最后一个开始赋值个后面一个。
即a[4] = a[3],a[3] = a[2] … … …… 直至跳出循环。
循环结束后:

将数组下标为0的位置赋值为6,此时size = 5,capacity = 8。

顺序表头删

void SeqListPopFront(SLT* psl)

	assert(psl);		//psl不可能为NULL,对其进行断言
	assert(psl->size > 0);		//存在有效数据才进行删除
	for (int i = 0; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;		//有效数据个数减一

从数组下标为1的元素开始,每个元素都赋值给前一个元素位置,让有效数据个数减一。

顺序表查找

查找顺序表中是否存在某一元素,若存在返回该元素下标,否则返回-1。

int SeqListFind(SLT* psl, SQDatatype x)

	assert(psl);		//psl不可能为NULL,对其进行断言
	for (int i = 0; i < psl->size; ++i)
	
		if (x == psl->a[i])
			return i;
	
	return -1;

在任意位置插入

void SeqListInsert(SLT* psl, size_t pos, SQDatatype x)

	assert(psl);
	assert(pos >= 0 && pos <= psl->size); //对插入位置pos取值进行断言
	CheckCapacity(psl);
	for (int i = psl->size; i > pos; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[pos] = x;		//将x赋值给下标为pos位置
	psl->size++;			//有效数据个数加一

假设要在下标为pos的位置插入一个元素,pos的值一定在0和有效数据个数之间,即pos的取值范围[0,size],pos值为0即头插,pos值为size即尾插。
将pos及其后面的元素向后挪动一位,将要插入的数据x赋值给下标为pos的位置。最后让有效数据的个数加一。

在任意位置删除

void SeqListErase(SLT* psl, size_t pos)

	assert(psl);		//对psl进行断言
	assert(pos >= 0 && pos < psl->size);	//对pos取值进行断言
	for (int i = pos; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;	//有效数据个数减一

想在任意位置删除时,pos的取值在[0,size)之间,因为数组最后一个元素的下标为size-1,假如删除size位置,数组越界了,所以这里是开区间。
让pos后面的位置都向前挪动一位,最后让有效数据的个数减一。

顺序表销毁

void SeqListDestory(SLT* psl)

	assert(psl);
	if (psl->a)
	
		free(psl->a);
		psl->a = NULL;
	
	psl->size = psl->capacity = 0;

将数组指针置空,并且将有效数据个数size和容量capacity也置空。

头文件

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

typedef int SQDatatype;

typedef struct SeqList

	SQDatatype* a;	
	int size;			//有效数据的个数
	int capacity;		//容量
SLT;

//初始化
void SeqListInit(SLT* psl);
//销毁
void SeqListDestory(SLT* psl);
//打印
void SeqListPrint(SLT* psl);
//扩容
void CheckCapacity(SLT* psl);
//尾插
void SeqListPushBack(SLT* psl, SQDatatype x);
//尾删
void SeqListPopBack(SLT* psl);
//头插
void SeqListPushFront(SLT* psl, SQDatatype x);
//头删
void SeqListPopFront(SLT* psl);
//查找
int SeqListFind(SLT* psl, SQDatatype x);
//任意位置插入
void SeqListInsert(SLT* psl, size_t pos, SQDatatype x);
//任意位置删除
void SeqListErase(SLT* psl, size_t pos);

源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"

void SeqListInit(SLT* psl)		//初始化

	assert(psl);
	psl->a = NULL;
	psl->size = psl->capacity = 0;


void SeqListDestory(SLT* psl)	//销毁

	assert(psl);
	if (psl->a)
	
		free(psl->a);
		psl->a = NULL;
	
	psl->size = psl->capacity = 0;


void SeqListPrint(SLT* psl)		//打印

	assert(psl);
	for (int i = 0; i < psl->size; ++i)
	
		printf("%d ", psl->a[i]);
	
	printf("\\n");


void CheckCapacity(SLT* psl)		//检查空间进行扩容

	assert(psl);
	if (psl->size == psl->capacity)
	
		size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SQDatatype* temp = (SQDatatype*)realloc(psl->a, sizeof(SQDatatype) * newCapacity);
		if (temp == NULL)
		
			printf("realloc fail");
			exit(-1);
		
		psl->a = temp;
		psl->capacity = newCapacity;
	


void SeqListPushBack(SLT* psl, SQDatatype x)	//尾插

	assert(psl);
	CheckCapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;


void SeqListPopBack(SLT* psl)		//尾删

	assert(psl);
	assert(psl->size > 0);
	psl->size--;


void SeqListPushFront(SLT* psl, SQDatatype x)	//头插

	assert(psl);
	CheckCapacity(psl);
	for (int i = psl->size; i > 0; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[0] = x;
	psl->size++;


void SeqListPopFront(SLT* psl)		//头删

	assert(psl);
	assert(psl->size > 0);
	for (int i = 0; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;


int SeqListFind(SLT* psl, SQDatatype x)		//查找

	assert(psl);
	for (int i = 0; i < psl->size; ++i)
	
		if (x == psl->a[i])
			return i;
	
	return -1;


void SeqListInsert(SLT* psl, size_t pos, SQDatatype x)	//pos位置插入

	assert(psl);
	assert(pos >= 0 && pos <= psl->size);
	CheckCapacity(psl);
	for (int i = psl->size; i > pos; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[pos] = x;
	psl->size++;


void SeqListErase(SLT* psl, size_t pos)		//pos位置删除

	assert(psl);
	assert(pos >= 0 && pos < psl->size);
	for (int i = pos; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;

以上是关于数据结构之顺序表的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之双链表

数据结构之单链表

数据结构--顺序表及其实现

数据结构 动态存储的顺序表

数据结构与算法 顺序表 链表

线性表