顺序表的实现与应用
一、顺序表的基本概念与特性
顺序表是一种逻辑上和物理上均为线性的数据结构,它的底层是用数组实现的,在这篇文章中
我们将使用C语言将顺序表的所有的功能实现出来,需要具备的知识有:结构体、动态内存管理
以及指针相关的知识。
二、顺序表的实现
使用结构体来定义一个顺序表SL
typedef int SLDatatype
typedef struct SeqList
{
SLDatatype* arr;
int size;
int capacity;
}SL;
这里注意我们定义的是动态顺序表,也就是大小并没有确定下来的,并且可以改变的顺序表,与之对应的有静态顺序表,也就是大小在编译的时候就已经确定下来的。因为我们可能之后要在数组里面放char类型的变量,因此不能仅仅写int,我们选择用SLDatatype来代替,一遍一键替换。
在这个结构体中我们定义了三个参数:第一个是一个指针arr,指向的是一个数组,数组里的每一个值的类型都是SLDatatype;第二个是size,表示的是arr的数组里面的元素的个数;第三个是capacity,指的是arr数组的容量大小,也就是arr数组的开辟的空间字节数。
三、顺序表的操作以及实现
顺序表的操作主要有:顺序表的初始化;顺序表的销毁;顺序表的打印;顺序表的插入数据操作;检查顺序表的容量;顺序表的删除数据操作(头删和尾删);在指定的位置钱插入数据;删除指定位置的数据;找到指定的元素
首先我们先建立一个项目,然后再建立一个头文件SeqList.h用来声明我们要用到的方法,再建立一个源文件SeqList.c用来实现具体的操作,再建立一个源文件test.c用来测试我们的代码
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SLDatatype;
typedef struct SeqList
{
SLDatatype* arr;
int capacity;
int size;
}SL;
void SLInit(SL* ps);
1、下面我们来实现顺序表的初始化方法,在刚开始的时候,我们将arr指向空指针,然后因为里面并不存放数据,因此我们将其中的size和capacity全部设置为0
#include "SeqList.h"
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
2、接下来是动态顺序表的销毁操作,我们知道arr后面指向的空间是要动态开辟的,因此需要我们手动的去销毁
void SLDestroy(SL* ps);
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr); //arr应该是动态开辟的空间
}
ps->arr = NULL; //最本质的内存上面的东西
ps->size = ps->capacity = 0;
}
将arr指针free掉后设置为空,防止其形成野指针,然后将结构体中的size和capacity全部设置为0,这里需要注意的是顺序表最本质上还是数组,是内存上事实存在的空间,而不是仅仅修改某一个数字而已哈。
3、接下来的操作是顺序表的打印
void SLPrint(SL* ps);
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
当然这里如果是用C++的话会更好一点,因为%d就只能打印整型,如果之后修改存储的类型的话,还需要将这里一并修改掉。
4、接下来是检查顺序表的容量大小是否足够,这个操作实际上是为了在插入数据的时候判断空间是否足够而产生的方法,如果空间足够就插入数据,不够的话就要句容。
检查容量分为两种情况,第一种是第一次检查容量是0,我们选择随便给他赋一个初值(不是简单的赋值,而是内存的实际开辟!)第二种情况是:不是0,我们选择将容量扩充为原来的2倍到3倍(这是有数学原理的,感兴趣的小伙伴可以去搜一搜)
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(SLDatatype));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
//capacity的值已经得到改变
}
}
在这里我们第一次检查的时候将空间大小设置为4个SLDatatype类型的大小,使用的是一个三目表达式:如果capacity为0就赋4,否则赋为原来的两倍,将这个值交给newCapacit保存。并且将capacity更新。
5、顺序表在尾部位置插入数据(尾插)
void SLPushBack(SL* ps, SLDatatype x);
首先我们需要断言传入的参数ps是否是空指针,然后检查空间容量是否足够我们进行尾部插入的操作,由于数组的下标是从0开始,因此size的下标的位置是数组目前最后的元素的后一个位置。我们只需要再size位置插入我们需要的数据,然后更新size即可。
void SLPushBack(SL* ps, SLDatatype x)
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
6、顺序表的头部插入数据,也就是在下标为0的位置插入数据。还是和尾部插入数据一样需要assert断言ps指针,并且检查空间的容量大小。之后我们要将数组arr里面的每一个值都向后移动一位,空出0下标的位置,然后插入数据。
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++;
}
7、顺序表的删除数据(尾部删除),我们要判断是否有数据能够让我们进行删除,也就是size不能为0,接下来我们要去删除最后一个数据,有人可能会让他设置为0之类的,事实上我们只需要让size–即可,这样的话无论后续的打印还是其他操作,那个元素实际上并没有对操作产生任何的影响。
void SLPopBack(SL* ps);
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
8、顺序表的头删操作,还是一样先断言ps和size不能为空,然后从下标为1的元素开始向前进行覆盖。最后更新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--;
}
9、在指定的位置之前插入数据
void SLInsert(SL* ps, SLDatatype x, int pos);
首先断言ps不能为空指针,并且检查空间容量是否足够,否则进行扩容处理。
pos的取值也不是任意的,它在0到size时间,特殊的是如果是0则就是头插,如果是size则相当于尾插。从pos的位置开始将pos以及它之后的元素全部向后移动一位,留出pos的位置,然后插入我们所需要的数据x.
void SLInsert(SL* ps, SLDatatype x, int pos)
{
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++;
}
10、删除指定位置的数据
void SLErase(SL* ps, int pos);
断言的操作和9一样,在此基础上我们需要将pos+1位置以及之后的元素向前覆盖即可。
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--; //very good
}
11、在顺序表中查找我们所需要的数据,如果能找到则返回下标,找不到的话则返回-1.
int SLFind(SL* PS, SLDatatype x);
只需要遍历arr即可。
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中测试我们所写的代码块。
#include "SeqList.h"
void SLtest01()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushBack(&s, 6);
SLPrint(&s);
}
int main()
{
SLtest01();
return 0;
}
首先我们定义一个结构体(顺序表)s,然后对塔进行初始化,接下俩尾插1,2,3,4,5,6,接下来打印顺序表。
我们可以看到测试的是正确的。还有很多操作留给小伙伴们自己去进行测试啦~
顺序表的介绍就到这里结束了,最后附上所有的代码块
SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//定义动态顺序表的结构
typedef int SLDatatype;
typedef struct SeqList
{
SLDatatype* arr;
int capacity;
int size;
}SL;
//第一个操作:顺序表的初始化
void SLInit(SL* ps);
//第二个操作,顺序表的销毁
void SLDestroy(SL* ps);
//第三个操作,顺序表的打印
void SLPrint(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, SLDatatype x, int pos);
//第七个操作,删除指定位置的数据
void SLErase(SL* ps, int pos);
//第八个操作,找到指定的元素
int SLFind(SL* PS, SLDatatype x);
void SLCheckCapacity(SL* ps);
SeqList.c
#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); //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(SLDatatype));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
//capacity的值已经得到改变
}
}
//第四个操作插入数据
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 SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->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, SLDatatype x, int pos)
{
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--; //very good
}
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
#include "SeqList.h"
void SLtest01()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushBack(&s, 6);
//SLPushBack(NULL, 6);
SLPrint(&s);
//SLPushFront(&s, 1);
//SLPushFront(&s, 2);
//SLPushFront(&s, 3);
//SLPushFront(&s, 4);
//SLPrint(&s); //4 3 2 1
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
/*SLInsert(&s, 11, 0);
SLPrint(&s);
SLInsert(&s, 22, s.size);
SLPrint(&s);
SLInsert(&s, 33, 1);
SLPrint(&s);
int find = SLFind(&s, 222);
if (find < 0)
{
printf("not found\n");
}
else
{
printf("found it always\n");
}
SLDestroy(&s);*/
}
int main()
{
SLtest01();
return 0;
}