线性表*(linear list)*是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。常见的线性表:顺序表、链表、栈、队列、字符串…
我们说“线性”和“非线性”,只在逻辑层次上讨论,而不考虑存储层次,所以链表依旧是线性表。
下面我们先来讲讲顺序表。
顺序表是在内存中以数组的形式保存的线性表,是将表中的结点依次存放在内存中一组地址连续的存储单元中。
#define N 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType array[N]; //静态容量
size_t size; //有效数据个数
}SeqList;
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* array; //指向动态开辟的数组
size_t size; //有效数据个数
size_t capacity; //数组容量
}SeqList;
实际很多场景不知道要存多少数据,有时静态顺序表不是开大了就是开小了,所以更多使用动态顺序表,下面来介绍动态顺序表的实现(有点类似之前的通讯录)
以下是我们要实现的功能:
// SeqList.h
#pragma once
#include
#include
#include
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
size_t size;
size_t capacity;
}SeqList;
// 对数据的管理:增删查改
void SeqListInit(SeqList* ps);//初始化
void SeqListDestory(SeqList* ps);//销毁
void SeqListPrint(SeqList* ps);//打印顺序表
void SeqListPushBack(SeqList* ps, SLDataType x);//尾插
void SeqListPushFront(SeqList* ps, SLDataType x);//头插
void SeqListPopFront(SeqList* ps);//前删
void SeqListPopBack(SeqList* ps);//尾删
int SeqListFind(SeqList* ps, SLDataType x);//查找
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);//顺序表在pos位置插入x
void SeqListErase(SeqList* ps, size_t pos);//删除pos位置的值
void SeqListInit(SeqList* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SeqListDestory(SeqList* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
注意到头插尾插都属于插入,头删尾删都属于删除,所以我们先写插入和删除。
插入之前得先检查容量
void SeqListCheckCapacity(SeqList* ps)
{
assert(ps);
//如果满了,那么扩容
if (ps->size == ps->capacity)
{
size_t newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//2倍扩容,第一次扩容给4个容量
SLDataType* tmp = realloc(ps->a, sizeof(SLDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = newCapacity;
}
}
}
对realloc
还不熟悉可以去复习一下[c语言]动态内存分配|malloc realloc calloc函数|相关错误|习题|柔性数组
exit(-1)
可以直接结束程序,括号中的-1
为程序退出的返回代码,表示错误退出。与return
不同,return
是结束函数。
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
if (pos > ps->size)
{
printf("pos 越界:%d\n", pos);
return;
}
SeqListCheckCapacity(ps);
size_t end = ps->size;
while (end > pos)
{
ps->a[end] = ps->a[end - 1];
--end;
}
ps->a[pos] = x;
ps->size++;
}
assert
断言相较用if判断更暴力(为假则终止程序),这边判断给的pos是否越界不用这么暴力(为假则终止函数)
可能有人要问,传入的pos
如果是-1
怎么办?size_t
是无符号整型,不用担心这个问题。
然后从后向前挪动数据,腾出空间, 这样就把值插入了。
void SeqListErase(SeqList* ps, size_t pos)
{
assert(ps);
if (pos >= ps->size)
{
printf("pos 越界:%d\n", pos);
return;
}
size_t begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
因为数组的存储是连续的,所以删除只能是后面一个覆盖前面一个。
写完上面的,这几个就很简单了。
void SeqListPushBack(SeqList* ps, SLDataType x)
{
assert(ps);
SeqListInsert(ps, ps->size, x);
}
void SeqListPopBack(SeqList* ps)
{
assert(ps);
SeqListErase(ps, ps->size - 1);
}
void SeqListPushFront(SeqList* ps, SLDataType x)
{
assert(ps);
SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SeqList* ps)
{
assert(ps);
SeqListErase(ps, 0);
}
void SeqListPrint(SeqList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
找到就返回下标,未找到返回-1
int SeqListFind(SeqList* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
//SeqList.c
#include "SeqList.h"
void SeqListInit(SeqList* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SeqListDestory(SeqList* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void SeqListCheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
size_t newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = realloc(ps->a, sizeof(SLDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = newCapacity;
}
}
}
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
if (pos > ps->size)
{
printf("pos 越界:%d\n", pos);
return;
}
SeqListCheckCapacity(ps);
size_t end = ps->size;
while (end > pos)
{
ps->a[end] = ps->a[end - 1];
--end;
}
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SeqList* ps, size_t pos)
{
assert(ps);
if (pos >= ps->size)
{
printf("pos 越界:%d\n", pos);
return;
}
size_t begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
void SeqListPushBack(SeqList* ps, SLDataType x)
{
assert(ps);
SeqListInsert(ps, ps->size, x);
}
void SeqListPopBack(SeqList* ps)
{
assert(ps);
SeqListErase(ps, ps->size - 1);
}
void SeqListPushFront(SeqList* ps, SLDataType x)
{
assert(ps);
SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SeqList* ps)
{
assert(ps);
SeqListErase(ps, 0);
}
void SeqListPrint(SeqList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
int SeqListFind(SeqList* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
//test.c
#include "SeqList.h"
void menu()
{
printf("***********************\n");
printf("**** 1.头插 2.头删 ****\n");
printf("**** 3.尾插 4.尾删 ****\n");
printf("**** 5.插入 6.删除 ****\n");
printf("**** 7.打印 8.查找 ****\n");
printf("**** 0.退出 ****\n");
printf("***********************\n");
}
int main()
{
SeqList s;
SeqListInit(&s);
int option = 0;
SLDataType x;
size_t pos;
do
{
menu();
printf("请选择:");
scanf("%d", &option);
switch (option)
{
case 1:
printf("输入要插入的值:");
scanf("%d", &x);
SeqListPushFront(&s, x);
printf("插入成功\n");
break;
case 2:
SeqListPopFront(&s);
printf("删除成功\n");
break;
case 3:
printf("输入要插入的值:");
scanf("%d", &x);
SeqListPushBack(&s, x);
printf("插入成功\n");
break;
case 4:
SeqListPopBack(&s);
printf("删除成功\n");
break;
case 5:
printf("输入要插入的位置:");
scanf("%u", &pos);
printf("输入要插入的值:");
scanf("%d", &x);
SeqListInsert(&s, pos, x);
printf("插入成功\n");
break;
case 6:
printf("输入要删除的位置:");
scanf("%u", &pos);
SeqListErase(&s, pos);
printf("删除成功\n");
break;
case 7:
SeqListPrint(&s);
break;
case 8:
printf("输入要查找的值:");
scanf("%d", &x);
int index = SeqListFind(&s, x);
if (index == -1)
{
printf("未找到\n");
}
else
{
printf("下标为:%d\n", index);
}
break;
}
} while (option);
printf("退出程序\n");
SeqListDestory(&s);
return 0;
}
虽然我们在头文件定义了类型typedef int SLDataType;
,但要修改数据类型不能光改这里,还有scanf("%d", &x);
里的格式占位符也要跟着改,这算是c语言的一个缺陷吧。
顺序表在内存中的存储是连续的
顺序表的优点:支持随机访问,查找速度快,时间复杂度是 O ( 1 ) O(1) O(1)
顺序表的缺点:空间利用率不高,删除插入效率低,时间复杂度为 O ( n ) O(n) O(n),因为要依次向前挪或者向后移。
链表优缺点几乎与它互补,我们下一篇数据结构博客再聊。
接下来还会更新一些数组相关的练习,还望有大佬指点,多多支持~