今天主要讲解顺序表,实现顺序表的尾插,头插,头删,还有尾删等操作,和我们之前写的通讯录的增删查改有类似的功能。接下来让我们开始我们的学习吧。
1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结
构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物
理上存储时,通常以数组和链式结构的形式存储
顺序表其实本质上和数组差不多。
2.顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组
上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储元素。
静态的顺序表通俗点讲就是死的,我们不能改变里面的元素,一开始是多少就是多少,不能实现我们的增删查改一些操作,所以我们写顺序表的时候基本上都是动态。
静态顺序表的代码。
typedef struct SeqList { int arry[100]; int size; }SL
我们可以写成这样,但是如果我存储数据的个数不是100的话又要改,我们可以写成这样,在前面define一下。
同时我们的顺序表可能不只是int 类型的 这个时候我们就可以typedef一下,下次改的时候就方便很多。
但是我们说静态的顺序表存在缺陷,这个时候我们就得动态开辟空间,我们可以写一个动态顺序表。
typedef int SLDataType; typedef struct SeqList { SLDataType* arry;//指向顺序表开始位置 int size;//个数 int capacity;//空间 }SL;
这样的话我们就可以用动态开辟的方式实现了。
首先我们要初始化顺序表。
我们一开始这样写的时候,经过调试可以发现我们的s并没有初始化,在VS2022下直接会提醒你使用未初始化的变量s,这是为什么,是因为我们形参是实参的一份临时拷贝,并不能改变实参,所以我们传值不行,要传地址过去次啊可以解决。
#include"SeqList.h" void SLInit(SL* ps) { ps->arry = NULL; ps->capacity = ps->size = 0; }
void SLtest1() { SL s; SLInit(&s); } int main() { SLtest1(); return 0; }
那我们接下来初始化之后实现一个简单的尾插功能吧
void SLPushBack(SL* ps, SLDataType x) { ps->arry[ps->size] = x; ps->size++; }
我们一开始肯定会这样想,在末尾插入数字,那我们就在末尾找到这个位置,然后插入就行,但是这会有问题,一个原因就是如果这个数组满了,我们就不能插入,插入的化就要扩容,而我们一开始为什么要写动态顺序表的原因就是这个,除了这个原因以外,还有一个原因就是如果我们一开始放的是空指针的时候,这个是时候插入就相当于非法访问,所以我们在插入的时候要进行一下判断。那因为后面头插的时候也会遇到这样的问题,我们干脆给它写成一个函数,这样就方便我们使用。
尾插函数
void SLCheckCapacity(SL* ps) { if (ps->capacity == ps->size) { int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; SLDataType* tmp = (SLDataType*)realloc(ps->arry, sizeof(SLDataType) * NewCapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } ps->arry = tmp; ps->capacity = NewCapacity; } }
所以我们的尾插也可以写成
void SLPushBack(SL* ps, SLDataType x) { SLCheckCapacity(ps); ps->arry[ps->size] = x; ps->size++; }
我们为什么取得是结构体的地址,原因还是形参只是实参的一个临时拷贝,形参改变并不会改变实参
打印顺序表的函数。
void SLPrint(SL* ps) { int i = 0; for (i = 0; i < ps->size; i++) { printf("%d ", ps->arry[i]); } printf("\n"); }
打印函数可以传地址也可以传值,因为打印不会改变我们的内容,我们这里写的就是传址。
实现打印函数,我们其实可以在这些前面都加上assert,做好防御,因为如果传进来的是一个空指针的话,这就会,我们是没有办法进行修改的,所以在函数进来的时候都写好断言,从根本上解决问题。
接下来我们就写一个尾删,尾删就更简单了,直接–就行了,但是我们还要考虑一个问题就是里面一开始的时候有没有数字,如果这个顺序表为空就没有必要删除了。
void SLPopBack(SL* ps) { assert(ps->size > 0); ps->size--; }
从上面顺序表的尾删和尾插来看,其实顺序表相对来说效率还是比较快的,但是头删和头插的效率并不是特别高,我们继续接着写
头插
void SLPushFront(SL* ps, SLDataType x) { assert(ps); SLCheckCapacity(ps); int end = ps->size - 1; while (end >= 0) { ps->arry[end + 1] = ps->arry[end]; end--; } ps->arry[0] = x; ps->size++; }
头插的时候也要进行检查 否则空间满的话,就会越界。
头删
void SLPopFront(SL* ps) { assert(ps); assert(ps->size > 0); int begin = 0; while (begin < ps->size-1) { ps->arry[begin] = ps->arry[begin + 1]; begin++; } ps->size--; }
为什么要assert(ps->size > 0)
是因为如果数都没有的话就不需要删了,所以就需要断言一下。
void SLInsert(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos >= 0 && pos <= ps->size); SLCheckCapacity(ps); int end = ps->size - 1; while (end >= pos) { ps->arry[end + 1] = ps->arry[end]; end--; } ps->arry[pos] = x; ps->size++; }
写完这个其实头插就可以改成SLInsert(SL* ps, 0, SLDataType x)
尾插就可以改成SLInsert(SL* ps, ps->size-1, SLDataType x)
那我们在实现一个在pos位置进行删除的代码。
void SLErase(SL* ps, int pos) { assert(ps); assert(pos >= 0 && pos < ps->size); int begin = pos; while (begin < ps->size-1) { ps->arry[begin] = ps->arry[begin + 1]; begin++; } ps->size--; }
那这样我们的每个功能基本都实现了,因为我们一开始realloc空间,所以我们现在要销毁原来的空间。
void SLDestory(SL* ps) { if (ps->arry != NULL) { free(ps->arry); ps->arry = NULL; } }
这样我们其实还可以用一个菜单来实现这写功能,和通讯录那个差不多,就设置一个菜单,这里小编就不搞了
给大家分享一下完整代码。
SeqList.h
#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> //#define N 100 //typedef int SLDataType; //typedef struct SeqList //{ // int arry[N]; // int size; //}SL; typedef int SLDataType; typedef struct SeqList { SLDataType* arry;//指向顺序表开始位置 int size;//个数 int capacity;//空间 }SL; //初始化 void SLInit(SL* ps); //尾插 void SLPushBack(SL* ps, SLDataType x); //检查空间是否够 void SLCheckCapacity(SL* ps); void SLPrint(SL* ps); void SLPopBack(SL* ps); //头插 void SLPushFront(SL* ps, SLDataType x); //头删 void SLPopFront(SL* ps); void SLInsert(SL* ps, int pos, SLDataType x); void SLErase(SL* ps, int pos); void SLDestory(SL* ps);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"SeqList.h" void SLInit(SL* ps) { ps->arry = NULL; ps->capacity = ps->size = 0; } void SLCheckCapacity(SL* ps) { assert(ps != NULL); if (ps->capacity == ps->size) { int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; SLDataType* tmp = (SLDataType*)realloc(ps->arry, sizeof(SLDataType) * NewCapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } ps->arry = tmp; ps->capacity = NewCapacity; } } void SLPushBack(SL* ps, SLDataType x) { assert(ps != NULL); SLCheckCapacity(ps); ps->arry[ps->size] = x; ps->size++; } void SLPrint(SL* ps) { assert(ps != NULL); int i = 0; for (i = 0; i < ps->size; i++) { printf("%d ", ps->arry[i]); } printf("\n"); } void SLPopBack(SL* ps) { assert(ps); assert(ps->size > 0); ps->size--; } void SLPushFront(SL* ps, SLDataType x) { assert(ps); SLCheckCapacity(ps); int end = ps->size - 1; while (end >= 0) { ps->arry[end + 1] = ps->arry[end]; end--; } ps->arry[0] = x; ps->size++; } void SLPopFront(SL* ps) { assert(ps); assert(ps->size > 0); int begin = 0; while (begin < ps->size-1) { ps->arry[begin] = ps->arry[begin + 1]; begin++; } ps->size--; } void SLInsert(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos >= 0 && pos <= ps->size); SLCheckCapacity(ps); int end = ps->size - 1; while (end >= pos) { ps->arry[end + 1] = ps->arry[end]; end--; } ps->arry[pos] = x; ps->size++; } void SLErase(SL* ps, int pos) { assert(ps); assert(pos >= 0 && pos < ps->size); int begin = pos; while (begin < ps->size-1) { ps->arry[begin] = ps->arry[begin + 1]; begin++; } ps->size--; } void SLDestory(SL* ps) { if (ps->arry != NULL) { free(ps->arry); ps->arry = NULL; } }
今天的分享就到这里了,我们下次再见!!!