【数据结构】一、顺序表

本文详细介绍了顺序表的概念,包括静态和动态顺序表的特性。重点讨论了动态顺序表的实现,包括malloc、realloc和calloc等内存管理函数的使用,并设计了动态顺序表的结构体,涵盖了初始化、插入、查找、删除等操作。同时,文章提到了顺序表的优缺点及其在空间利用和存取速度上的优势。最后,概述了动态顺序表的完整代码实现,涉及三个文件:Seqlist.h、SeqList.cpp和main.cpp。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概念

顺序表:是用一段物理地址连续的存储单元依次存储数据元素的线性结构,即逻辑相邻,物理相邻

顺序表分为:

  • 静态顺序表使用定长的数组进行存储,和数组操作类似。
  • 动态顺序表使用动态开辟的内存进行存储,长度可变,不固定。

顺序表的特点:

【1.】优点:

  • 空间连续,存储数据密度大,空间利用率高
  • 存取速度快,通过下标来直接存储,随机访问,根据下标查找元素O(1)。
  • 不频繁申请或者释放内存,没有内存碎片的问题。
  • 代码简单

【2.】缺点:

  • 插入和删除比较慢,时间复杂度为O(n),比如:插入或者删除一个元素时,整个表需要遍历移动元素来重新排一次顺序。
  • 有空间限制,需要先一次性分配一定的空间。当需要存取的元素个数可能多于顺序表的元素个数时,会出现"溢出"问题;当元素个数远少于预先分配的空间时,空间浪费巨大。

动态顺序表需要动态开辟内存和扩容,就会使用到下面几种函数:

malloc函数

malloc函数,功能是动态分配内存,内存未被初始化,函数原型如下:

# include<stdlib.h>
void * malloc(unsigned int size);

size表示申请的内存大小,它并不知道内存中应该存储何种数据类型,所以需要对其进行进行类型说明,一般是

(数据类型指针)malloc(长度*数据类型)

realloc函数

realloc函数,用来扩容,函数原型如下:

void realloc(void *ptr,unsigned int newsize);

ptr是指向原来内存块的指针,按照size大小进行扩容,原始内存中的数据不变。使用方式和malloc类似。

【calloc函数】

calloc函数,功能是分配内存,并将分配内存的空间每一位初始化为0,内部用memset实现初始化。函数原型为:

void* calloc(unsigned int n,unsigned int size);

表示开辟n个为size大小的连续空间。一般不用它,都用malloc函数开辟。

二、实现

我们主要讲解动态顺序表的实现,因为静态顺序表和数组操作类似。

(一)设计

【一、动态顺序表的结构体设计:】

  • 要实现长度不固定,需要引入扩容机制,所以需要有个成员保存动态内存的地址,定义为int* elem。
  • 长度不固定,所以需要一个变量保存现在顺序表的总长度,定义为size。
  • 需要有一个变量保存当前的有效数据,即格子中放的数据的个数,定义为length。

那么整个顺序表的图示:

在这里插入图片描述
当我们存放数据时:

在这里插入图片描述

【二、顺序表功能设计:】

对顺序表有一定的操作,如初始化,增加,删除等,我们设计主要的操作功能,在实现设计时,一定要对参数进行一定的判断,增强代码的健壮性:

【1. 初始化操作】

初始化:目的是把顺序表空间建立起来,三个成员初始化。

  • 进行空指针的判断,用断言,如果为空就直接返回,常量放在左边,变量放在右边是一个好的编码风格。
  • 指针指向malloc开辟的空间,其他两个成员进行初始化,要记住每次处理三个成员。
//1.初始化,将结构体的三个成员都进行一个初始化
void InitSeq(SqList ps)
{
	//判空
	assert(ps != NULL);
	if(ps == NULL)
	{
		return;
	}
	//开辟SIZE格子的大小
	ps->elem = (int*)malloc(sizeof(int)*SIZE);
	ps->size = SIZE;
	ps->length = 0;
}

【2. 向指定的位置插入数据val,O(N)】

  • 判断参数是否合法,空指针,位置的判断,是否现在已经没有位置可以插入了。

  • 所以需要写判满函数,判满函数写为内部的,即static修饰的,判断当前顺序表长度是否等于有效数据个数即可。

    //2-1.判断格子是否满了,写为内部函数
    static bool IsFull(SqList ps)
    {
    	return ps->length == ps->size;
    }
    
  • 判断顺序表是否满了,满了需要扩容,所以需要扩容函数,也写成私有的,扩容到原来的两倍即可。使用realloc扩容,扩容后记得将总长度扩大。 那么STL里面扩容是2倍还是1.5倍?答案都对,VS下为1.5倍,gcc为2倍。

    //2-2.扩容函数
    static void ReLength(SqList ps)
    {
    ps->elem = (int*)realloc(ps->elem,sizeof(int)*ps->size*2);//表示在原来内存的基础上增加内存
    ps->size *= 2;
    }
    
  • 数据从后往前移动。然后pos位置插入即可。

//2.向pos的位置插入val数值,返回是否插入成功,需要对参数进行判断
bool InsertSeq(SqList ps,int pos,int val)
{
	assert(ps!=NULL);
	if(ps==NULL)
		return false;
	if(pos < 0 || pos > ps->length)
	{
		return false;
	}
	if(IsFull(ps))//判满
	{
		ReLength(ps);//扩容
	}
	//后移插入
	for(int i = ps->length-1;i >= pos;i--)
	{
		ps->elem[i+1]=ps->elem[i];
	}
	ps->elem[pos] = val;
	ps->length++;
	return true;
}

【3. 按元素查找下标,O(N)】

当前顺序表元素无序,所以顺序找,O(n)复杂度。如果序列数据有序折半查找O(logn),哈希O(1)数据存放的位置和值相关,不常用,因为有哈希冲突。

//3.查找从头开始的第一个key,成功返回下标,失败返回-1
int Search(SqList ps,int key)
{
	assert(ps != NULL);
	if(ps == NULL)
		return -1;
	for(int i = 0;i< ps->length;i++)
	{
		if(ps->elem[i] == key)
			return i;
	}
	return -1;
}

【4. 删除pos位置的值,O(N)】

  • 判断下标的合法性
  • 从pos位置将后面的数据往前挪即可。
//4.删除pos位置的值
bool DeletePos(SqList ps,int pos)
{
   assert(ps != NULL);
   if(ps == NULL)
   	return false;
   
   if(pos < 0 || pos > ps->length)
   {
   	return false;
   }
   //前移删除
   for(int i = pos;i < ps->length-1; i++)
   {
   	ps->elem[i] = ps->elem[i+1];
   }
   ps->length--;//记住,不要忘了。
   return true;
}

【5. 删除key,O(N)】

  • 调用查找函数,得到下标pos。
  • 调用根据下标位置删除元素的函数,删除pos位置的元素。
//5.删除从头开始的第一个key
bool DeleteVal(SqList ps,int key)
{
	assert(ps != NULL);
	if(ps == NULL)
		return false;
	int pos = Search(ps,key);
	return DeletePos(ps,pos);
}

【6. 销毁】

判断是否存在内存泄露,可以调两次测试一下,O(N)

  • 内存的释放,free函数。
  • 将其他成员的值重新初始化,指针指向空,长度变为0。
//6.销毁
void Destroy(SqList ps)
{
	assert(ps != NULL);
	free(ps->elem);
	ps->elem = NULL;
	ps->size = 0;
	ps->length = 0;
}

【7. 打印,O(N)】

循环打印顺序表数据即可。

//打印函数
void Show(SqList ps)
{
	for(int i = 0;i < ps->length;i++)
	{
		printf("%d ",ps->elem[i]);
	}
	printf("\n");
}

(二)代码

我们给出完整的代码,将函数声明写到另一个头文件中,测试也写到另一个.cpp文件中,所以有三个文件:

【 Seqlist.h 】

#pragma once//防止头文件重复包含
# define SIZE 20 //格子的大小
typedef struct SeqList
{
	int* elem;//指向动态内存的地址
	int size;//保存顺序表的大小
	int length;//保存有效数据长度
}SeqList,*SqList;

//1.初始化
void InitSeq(SqList ps);
//2.向pos的位置插入val数值,返回是否插入成功
bool InsertSeq(SqList ps,int pos,int val);
//3.查找从头开始的第一个key,成功返回下标,失败返回-1
int Search(SqList ps,int key);
//4.删除pos位置的值
bool DeletePos(SqList ps,int pos);
//5.删除从头开始的第一个key
bool DeleteVal(SqList ps,int key);
//6.销毁
void Destroy(SqList ps);
//7.打印函数
void Show(SqList ps);

【SeqList.cpp】

# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include "Seqlist.h"

//1.初始化,将结构体的三个成员都进行一个初始化
void InitSeq(SqList ps)
{
	//判空
	assert(ps != NULL);
	if(ps == NULL)
	{
		return;
	}
	//开辟SIZE格子的大小
	ps->elem = (int*)malloc(sizeof(int)*SIZE);
	ps->size = SIZE;
	ps->length = 0;
}
//2-1.判断格子是否满了,写为内部函数
static bool IsFull(SqList ps)
{
	return ps->length == ps->size;
}
//2-2.扩容函数
static void ReLength(SqList ps)
{
	ps->elem = (int*)realloc(ps->elem,sizeof(int)*ps->size*2);//表示在原来内存的基础上增加内存
	ps->size *= 2;
}
//2.向pos的位置插入val数值,返回是否插入成功,需要对参数进行判断
bool InsertSeq(SqList ps,int pos,int val)
{
	assert(ps!=NULL);
	if(ps==NULL)
		return false;
	if(pos < 0 || pos > ps->length)
	{
		return false;
	}
	if(IsFull(ps))//判满
	{
		ReLength(ps);//扩容
	}
	//后移插入
	for(int i = ps->length-1;i >= pos;i--)
	{
		ps->elem[i+1]=ps->elem[i];
	}
	ps->elem[pos] = val;
	ps->length++;
	return true;
}
//3.查找从头开始的第一个key,成功返回下标,失败返回-1
int Search(SqList ps,int key)
{
	assert(ps != NULL);
	if(ps == NULL)
		return -1;
	for(int i = 0;i< ps->length;i++)
	{
		if(ps->elem[i] == key)
			return i;
	}
	return -1;
}
//4.删除pos位置的值
bool DeletePos(SqList ps,int pos)
{
	assert(ps != NULL);
	if(ps == NULL)
		return false;
	
	if(pos < 0 || pos > ps->length)
	{
		return false;
	}
	//前移删除
	for(int i = pos;i < ps->length-1; i++)
	{
		ps->elem[i] = ps->elem[i+1];
	}
	ps->length--;//记住,不要忘了。
	return true;
}
//5.删除从头开始的第一个key
bool DeleteVal(SqList ps,int key)
{
	assert(ps != NULL);
	if(ps == NULL)
		return false;
	int pos = Search(ps,key);
	return DeletePos(ps,pos);
}
//6.销毁
void Destroy(SqList ps)
{
	assert(ps != NULL);
	free(ps->elem);
	ps->elem = NULL;
	ps->size = 0;
	ps->length = 0;
}
//打印函数
void Show(SqList ps)
{
	for(int i = 0;i < ps->length;i++)
	{
		printf("%d ",ps->elem[i]);
	}
	printf("\n");
}

【main.cpp】

# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include "Seqlist.h"


int main()
{
	SeqList ps;
	InitSeq(&ps);
	for(int i=0;i<10;i++)
	{
		InsertSeq(&ps,i,i);
	}
	printf("插入0~9的数值:\n");
	Show(&ps);
	printf("---------------\n");
	int pos= Search(&ps,3);
	printf("查找3的位置为:%d\n",pos);
	printf("---------------\n");
	DeletePos(&ps,pos);
	printf("按照下标删除元素3:\n");
	Show(&ps);
	printf("---------------\n");
	DeleteVal(&ps,5);
	printf("按照元素删除5:\n");

	Show(&ps);
	printf("---------------\n");
	Destroy(&ps);
	printf("销毁\n");
	Destroy(&ps);
	Show(&ps);
}

代码运行结果:
在这里插入图片描述

加油哦!🍝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值