顺序表详解

预备知识:

C语言常见概念,循环,数组,函数,指针,结构体指针,动态内存管理

最简单的数据结构:数组

数组作为最简单的数据结构,具有存储数据的功能,但是使用数组存储数据时,无法动态的定义数组的长度,如果给小了,容易出现数组空间不够的情况,如果给大了,又有可能会导致空间的浪费,而且对数组中的元素进行插入删除等操作时比较麻烦。

顺序表的概念及结构

顺序表是线性表的一种,所谓线性表,就是指数据以连续的直线存储,顺序表在物理结构和逻辑结构上都是线性的。顺序表分为两种,静态顺序表和动态数据表,本文主要介绍动态数据表及其实现。

顺序表的实现

接下来将实现以下功能

//顺序表的初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);
//顺序表的打印
void SLPrint(SL ps);

//顺序表的容量检查并扩容
void SLCheckCapacity(SL* ps);

//顺序表的尾部插入
void SLPushBack(SL* ps, SLDataType x);
//顺序表的尾部删除
void SLPopBack(SL* ps);
//顺序表的头部插入
void SLPushFront(SL* ps, SLDataType x);
//顺序表的头部删除
void SLPopFront(SL* ps);

//指定位置之前插入
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置删除
void SLErase(SL* ps, int pos);
//元素查找
int SLFind(SL* ps, SLDataType x);

顺序表的创建

对于一个数组,插入和删除元素比较麻烦,但是对于线性表,这个问题就迎刃而解了。首先,我们先来创建一个顺序表SeqList,一个顺序表应包含一个指针,一个变量size记录有效的数据个数,另一个变量capacity记录当前顺序表的容量,并且是以结构体的方式来实现的。

例如,我需要创建一个int类型的顺序表用来存储int类型的数据

struct Seqlist
{
	int* arr;
	int size; //有效数据个数
	int capacity; //顺序表容量
};

那么既然能存int类型的数据,当然也可以存储char类型,float类型,double类型的数据等,为了避免后期修改工程过于庞大,所以对顺序表的定义进行一些优化

typedef int SLDataType;

struct Seqlist
{
	SLDataType* arr;
	int size; //有效数据个数
	int capacity; //顺序表容量
};

如此一来,当我们想要改变数据类型时,只需要改变typedef后的类型,就可以改变之后所有使用到的类型(注:size 和 capacity 并不需要用SLDataType代替,因为不管前面的变量怎么变这两个变量都是用来计数的,所以用int类型)

同理,我们将结构体也进行typedef定义

typedef int SLDataType;

typedef struct Seqlist
{
	SLDataType* arr;
	int size; //有效数据个数
	int capacity; //顺序表容量
}SL;

顺序表的初始化

当我们传入结构体指针 *ps时,第一个问题就是传入的指针不能是NULL,否则就会报错,

所以第一步就要进行assert断言。然后我们对两个计数器置零,对指针置空,这样初始化就完成了。

//顺序表的初始化
void SLInit(SL* ps)
{
    assert(ps);
    ps->size = ps->capacity = 0;
    ps->arr = NULL;
}

顺序表的尾插

首先,还是要进行assert断言,判断是否尾空指针。

在插入数据前,我们要先判断是否有足够的空间来插入数据,如果空间不够,我们就要手动来开辟空间来存放数据,那么就存在一个问题,如果空间不够,我们应该开辟多大的空间呢?如果开辟的过多,会造成空间的浪费,如果开辟的太少,又有可能导致空间不足。一般来说,开辟的空间为原空间的2~3倍左右是最佳的。

void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
    if (ps->size == ps->capacity)
    {
        int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLdataType* temp = (SLdataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType);
        assert(temp);
    }
}

当有效的数据个数已经达到了顺序表容量的时候,我们就要对顺序表进行扩容 

此时,如果直接对capacity进行操作会改变原来capacity的值,所以我们建立一个新的变量来表示

需要扩容的数量,如果capacity的值为0,那么我们将其置为4,反之,我们将其空间乘2。

在确定了需要扩容的数量后,我们使用realloc进行空间开辟,由于realloc的返回值为void* 类型,所以我们将其强制类型转化为我们自定义的类型SLDataType* ,此外,需要注意realloc的空间开辟有可能会失败,当空间开辟失败时会返回空指针,所以要对temp进行assert断言。

void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
    if (ps->size == ps->capacity)
    {
        int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLdataType* temp = (SLdataType*)realloc(ps->arr, 2 * newcapacity * sizeof(SLDataType);
        assert(temp);
		ps->arr = temp; //将开辟好的空间赋给arr
		ps->capacity = newcapacity; 
    }
	ps->arr[ps->size++] = x;
}

在开辟好空间之后,让arr去接收这个空间,并且更新新的容量newcapacity,再将需要写入的数据写入创建好的数组,最后让size自增。

顺序表的销毁

  在使用完顺序表后,需要将其进行销毁

void SLDestroy(SL* ps)
{
    assert (ps);
    free(ps->arr);
    ps = NULL;
    ps->capacity = ps->size = 0;
}

首先进行assert断言,在之前我们使用realloc函数开辟了一块空间,那么在销毁的时候就要对空间释放归还给操作系统, 然后将ps置为空指针,此处应该free(ps->arr)而不是free(ps)因为ps中只有arr开辟了新的空间,而size和capacity并没有申请空间。然后将两个计数器归零即可。

顺序表中元素的打印

void SLPrint(SL ps)
{
    for (int i = 0; i < ps.size; i++)
    {
        printf("%d", ps.arr[i]);
    }    
    printf("\n");
}

 此处直接传入结构体,通过循环打印顺序表中的元素。

顺序表的头插

  不同于尾插,头插需要将所有的数字向后移动一位,此时又需要判断空间是否足够,所以我们可以将空间判断作为一个单独的函数,每次使用调用即可。

顺序表的空间容量检查并扩容

void SLCheckCapacity(SL* ps)
{
    assert(ps);
    if (ps->size == ps->capacity)
    {
        int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        SLDataType* temp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * newcapacity;
        ps->arr = temp;
        ps->capacity = newcapacity;
    }
}

 头插操作

void SLPushFront(SL* ps, SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    for (int i = ps->size; i >= 0; --i)
    {
        ps->arr[i + 1] = ps->arr[i];
    }
    ps->arr[0] = x;
    ps->size++;
}

在检查完空间后,我们从最后一位开始依次往前吧把前一位的数赋给后一位,实现后移,最后在第一位插入需要插入的数字,最后将有效位数size自增。

顺序表的尾部删除 

void SLPopBack(SL* ps)
{
    assert(ps);
    assert(ps->size);
    ps->size--;
}

在尾部删除时,首先断言ps,在尾删时,我们要确保顺序表中又元素,所以需要size不能为0,要删除一个数据,一种方式是将数据直接删除,另一种是将该数据标记为可覆盖空间,也就是直接将size--,之后再往顺序表中插入数据时,相当于直接覆盖了需要删除的数据。

顺序表的头部删除

void PspFront(SL* ps, SLDataType x)
{
    assert(ps);
    for (int i = 1; i < ps->size; i++)
    {
        ps-arr[i - 1] = ps->arr[i];
    }
    ps->size--;
}

对于头删,我们只需要把第二个数据覆盖到第一个数据,依次直到最后。

在指定位置之前插入

void SLInsert(SL* ps, int pos, SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    assert(pos >= 0 && pos <= ps->capacity); //检查需要插入的位置
    for (int i = ps->size; i > pos; i++)
    {
        ps-arr[i] = ps-arr[i - 1];
    }
    ps->arr[pos] = x;
    ps->size++;
}

首先我们要保证插入的位置是在我们开辟的空间capacity内,然后将和头插的方法类似,只不过头插是从第一个数开始往后移动,而这个是在指定位置之后的数往后移动。

在指定位置删除数据

void SLErase(SL* ps, int pos)
{
    assert(ps);
    assert(pos >= 0 & pos < ps->size);
    for (int i = pos; i < ps->size; i++)
    
        //ps->arr[i - 1] = ps->arr[i];//第一位是记为1
        ps->arr[i] = ps->arr[i + 1];//数组位置,第一位是0
    }
    ps->size--;
}

和插入数据略有不同,在删除数据是,应该要使pos小于size,因为删除只能删除已有的数据。通过循环将需要删除的数据进行覆盖(此处给出两种位置),最后别忘了有效个数size--。

元素查找

int SLFind(SL* ps, SLDataType x)
{
    assert(ps);
    for (int i = 0; i < ps->size; i++)
    {
        if (x == ps->arr[i])
        {
            return i;
        }
    }
    return -1;
}

查找元素只需要对数组进行遍历即可,返回-1表示没找到。

源代码

头文件SeqList.h

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

typedef int SLDataType;

typedef struct Seqlist
{
	SLDataType* arr;
	int size;
	int capacity;
}SL;

//顺序表的初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);
//顺序表的打印
void SLPrint(SL ps);

//顺序表的容量检查并扩容
void SLCheckCapacity(SL* ps);

//顺序表的尾部插入
void SLPushBack(SL* ps, SLDataType x);
//顺序表的尾部删除
void SLPopBack(SL* ps);
//顺序表的头部插入
void SLPushFront(SL* ps, SLDataType x);
//顺序表的头部删除
void SLPopFront(SL* ps);

//指定位置之前插入
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置删除
void SLErase(SL* ps, int pos);
//元素查找
int SLFind(SL* ps, SLDataType x);

函数实现文件SeqList.c

#include"SeqList.h"

int i = 0;//全局变量

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

void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

void SLPrint(SL ps)
{
	for (i = 0; i < ps.size; i++)
	{
		printf("%d ", ps.arr[i]);
	}
	printf("\n");
}

void SLCheckCapacity(SL* ps)
{
	assert(ps);
	int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
	if (ps->size == ps->capacity)
	{
		SLDataType* temp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
		assert(temp);
		ps->arr = temp;
		ps->capacity = newcapacity;
	}
}

void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	ps->arr[ps->size++] = x;
}

void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);

	--ps->size;
}

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	for (i = ps->size; i >= 0; i--)
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[0] = x;
	ps->size++;
}

void SLPopFront(SL* ps)
{
	assert(ps);
	for (i = 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	--ps->size;
}

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	assert(pos <= ps->capacity && pos >= 0);//检查需要插入的位置
	for (i = ps->size; i >= pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (i = pos; i < ps->size; i++)
	{
		//ps->arr[i - 1] = ps->arr[i];//第一位记为1
		ps->arr[i] = ps->arr[i + 1];//数组位置,第一位是0
	}
	--ps->size;
}

int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;
}



测试代码test.c

#include"SeqList.h"

void SqLtest()
{
	SL st;
	SLInit(&st);//创建

	for (int i = 1; i <= 10; i++)
	{
		SLPushBack(&st, i);//插入十个元素
	}
	SLPrint(st);

	SLPushFront(&st, 0);//头部插入
	SLPrint(st);

	SLPopBack(&st);//尾删
	SLPrint(st);

	SLPopFront(&st);//头删
	SLPrint(st);

	SLInsert(&st, 1, 2);//指定位置插入
	SLPrint(st);

	SLErase(&st, 1);//指定位置删除
	SLPrint(st);

	int find = 0;
	printf("请输入要查找的数字\n");
	scanf("%d", &find);
	int ret = SLFind(&st, find);
	if (ret >= 0)
		printf("找到了,在第%d位", find);
	else
		printf("没找到!");

	SLDestroy(&st);//销毁
}

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

测试结果

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值