【数据结构】顺序表

2024-01-03 07:39:16

简单不先于复杂,而是在复杂之后。

在这里插入图片描述

1. 线性表

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

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…

线性表在逻辑上是线性结构,也就是说连续的一条直线。

但是在物理结构上不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

在这里插入图片描述

2. 顺序表

2.1 概念及结构

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

顺序表一定是连续存储数据的,不能跳跃。

顺序表一般可以分为:

  1. 静态顺序表:使用定长数组存储元素。

    以下源代码在SeqList.h

    #pragma once
    //确保头文件只被包含一次,避免被重复包含
    
    #define N 10
    typedef int SLDataType;
    
    //静态顺序表
    struct SeList
    {
    	SLDataType a[N];//定长数组
    	int size;//存储数据的个数
    };
    

    静态的顺序表是有缺陷的,静态顺序表在创建时需要指定一个固定的大小,这个大小通常在编译时就确定了。这就意味着,一旦分配的空间用完,无法动态调整表的大小。如果实际数据量超过了静态顺序表的大小,就可能导致溢出或者需要重新定义一个更大的静态顺序表,这会浪费内存或者导致程序运行错误。

  2. 动态顺序表:使用动态开辟的数组存储。

    //动态顺序表
    typedef int SLDataType;
    
    struct SeList
    {
    	SLDataType* a;  //指向动态开辟的数组
    	int size;		//有效数据的个数
    	int capacity;	//存储空间的大小
    	//空间不够则增容
    };
    

    动态顺序表可以动态分配空间的大小,可以扩容。

    一般是扩容2倍比较合适,其他倍数也可行,只不过扩容扩多了会存在空间浪费,扩少了就会导致频繁扩容,效率损失,所以扩容2倍比较合适。

2.2 接口实现

静态顺序表只适用于确定知道需要存多少数据的场景。

静态顺序表的定长数组导致 N 定大了,空间开多了浪费,开少了不够用。

所以现实中基本都是使用动态顺序表,根据需要动态地分配空间大小。

数据结构是在内存中管理数据:增删查改。

有一个原则:要控制顺序表,实现增删查改的访问等操作,不要直接访问结构体,要通过对应的函数。

//动态顺序表
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;  //指向动态开辟的数组
	int size;		//存储数据的个数
	int capacity;	//存储空间的大小
	//空间不够则增容
}SL;//实现接口更方便

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"

void TestSeqList1()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPushBack(&sl, 6);

	SLPrint(&sl);

	SLPushFront(&sl, 10);//头插有代价,尽量少用,时间复杂度为 O(N)
	SLPushFront(&sl, 20);
	SLPushFront(&sl, 30);

	SLPrint(&sl);

	SLDestory(&sl);
}

void TestSeqList2()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPrint(&sl);

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


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

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

	SLPushBack(&sl, 10);
	SLPushBack(&sl, 20);
	SLPushBack(&sl, 30);


	SLDestory(&sl);

}

void TestSeqList3()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPrint(&sl);

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

}

TestSeqList4()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPushBack(&sl, 5);
	SLPrint(&sl);

	SLInsert(&sl, 0, 30);
	SLPrint(&sl);

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


	//int x = 0;
	//scanf("%d", &x);
	//int pos = SLFind(&sl, x);
	//if (pos != -1)
	//{
	//	SLInsert(&sl, pos, 10 * x);
	//}
	//SLPrint(&sl);

}


int main()
{
	
	//TestSeqList1();
	//TestSeqList2();
	//TestSeqList3();
	TestSeqList4();


	return 0;
}

SeqList.h

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

//动态顺序表
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;  //指向动态开辟的数组
	int size;		//存储数据的个数
	int capacity;	//存储空间的大小
	//空间不够则增容
}SL;

//顺序表初始化
void SLInit(SL* psl);

//顺序表销毁
void SLDestory(SL* psl);

//增删查改

//顺序表打印
void SLPrint(const SL* psl);

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

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

//检查容量
void SLCheckCapacity(SL* psl);

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

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

//搜索,找到返回下标,没找到返回-1
int SLFind(SL* psl, SLDataType x);

//在 pos 位置插入 x(可以代替头插尾插)
void SLInsert(SL* psl, size_t pos, SLDataType x);

//删除 pos 位置的值
void SLErase(SL* psl, size_t pos);

//修改 pos 位置的值
void SLModify(SL* psl, size_t pos, SLDataType x);

SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"

void SLPrint(const SL* psl)
{
	assert(psl);
	for (int i = 0; i < psl->size; ++i)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}


void SLInit(SL* psl)
{
	assert(psl);

	psl->a = NULL;
	psl->capacity = psl->size = 0;
}

void SLDestory(SL* psl)
{
	assert(psl);

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

void SLPushBack(SL* psl, SLDataType x)
{
	assert(psl);

	SLCheckCapacity(psl);
		
	psl->a[psl->size] = x;
	psl->size++;
}


void SLPushFront(SL* psl, SLDataType x)
{
	assert(psl);
	SLCheckCapacity(psl);

	//挪动数据->从后往前挪
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}
	psl->a[0] = x;
	psl->size++;
}

void SLCheckCapacity(SL* psl)
{
	//扩容是以空间换时间
	//缩容是以时间换空间
	//坚决不缩容

	//检查容量
	if (psl->size == psl->capacity)
	{
		int newCapcity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = realloc(psl->a, newCapcity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			//return;
			exit(-1);
		}
		psl->a = tmp;
		psl->capacity = newCapcity;
	}
}


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

	//温柔的检查
	if (psl->size == 0)
	{
		return;
	}

	暴力的检查
	//assert(psl->size);

	psl->size--;
}


void SLPopFront(SL* psl)
{
	assert(psl);
	SLCheckCapacity(psl);

	int begin = 0;
	while (begin <= psl->size - 2)
	{
		psl->a[begin] = psl->a[begin + 1];
		begin++;
	}
	if (psl->size == 0)
	{
		return;
	}
	psl->size--;
}

int SLFind(SL* psl, SLDataType x)
{
	assert(psl);

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

	return -1;
}

void SLInsert(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos <= psl->size);

	SLCheckCapacity(psl);

	//挪动数据
	size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end - 1];
		end--;
	}
	if (pos == 0)
	{
		psl->a[pos + 1] = psl->a[pos];
	}
	psl->a[pos] = x;
	psl->size++;
}

void SLErase(SL* psl, size_t pos)
{
	assert(psl);

	size_t cur = pos + 1;
	while (cur < psl->size)
	{
		psl->a[cur - 1] = psl->a[cur];
		cur++;
	}
	psl->size--;
}

void SLModify(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos < psl->size);

	psl->a[pos] = x;
}

文章来源:https://blog.csdn.net/Lixinze__/article/details/135353896
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。