本文将通过C语言模拟顺序表,实现基本函数。
顺序表是线性表的顺序表示,即用一组地址连续的存储单元依次存储线性表的数据元素。
物理结构大概如下图所示:
通过数据表,我们将实现数据的增删查改等功能,每个功能都由单独的接口函数来实现。
按照正规工程的写法,我们还是分成三个文件来写:
Seqlist.h 顺序表结构体的创建、各函数的声明
Seqlist.c 各函数的实现
Test.c 用于测试
一、Seqlist.h
为了能使该顺序表存各种类型的数据,使用typedef重命名。比如要存char类型的,直接将 typedef int DataType; 改为typedef char DataType;即可
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
struct Seqlist
{
DataType* a;
int size;//有效数据的个数
int capacity;//容量 size 一定<=capacity
};
下面是顺序表中需要实现的各个功能函数。
如果在函数中要改变顺序表结构体内部成员的值,就得用传地址。
//1.初始化
void SeqlistInit(struct Seqlist* seq);
//2.销毁
void SeqlistDestroy(struct Seqlist* seq);//销毁
//3.打印
void SeqlistPrint(struct Seqlist* seq);
//4.检查是否超出容量,超出则扩容
//这不属于接口,是我们程序内部使用的
void SeqlistCheckCapacity(struct Seqlist* seq);
//5.尾插
void SeqlistPushBack(struct Seqlist* seq, DataType x);
//6.头插
void SeqlistPushFront(struct Seqlist* seq, DataType x);
//7.尾删
void SeqlistPopBack(struct Seqlist* seq);
//8.头删
void SeqlistPopFront(struct Seqlist* seq);
//9.查找
int SeqlistFind(struct Seqlist* seq , DataType x);
//10.指定位置插入
void SeqlistInsert(struct Seqlist* seq, DataType x,int pos);
//11.指定位置删除
void SeqlistErease(struct Seqlist* seq, int pos);
//12.修改指定位置数据
void SeqlistModify(struct Seqlist* seq, DataType x, int pos);
接下来,我们在Seqlist.c文件中具体实现各个功能函数:
二、Seqlist.c
- 初始化
void SeqlistInit(struct Seqlist* seq)
{
//通过断言,可以在程序出错时更快地找出错误
assert(seq != NULL);
seq->a = NULL;
seq->capacity = 0 ;
seq->size = 0;
}
- 销毁
void SeqlistDestroy(struct Seqlist* seq)
{
assert(seq != NULL);
//手动释放空间并置空
free(seq->a);
seq->a=NULL;
seq->size = 0;
seq->capacity = 0;
}
- 打印
void SeqlistPrint(struct Seqlist* seq)
{
assert(seq);
for (int i = 0; i < seq->size; i++)
{
printf("%d\t",seq->a[i]);
}
printf("\n");
}
4.检查是否超出容量,超出则扩容
各个插入函数都要用到此功能,为了节省代码量,将此功能独立出来
void SeqlistCheckCapacity(struct Seqlist* seq)
{
assert(seq != NULL);
//扩容,这里采用扩2倍的方式
if (seq->capacity == seq->size)
{
//这里用三目操作符:第一次调用时,seq->capacity是0,如果单纯地乘上2,那永远都是0,无法成功增容
int capacityNew = (seq->capacity == 0) ? 4 : (seq->capacity * 2);
//注意realloc的返回值是新开辟空间的地址,所以必须用struct Seqlist*类型的变量去接收
struct Seqlist* seqNew = realloc(seq->a, sizeof(struct Seqlist) * capacityNew);
if (seqNew==NULL)
{
printf("增容失败");
exit(-1);
}
seq->a = seqNew;
seq->capacity = capacityNew;
}
}
5.尾插
void SeqlistPushBack(struct Seqlist* seq, DataType x)
{
SeqlistCheckCapacity(seq);
seq->a[seq->size] = x;
seq->size++;
}
6.头插
头插较为复杂,在插入前需要依次移位
void SeqlistPushFront(struct Seqlist* seq, DataType x)
{
SeqlistCheckCapacity(seq);
int end = seq->size - 1;
while (end >= 0)
{
seq->a[end + 1] = seq->a[end];
end--;
}
seq->a[0] = x;
seq->size++;
}
7.尾删
删除函数在断言seq不为空时,还要断言size>0
void SeqlistPopBack(struct Seqlist* seq)
{
assert(seq);
assert(seq->size > 0);
seq->size--;
}
8.头删
//基本思想也是挪动,但注意要从前往后挪
void SeqlistPopFront(struct Seqlist* seq)
{
assert(seq);
assert(seq->size > 0);
int begin = 0;
while (begin < (seq->size - 1))
{
seq->a[begin] = seq->a[begin + 1];
begin++;
}
seq->size--;
}
9.查找
返回下标。如果有多个相同的数据,返回第一个数据的下标;无相同数据返回-1
int SeqlistFind(struct Seqlist* seq, DataType x)
{
assert(seq);
for (int i = 0; i < seq->size; i++)
{
if (seq->a[i] == x)
{
return i;
}
}
return -1;
}
10.指定位置插入
void SeqlistInsert(struct Seqlist* seq, DataType x,int pos)
{
assert(seq);
assert(pos >= 0 && pos < seq->size);
//检查容量是否已满
SeqlistCheckCapacity(seq);
int end = seq->size - 1;
while (end >= pos)//pos之后的元素都要依次往后移
{
seq->a[end + 1] = seq->a[end];
--end;
}
seq->a[pos] = x;
seq->size++;
}
11.指定位置删除
void SeqlistErease(struct Seqlist* seq, int pos)
{
assert(seq);
assert(pos >= 0 && pos < seq->size);
int begin = 0;
//从pos开始,后一个移到前一个
for (begin = pos; begin < seq->size - 1; begin++)
{
seq->a[begin] = seq->a[begin + 1];
}
seq->size--;
}
12.修改指定位置数据
void SeqlistModify(struct Seqlist* seq, DataType x, int pos)
{
assert(seq);
assert(pos >= 0 && pos < seq->size - 1);
seq->a[pos] = x;
}
读到这里,细心的小伙伴或许已经发现,可以在SeqlistInsert中通过复用实现头插和尾插,在SeqlistErease中通过复用实现头删和尾删:
//改进版尾插
void Improve_SeqlistPushBack(struct Seqlist* seq, DataType x)
{
SeqlistCheckCapacity(seq);
SeqlistInsert(seq, x, seq->size);
}
//改进版头插
void Improve_SeqlistPushFront(struct Seqlist* seq, DataType x)
{
SeqlistCheckCapacity(seq);
SeqlistInsert(seq, x, 0);
}
尾删头删类似,这里不再赘述。
三、Test.c
我们现在test1中进行基本的接口可行性的测试。
void TestSeqlist1()
{
struct Seqlist s;
SeqlistInit(&s);
printf("尾插: ");
SeqlistPushBack(&s, 1);
SeqlistPushBack(&s, 2);
SeqlistPushBack(&s, 3);
SeqlistPushBack(&s, 4);
SeqlistPushBack(&s, 5);
SeqlistPrint(&s);
printf("头插: ");
SeqlistPushFront(&s, -1);
SeqlistPushFront(&s, -2);
SeqlistPushFront(&s, -3);
SeqlistPrint(&s);
printf("尾删: ");
SeqlistPopBack(&s);
SeqlistPopBack(&s);
SeqlistPrint(&s);
printf("头删: ");
SeqlistPopFront(&s);
SeqlistPopFront(&s);
SeqlistPrint(&s);
printf("在位置3插入2: ");
SeqlistInsert(&s, 3, 2);
SeqlistPrint(&s);
printf("删除位置2的数据: ");
SeqlistErease(&s, 2);
SeqlistPrint(&s);
printf("修改位置0的值: ");
SeqlistModify(&s, 0, 0);
SeqlistPrint(&s);
SeqlistDestroy(&s);
}
int main()
{
TestSeqlist1();
return 0;
}
运行结果:
如果想增加代码的实现效果,可以在Seqlist.c中引入菜单函数:
void menu()
{
printf("*****************************\n");
printf("1.尾插数据 2.头插数据\n");
printf("3.尾删数据 4.头删数据\n");
printf("5.查找数据 6.打印数据\n");
printf(" 0.退出 \n");
printf("*****************************\n");
}
然后再main函数中使用switch for语句根据选项对应操作:
int main()
{
struct Seqlist s;
SeqlistInit(&s);
int input = 0;
int data = 0;//用于输入数据
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出程序\n");
exit(-1);
case 1:
printf("请输入想要尾插的值:\n");
scanf("%d", &data);
SeqlistPushBack(&s, data);
break;
case 2:
printf("请输入想要头插的值:\n");
scanf("%d", &data);
SeqlistPushFront(&s, data);
break;
case 3:
printf("尾删数据\n");
SeqlistPopBack(&s);
break;
case 4:
printf("头删数据\n");
SeqlistPopFront(&s);
break;
case 5:
printf("请选择想要查找的值:\n");
if (SeqlistFind(&s, data) != -1)
{
printf("要找的数在第 %d 个位置\n", SeqlistFind(&s, data));
}
else
{
printf("找不到\n");
}
break;
case 6:
printf("打印数据\n");
SeqlistPrint(&s);
break;
default:
printf("无此选项,请重新输入\n");
break;
}
} while (input != EOF);
SeqlistDestroy(&s);
return 0;
}