目录
1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
2.顺序表
2.1 顺序表的概念及其结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1.静态顺序表:使用定长数组存储元素。(空间开多了浪费,开少了不够用)
#define N 10
typedef int SLDadaType //将SLDadaType代替int,后续需要整体改成其他类型时只需要在此改写int就行
typedef struct SeqList
{
SLDateType arr[N]; //定义数组长度
size_t size; //有效数组的个数
}SL;
2.动态顺序表:使用动态开辟的数组存储。(按需申请)
typedef struct SeqList
{
SLDateType *arr; //指向动态开辟的数组
size_t size; //有效数组的个数
size_t capicity; //容量空间的大小
}SL;
2.2 内容
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
首先我们会在编译器下添加一个头文件 Seqlist.h,进行接口实现,以下我们先将所需函数包含。
#pragma once
//在头文件的最开始加入这条预处理指令,就能够保证头文件只被编译一次,防止头文件被重复引用。
#include<stdio.h>
#include <assert.h>
#include <stdlib.h>typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;
int size;
int capacity;
}SeqList,SL;// 顺序表初始化
void SeqListInit(SeqList* ps);//顺序表检查容量,如果满了就进行增容
void SLCheckCapacity(SeqList* ps);//顺序表头插
void SeqListPushBack(SeqList* ps, SLDateType x);//顺序表尾插
void SeqListPushFront(SeqList* ps, SLDateType x);//顺序表头删
void SeqListPopFront(SeqList* ps);//顺序表尾删
void SeqListPopBack(SeqList* ps);// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);//顺序表更改pos位置的值
void SeqListRevise(SeqList* ps, int pos, SLDateType x);
//打印顺序表
void SeqListPrint(SeqList* ps);
// 顺序表销毁
void SeqListDestroy(SeqList* ps);
接下来我们将在源文件Seqlist.c中讲上述所需函数一一定义。
2.2.1 顺序表初始化
#include"SeqList.h"
void SeqListInit(SeqList* ps) {
assert(ps);
//断言,判断ps如果为空,程序将会报错
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
将初始化结构体中数组指向空,将数组长度及有效个数初始化为0,以便于后续我们的增删查改
将顺序表初始化后,我们就可以对顺序表进行增删查改,但是在每次插入前,我们需要检查容量是否已满,如果满了就需要扩容。
2.2.2 顺序表检查容量
void SLCheckCapacity(SeqList* ps) {
assert(ps);
//如果有效数组个数等于容量空间大小,说明需要扩容了
if (ps->capacity == ps->size) {
int newCapacity = ps->capacity == 0 ? 4 : ps->size * 2;
SeqList* tep = (SeqList*)realloc(ps->a, sizeof(SLDateType) * newCapacity);
if (tep == NULL) {
printf("扩容失败");
return;
}
ps->a = tep;
ps->capacity = newCapacity;
}
}
为了防止扩容失败而导致原来的数组地址及数组容量大小丢失,我们用tep和newCaoacity来充当所需扩容后的数组地址及数组的容量大小。
扩容一般一次增容 2 倍,当然也可以更具个人所需扩容3、4倍,要注意的是,如果是对刚初始后顺序表扩容,我们可以先给其4个整形大小的空间。
realloc()
realloc函数返回的是void*类型的指针,指向在重新开辟的内存块的起始地址,而ptr是先前内存块的指针,size_t size指的是新开辟空间的的字节数,重新开辟的内存块并不一定是在原数组的地址空间基础上,继续往后按照我们的意愿去扩展。因为,我们并不能保证拟扩展的地址空间上,是否有其他程序已经先行占用了?如果拟扩展地址空间上没有占用,那么后续扩展的内容调整后的内存空间和原来的内存空间,保持同一内存始址。否则,程序会在内存的堆区重新找一块空闲的地址空问,并返回新的内存始址,如果申请失败,将返回NULL,所以,realloc返回的指针很可能指向一个新的地址,如果指向一个新的地址,那么一定为释放(free)掉原来的指针。
检查完容量后我们即可进行插入操作
2.2.3 顺序表的尾插
void SeqListPushBack(SeqList* ps, SLDateType x) {
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
size是数据个数,同时也是最后一个数据的下一个位置的下标,所以我们直接将所需插入数x放置该位置,随后对有效数组个数加一就行。
2.2.4 顺序表的头插
void SeqListPushFront(SeqList* ps, SLDateType x) {
assert(ps);
SLCheckCapacity(ps);
for (SLDateType i = ps->size; i > 0; i--) {
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
头插我们只需要从后往前以此将数组数据往后挪一格就行,让前一个数据覆盖掉后一个数据,注意,必须是从后往前,如果从前往后那么后面一个数据还没有移动就被覆盖,就会丢失数据了,最后将所需插入数据插入到第一个,随后对有效数组个数加一。
2.2.5 顺序表的尾删
void SeqListPopBack(SeqList* ps) {
assert(ps);
assert(ps->size > 0);
ps->size--;
}
这里我们可以看到,我们不仅要对ps断言,还要对ps的有效数组个数是否大于0进行断言,如果有效数组个数等于0的,那么就没有可删除的数据,程序退出。如果大于0,我们只需要将有效数组个数减一,并不需要将尾数赋为空,因为在下次用到该内存时,我们一定会对该内容进行修改,所以原来的值可删可不删。
2.2.6 顺序表的头删
void SeqListPopFront(SeqList* ps) {
assert(ps);
assert(ps->size > 0);
for (SLDateType i = 0; i < ps->size - 1; i++) {
ps->a[i] = ps->a[i+1];
}
ps->size--;
}
和头插原理相同,只需从头开始,将后一个覆盖掉前面一个即可。
顺序表的增删查改不仅能在头部尾部,还能在指定的任何位置进行
2.2.7 指定位置的增删查改
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
if (x == ps->a[i])
return i;
}
printf("顺序表中没有%d",x);
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x) {
assert(ps);
SLCheckCapacity(ps);
for (SLDateType i = ps->size; i >=pos; i--) {
ps->a[i] = ps->a[i - 1];
}
ps->a[pos - 1] = x;
ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos) {
assert(ps);
assert(ps->size > 0);
for (SLDateType i = pos-1; i <= ps->size - pos; i++) {
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//顺序表更改pos位置的值
void SeqListRevise(SeqList* ps, int pos, SLDateType x) {
ps->a[pos - 1] = x;
}
在写进行这么多的函数的定义时,我们最好不要一次性将所有函数写完再去测试,这样的话我们可能会很难找到代码的错误,这里我们就需要每次写完一个函数就进行测试,那么我们就需要看到顺序表的打印结果才可以知道是否出现问题。
2.2.8 打印顺序表
void SeqListPrint(SeqList* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
printf("%d ", ps->a[i]);
}
printf("\n");
}
最后如果我们不需要该顺序表,我们对顺序表进行销毁
2.2.9 顺序表的销毁
void SeqListDestroy(SeqList* ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
这里我们讲到了顺序表的初始化、扩容、头部尾部和指定位置的增删查改以及顺序表的打印和销毁。当然还可以实现更多函数功能,如增序排序、元素翻转、同值删除,二分查找等等,这些函数大家可以自己练习实现。
2.2.10 源文件test.c
在每次实现函数后我们都应该在main函数中使用测试,查看是否出现错误,是否需要修改。
void test1() {
SL sl;
SeqListInit(&sl);
SeqListPushBack(&sl, 5);
SeqListPushBack(&sl, 2);
SeqListPrint(&sl);
SeqListDestroy(&sl);
SeqListPrint(&sl);
SeqListPushFront(&sl, 2);
SeqListPushFront(&sl, 3);
SeqListPushFront(&sl, 4);
SeqListPushFront(&sl, 5);
SeqListPushFront(&sl, 6);
SeqListPrint(&sl);
SeqListPopFront(&sl);
SeqListPrint(&sl);
SeqListPopBack(&sl);
SeqListPrint(&sl);
SeqListFind(&sl,4);
SeqListInsert(&sl, 2, 2);
SeqListPrint(&sl);
SeqListErase(&sl, 2);
SeqListPrint(&sl);
SeqListRevise(&sl, 2, 0);
SeqListPrint(&sl);
}
int main() {
test1();
return 0;
}
下面是所有代码运行的结果
3.所有代码
SeqList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;
int size;
int capacity;
}SeqList,SL;
// 对数据的管理:增删查改
void SeqListPrint(SeqList* ps);
void SeqListPushBack(SeqList* ps, SLDateType x);
void SeqListPushFront(SeqList* ps, SLDateType x);
void SeqListPopFront(SeqList* ps);
void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
//顺序表检查容量
void SLCheckCapacity(SeqList* ps);
void SeqListInit(SeqList* ps);//初始化
void SeqListDestroy(SeqList* ps);//销毁
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//初始化
void SeqListInit(SeqList* ps) {
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//销毁
void SeqListDestroy(SeqList* ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
//打印
void SeqListPrint(SeqList* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
printf("%d ", ps->a[i]);
}
printf("\n");
}
//检查容量
void SLCheckCapacity(SeqList* ps) {
assert(ps);
if (ps->capacity == ps->size) {
int newCapacity = ps->capacity == 0 ? 4 : ps->size * 2;
SeqList* tep = (SeqList*)realloc(ps->a, sizeof(SLDateType) * newCapacity);
if (tep == NULL) {
printf("扩容失败");
return;
}
ps->a = tep;
ps->capacity = newCapacity;
}
}
//尾插
void SeqListPushBack(SeqList* ps, SLDateType x) {
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//头插
void SeqListPushFront(SeqList* ps, SLDateType x) {
assert(ps);
SLCheckCapacity(ps);
for (SLDateType i = ps->size; i > 0; i--) {
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
//头删
void SeqListPopFront(SeqList* ps) {
assert(ps);
assert(ps->size > 0);
for (SLDateType i = 0; i < ps->size - 1; i++) {
ps->a[i] = ps->a[i+1];
}
ps->size--;
}
//尾删
void SeqListPopBack(SeqList* ps) {
assert(ps);
assert(ps->size > 0);
ps->size--;
}
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
if (x == ps->a[i])
return i;
}
printf("顺序表中没有%d\n",x);
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x) {
assert(ps);
SLCheckCapacity(ps);
for (SLDateType i = ps->size; i >=pos; i--) {
ps->a[i] = ps->a[i - 1];
}
ps->a[pos - 1] = x;
ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos) {
assert(ps);
assert(ps->size > 0);
for (SLDateType i = pos-1; i <= ps->size - pos; i++) {
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//顺序表更改pos位置的值
void SeqListRevise(SeqList* ps, int pos, SLDateType x) {
ps->a[pos - 1] = x;
}