C语言数据结构:顺序表

1.什么是顺序表

顺序表本质就是数组,就是用数组来存储数据,对数组进行增删查改的管理。

顺序表一般可以分为:

1.静态顺序表:使用已经定好长度的数组

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

2.顺序表的实现(动态顺序表)

重点实现动态的顺序表,静态的顺序表实践中没有任何的价值,所以不实现。

2.1顺序表的定义

顺序表结构体的定义:

typedef int SLDataType;
typedef struct SeqList
{
     SLDataType* Data;
     int size;//记录存储的有效数据的个数
     int capacity;//空间的大小
}SL;

将类型进行typedef重定义,方便后面我们想存其他类型的时候可以更换,size的作用是记录有效数据的个数,方便后续的管理,capacity记录空间的大小,当capacity和size相等时就说明存储数据的空间已经满了,需要扩容。

 2af4ac7ed5864b19a4ac7b5c047ceb2c.png

2.2初始化和销毁

1.初始化:

void SLInit(SL* ps)
{
    assert(ps);//断言一下指针是否为空
    ps->Data = NULL;
    ps->size = 0;
    ps->capacity = 0;
}

在初始化这部分,将指针设为空,将size和capacity设置为0。参数要用到指针的原因是因为形参的改变不会影响到实参。assert的作用就是断言,当assert的参数为假时就会报错,并且指出代码具体出错的所在行数。

2.销毁:

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

在顺序表销毁的时候重点就是将堆上申请的空间给释放掉,不然会造成内存泄漏。

2.3扩容:

static void SLCheckCapacity(SL* ps)
{
    assert(ps);
    if( ps->size == ps->capacity )//扩容
    {
        if( ps->capacity == 0 )
        {
            ps->Data = (SLDataType*)malloc(sizeof(SLDataType) * 4);
            ps->capacity = 4;
        }
        else
        {
            SLDataType* tmp = (SLDataType*)realloc(ps->Data,sizeof(SLDataType) * 2 * ps->capacity);
            ps->Data = tmp;
            ps->capacity *= 2;
        }

        if(ps->Data == NULL)//扩容失败,直接终止掉程序
        {
            perror("SLCheckCapacity failed");
            exit(-1);
        }
    }
}

由于在初始化的时候没有选择在堆上申请空间,所以在扩容部分麻烦了点。扩容最重要的一点就是检查一下扩容是否成功。

2.4尾插,头插:

1.尾插:

尾插可以直接在size的位置插入,如下图中数据5:

插入数据后,size++,代表着有效数据个数增加,如下图:

当size和capacity相等时,说明空间已经满了,需要进行扩容,如下图:

void SLPushBack(SL* ps,SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);//检查容量,是否需要扩容
    ps->Data[ps->size] = x;
    ps->size++;//增加数据个数
}

2.头插:

在头部的插入需要将数据往后去挪,如下图我们要在0的前面插入数据:

将数据往后挪后,就可以在数组0号下标处插入数据,也就是头插,如下图:

插入数据后的重点就是szie需要增加,如下图插入数据-1后:

当size和capacity相等后也需要进行扩容,从上图可以看出顺序表头插需要数据向后挪动代价是比较大的。

void SLPushFront(SL* ps,SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    int end = ps->size - 1;

    while(end >= 0)//重后往前挪
    {
        ps->Data[end + 1] = ps->Data[end];
        end--;
    }
    ps->Data[0] = x;
    ps->size++;
}

2.5尾删,头删

1.尾删:

如果我们想要尾删下图中的数据4,只需要将size--即可

size统计的是有效个数,将size--后尾部的数据就被删除了,我们不需要将数据给设置为0这一步操作,万一尾部的数据就是0或者顺序表如果存的是其他数据呢?所以这一步我们完全是不需要去写的。

尾插的重点就是如果size减成0后我们就不能再去删除了,否则size变成了负数我们再去插入数据就会造成非法访问,如下图:

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

2.头删:

如果我们想删除下图中的数据0,需要数据往前挪动。

挪动后最重要的是将size--,如下图

头删完成,如下图:

头删的重点和尾删一样,需要检查一下size数据的有效个数

void SLPopFront(SL* ps)
{
    assert(ps);
    assert(ps->size > 0);
    int pos = 0;
    while(pos < ps->size)
    {
        ps->Data[pos] = ps->Data[pos + 1];
        pos++;
    }
    ps->size--;
}

2.6在指定位置插入和删除

1.在指定位置插入:

如果我们想在下图数据中2的位置插入一个10,我们需要将包括2在内的后面所有数据都要向后挪动

挪动完成后就可以插入数据数据了,最后将size++

这样插入数据就完成了

在写代码前需要注意的点:

1.先检查一下容量;

2.检查pos,pos的值必须大于等于0和小于等于size(等于0的时候是头插,等于size是尾插)

void SLInsert(SL* ps,int pos,SLDataType x)
{
    assert(ps);
    assert(pos <= ps->size && pos >= 0);
    SLCheckCapacity(ps);

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

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

2.在指定位置删除:

如果想删除下图中的数据2,需要将2之后的数据往前挪

挪完数据后,2就被删除了,之后再让size--

这样删除就完成了

在写代码前需要注意的点:检查pos,pos的值必须小于size,当size为0就不要删了

void SLErase(SL* ps,int pos)
{
    assert(ps);
    assert(pos < ps->size && ps->size > 0);

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

2.7查找

查找就是从前向后依次将数据一一对比,如果相等就返回数据所在的下标,找不到就返回-1

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

3.顺序表的优点和缺点

1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值