赞
踩
本篇博客将为大家介绍数据结构中的顺序表的内容,它数据数据结构中的基础内容,希望所写内容能够为大家带来帮助,也希望大家多多支持;下面进入正文部分
数据结构::数据结构是计算机存储、组织数据的方式。
我们前面学过数组,数组就是一种数据结构,它是最基础的数据结构。
我们今天所要讨论的顺序表,其底层就是数组
顺序表是线性表的一种,什么是线性表呢?
线性表是相同特性的数据类型的集合,它在物理结构上不一定连续,在逻辑结构上是连续的;
而我们今天要说的顺序表是线性表的一种;
顺序表的特性:物理结构连续,逻辑结构连续。
顺序表可以分为两种:一种是静态顺序表,还有一种是动态顺序表;
大家可以看到,上面就是静态顺序表的结构,包括一个定长数组、还有顺序表当前有效的数据个数。
上面展示了动态顺序表的结构,大家发现,它与静态顺序表相比,多了一个成员,并且将定数组换成了指针的形式。
那么,请大家思考,静态顺序表和动态顺序表哪个更好呢?
其实这个问题显而易见,当然是动态顺序表更加实用。如果我们使用静态顺序表去存储一组不确定的信息,那么我们很难去精确地得知要存储多少,一旦我们定长数组的大小给的小了,那就会导致空间不够用,从而丢失信息;反之,如果我们害怕空间小了,一上来就给了一个很大的空间,那么这样也存在很大的问题,就是如果我们需要的空间远小于我们申请的空间,那将造成空间的浪费。
所以推荐大家使用动态顺序表,它可以动态增容,我们想要多少就申请多少,这样就不会担心空间不够或者空间大量浪费的问题
关于顺序表的实现,我们需要三个文件,分别是头文件,顺序表的方法文件,测试文件;具体请大家仔细看下面的代码!!!
我将把整个代码拆解成各个小的部分来说明,最后会为大家展示完整代码,下面请大家认真阅读下面的内容。
首先,我们来将顺序表的结构写入头文件,大家看下面的代码
- typedef int SLDataType;
- typedef struct SeqList
- {
- SLDataType* arr;
- int size;
- int capacity;
- }SL;
这里大家要注意,我将int定义成了SLDataType,这么做是为了我们后面方便修改代码,如果我们需要把int改为char,那么我们只需要把第一行的int改为char,这样后面的代码中的int就统一改成了char,实现了“一劳永逸”的效果。
首先我们需要在头文件中声明一下
void SLInit(SL* ps);
这里声明完之后,下面我们就需要在方法文件中写出具体的初始化的方法了
- void SLInit(SL* ps)
- {
- ps->arr = NULL;
- ps->size = ps->capacity = 0;
- }
这里大家需要注意,我们必须得用指针去接受,也就是传址调用;当我们写完上面的代码后,下面我们需要在测试文件中测试一下,看看写的代码是否正确。
- void SLtest01()
- {
- SL sl;
- SLInit(&sl);
- }
- int main()
- {
- SLtest01();
- return 0;
- }
这里就是测试代码,我们通过调试,可以得到下面的结果
大家可以发现,这里我们就初始化成功了,与我们在方法文件中的要求一样。
与初始化相同,我们需要在头文件中进行声明;
void SLDestroy(SL* ps);
声明完成后,我们在方法文件中写具体的销毁方法
- void SLDestroy(SL* ps)
- {
- if (ps->arr)
- {
- free(ps->arr);
- }
- ps->arr = NULL;
- ps->size = ps->capacity = 0;
- }
这里大家发现,顺序表销毁的代码与初始化的代码比较类似,我们首先需要判断顺序表中的数组是否为空,如果有空间,那我们就需要进行销毁,这里用到free;然后我们需要将其置为空指针,并且将size和capacity都置为0。
上面我们讨论完了初始化和销毁,下面来进行具体的增删查改的操作
在我们进行插入操作之前,我们需要注意顺序表的空间够不够,如果不够,我们需要先进行增容,下面为大家展示增容的具体方法;当然,我们可以将增容的过程包装成一个函数,那我们就需要在头文件中声明。
void SLCheckCapacity(SL* ps);
下面需要在方法文件中写出具体增容方法
- void SLCheckCapacity(SL* ps)
- {
- if (ps->size == ps->capacity)
- {
- int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
- SLDataType* tmp = (SLDataType * )realloc(ps->arr, newCapacity * sizeof(int));
- if (tmp == NULL)
- {
- perror("realloc fail");
- exit(1);
- }
- ps->arr = tmp;
- ps->capacity = newCapacity;
- }
- }
这里大家需要注意几点,首先,我们需要使用realloc来进行动态增容,并且判断空间是否足够,这里当size等于capacity时,就说明空间已经满了;
其次,我们要创建临时的指针变量,因为我们申请空间可能会失败,为了不让原始数据受到影响,我们需要创建临时的指针变量去保存我们申请的空间;同理,我们还创建了newCapacity来保护原始空间;
在申请空间的过程中,我们要进行强制类型转换,并将申请到的空间存放在临时的指针变量中;
中间加上一句判断,一旦申请失败,程序会直接报错并退出;
最后,我们将申请完的空间交给顺序表即可。
首先,还是先进行声明
void SLPushBack(SL* ps, SLDataType x);
声明完后,我们来写具体的尾插方法
- void SLPushBack(SL* ps, SLDataType x)
- {
- assert(ps);
- SLCheckCapacity(ps);
- ps->arr[ps->size++] = x;
- }
大家可以看到,尾插的代码很简单,这里大家需要注意,在我们尾插之前,需要进行assert断言。防止传入的是NULL,然后检查空间是否足够并进行增容操作;最后我们进行尾插。尾插完成后。我们需要让size自增。
第一步,先声明
void SLPushFront(SL* ps, SLDataType x);
下面写出头插的具体代码
- void SLPushFront(SL* ps, SLDataType x)
- {
- assert(ps);
- SLCheckCapacity(ps);
- for (int i = ps->size; i > 0; i--)
- {
- ps->arr[i] = ps->arr[i - 1];
- }
- ps->arr[0] = x;
- ps->size++;
- }
这里头插的过程就是将顺序表的每个数据都向后移动一位,并且是从后往前进行移动,最终会将首位空出来,这个时候我们将我们想要插入的数据插入到首位,这样就实现了头插的效果,最后,大家一定不要忘了让size自增,我们插入了数据,size就得跟着增大。
下面我们要进行测试;这里我们可以再写一个打印函数,将尾插和头插的结果全部打印出来;
我们在头文件中声明一下打印函数
void SLPrintf(SL s);
然后我们写出具体代码
- void SLPrintf(SL s)
- {
- for (int i = 0; i < s.size; i++)
- {
- printf("%d ", s.arr[i]);
- }
- printf("\n");
- }
这里就是一个简单的循环,我们去打印每个元素;
下面,统一为大家展示尾插和头插的运行结果
大家可以看到,上面我们尾插和头插都实现了理想的效果。
前面说完了插入。下面来进行删除操作,咱们先来看尾删
第一步,还是先在头文件中声明
void SLPopBack(SL* ps);
然后,写出具体的尾删代码
- void SLPopBack(SL* ps)
- {
- assert(ps);
- assert(ps->size);
- ps->size--;
- }
大家会发现。尾删代码很简洁,我们在进行删除操作前。需要保证顺序表不为空。也就是第二个断言;最后一行代码其实比较巧妙,我们直接让size自减,这样就相当于将尾部最后一个数据删除。
下面来测试尾删代码
大家可以看到,尾删代码是成功的。
和前面一样,还是先在头文件中声明
void SLPopFront(SL* ps);
下面来写具体头删代码
- void SLPopFront(SL* ps)
- {
- assert(ps);
- assert(ps->size);
- for (int i = 0; i < ps->size - 1; i++)
- {
- ps->arr[i] = ps->arr[i+1];
- }
- ps->size--;
- }
大家可以发现,头删的代码和头插有些类似,只不过头删是要求我们让后一位的数据覆盖其前一位的数据,其实就是将顺序表的每个数据都相前移动一位,并且是从前往后进行移动;当头删操作完成后,我们需要让size自减,因为我们删除了一个数据。
最后,我们来测试一下头删代码,大家请看
这里我们成功地实现了头删的效果,证明我们的头删代码是正确的。这里还需要给大家提醒一下,在删除的时候,我们不能将顺序表删为空!
首先,还是进行声明
void SLInsert(SL* ps, int pos, SLDataType x);
下面,我们来写出具体代码
- void SLInsert(SL* ps, int pos, SLDataType x)
- {
- assert(ps);
- assert(pos >= 0 && pos <= ps->size);
- SLCheckCapacity(ps);
- for (int i = ps->size; i > pos; i--)
- {
- ps->arr[i] = ps->arr[i - 1];
- }
- ps->arr[pos] = x;
- ps->size++;
- }
这里我们在进行指定位置插入时, 我们需要注意对pos的限制,首先“数组”的下标不能为负,其次pos也不能太大,不能超过size;前面说过,插入数据的时候,我们必须要判断顺序表的空间是否足够,所以我们在插入之前,需要先进行SLCheckCapacity;
再往下就与头删的代码很相似了,头删是将所有数据向后挪动一位;而现在我们需要将pos后的数据向后挪动一位,并且是从后往前进行挪动,这样我们就可以将pos这个位置空出来,将我们想要插入的数据进行插入,最后,我们要让size自增。因为我们增加了一个数据。
写完了方法,我们来测试一下;
大家可以看到,我们分别在不同位置实现了数据的插入,证明我们的代码是正确的.
上面说完了插入,现在来看删除数据
首先还是进行声明
void SLErase(SL* ps, int pos);
下面来实现删除代码
- void SLErase(SL* ps, int pos)
- {
- assert(ps);
- assert(pos >= 0 && pos < ps->size);
- for (int i = pos; i < ps->size-1; i++)
- {
- ps->arr[i] = ps->arr[i + 1];
- }
- ps->size--;
- }
这里大家需要注意,在对pos限制时,与插入的代码有一点区别,就是pos不能等于size,因为size位置是没有数据的,所以也就不存在删除;然后具体删除本质上就是将pos后的数据向前移动一位,并且是从前往后进行移动;最后,我们需要让size自减,因为我们删除了一个数据。
下面我们来进行测试
大家可以发现,我删除了下标为1的数据,也就是第二个位置的数据;实现了想要的效果,证明我们的代码是正确的。
第一部还是先声明
int SLFind(SL* ps, SLDataType x);
下面来写查找代码
- int SLFind(SL* ps, SLDataType x)
- {
- assert(ps);
- for (int i = 0; i < ps->size; i++)
- {
- if (ps->arr[i] == x)
- {
- return i;
- }
- }
- return -1;
- }
查找代码比较简单,只需要使用循环去遍历顺序表的数据,当找到时,返回这个数据的下标;如果找不到,那就返回-1(数组的下标不能为负)。
最后我们来进行测试
大家发现,我们可以找到对应的数据,说明代码没有问题。
OK,到这里,顺序表的内容就为大家介绍完毕了,下面为大家展示完整的代码,仅供参考
首先,来展示头文件SeqList.h
- #pragma once
- #include<stdio.h>
- #include<assert.h>
- #include<stdlib.h>
- typedef int SLDataType;
- typedef struct SeqList
- {
- SLDataType* arr;
- int size;
- int capacity;
- }SL;
- void SLInit(SL* ps);
- void SLDestroy(SL* ps);
- void SLPrintf(SL s);
- void SLCheckCapacity(SL* ps);
- void SLPushBack(SL* ps, SLDataType x);
- void SLPushFront(SL* ps, SLDataType x);
- void SLPopBack(SL* ps);
- 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
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"Seqlist.h"
- void SLInit(SL* ps)
- {
- ps->arr = NULL;
- ps->size = ps->capacity = 0;
- }
- void SLDestroy(SL* ps)
- {
- if (ps->arr)
- {
- free(ps->arr);
- }
- ps->arr = NULL;
- ps->size = ps->capacity = 0;
- }
- void SLCheckCapacity(SL* ps)
- {
- if (ps->size == ps->capacity)
- {
- int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
- SLDataType* tmp = (SLDataType * )realloc(ps->arr, newCapacity * sizeof(int));
- if (tmp == NULL)
- {
- perror("realloc fail");
- exit(1);
- }
- ps->arr = tmp;
- ps->capacity = newCapacity;
- }
- }
- void SLPushBack(SL* ps, SLDataType x)
- {
- assert(ps);
- SLCheckCapacity(ps);
- ps->arr[ps->size++] = x;
- }
- void SLPushFront(SL* ps, SLDataType x)
- {
- assert(ps);
- SLCheckCapacity(ps);
- for (int i = ps->size; i > 0; i--)
- {
- ps->arr[i] = ps->arr[i - 1];
- }
- ps->arr[0] = x;
- ps->size++;
- }
- void SLPrintf(SL s)
- {
- for (int i = 0; i < s.size; i++)
- {
- printf("%d ", s.arr[i]);
- }
- printf("\n");
- }
- void SLPopBack(SL* ps)
- {
- assert(ps);
- assert(ps->size);
- ps->size--;
- }
- void SLPopFront(SL* ps)
- {
- assert(ps);
- assert(ps->size);
- for (int i = 0; i < ps->size - 1; i++)
- {
- ps->arr[i] = ps->arr[i+1];
- }
- ps->size--;
- }
- void SLInsert(SL* ps, int pos, SLDataType x)
- {
- assert(ps);
- assert(pos >= 0 && pos <= ps->size);
- SLCheckCapacity(ps);
- for (int 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 (int i = pos; i < ps->size-1; i++)
- {
- ps->arr[i] = ps->arr[i + 1];
- }
- ps->size--;
- }
- int SLFind(SL* ps, SLDataType x)
- {
- assert(ps);
- for (int i = 0; i < ps->size; i++)
- {
- if (ps->arr[i] == x)
- {
- return i;
- }
- }
- return -1;
- }
最后是我们的测试文件test.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"Seqlist.h"
- void SLtest01()
- {
- SL sl;
- SLInit(&sl);
- SLPushBack(&sl, 1);
- SLPushBack(&sl, 2);
- SLPushBack(&sl, 3);
- SLPushBack(&sl, 4);
- SLPrintf(sl);
- SLPushFront(&sl, 5);
- SLPushFront(&sl, 6);
- SLPrintf(sl);
- SLPopFront(&sl);
- SLPrintf(sl);
- SLPopFront(&sl);
- SLPrintf(sl);
- SLPopFront(&sl);
- SLPrintf(sl);
- SLPopBack(&sl);
- SLPrintf(sl);
- SLInsert(&sl, 0, 9);
- SLPrintf(sl);
- SLInsert(&sl, sl.size, 8);
- SLPrintf(sl);
- SLInsert(&sl, 1, 99);
- SLPrintf(sl);
- SLErase(&sl, 0);
- SLPrintf(sl);
- SLErase(&sl, 3);
- SLPrintf(sl);
- SLErase(&sl, 1);
- SLPrintf(sl);
- int find = SLFind(&sl, 4);
- if (find < 0)
- {
- printf("没找到\n");
- }
- else
- {
- printf("找到了,下标为%d\n", find);
- }
- SLDestroy(&sl);
- }
- int main()
- {
- SLtest01();
- return 0;
- }
到这里,关于顺序表的内容就全部为大家介绍完了,希望大家认真去理解每一小部分的代码,注意其中的坑点,最终可以自己写出顺序表的所有内容;顺序表是数据结构中的基础内容,所以初学者一定要掌握好顺序表,为后面高阶数据的学习打好基础;最后,希望本篇博客的内容可以为大家带来帮助,如有错误,欢迎大家评论交流,谢谢!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。