顺序表之(条条有注释助你吃透顺序表以及基于顺序表实现的通讯录)

2023-10-15 02:28

本文主要是介绍顺序表之(条条有注释助你吃透顺序表以及基于顺序表实现的通讯录),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

顺序表

        顺序表是线性表的一种,而线性表指的是具有相同特性的一类数据结构的统称,这些相同特性即在逻辑结构(人为想象)上一定是线性的,在物理结构(内存存储中)上不一定是线性的

        顺序表的底层结构是数组(在后续的顺序表实现中很重要),所以顺序表在逻辑结构上是线性的,在物理结构上也是线性的

静态顺序表

格式:

typedef struct 静态顺序表名

{

        ElementType data[MaxSize]; // 存储元素的数组

        int length; // 当前顺序表的长度

} ;

缺点:空间小不够用,空间大太浪费

动态顺序表

格式:

typedef struct 动态顺序表名

{

        ElementType *data; // 存储元素的指针

        int length; // 当前顺序表的长度

        int capacity; // 当前顺序表的容量

} ;

!!! 关于动态顺序表你必须要知道的内容:

动态顺序表的实现通常包含两个部分:一个指向动态分配内存块的指针(通常称为data或elements),以及一个用于记录顺序表当前元素个数和容量的变量(通常称为size和capacity)。当创建动态顺序表时,会动态分配一块内存空间,并将 data 指针指向这个内存块。这个内存块可以根据需要进行动态调整,以适应顺序表的容量需求

动态顺序表实现:

我们要实现一个动态顺序需要经历以下两个主要部分:

  • 顺序表的初始化和释放
  • 顺序表中元素的插入和删除

我们这次仍然采用多文件的方式: 

首先分别创建头文件SeqList.h、测试文件test.c(主函数所在文件)、功能实现函数SeqList.c

主函数的创建

test.c文件:

#include "SeqList.h"
void SLTest()
{SL sl;SLInit(&sl);  //对动态顺序表的初始化//顺序表尾插SLPushBcak(&sl, 1);SLPushBcak(&sl, 2);SLPushBcak(&sl, 3);SLPushBcak(&sl, 4);//1 2 3 4SLPrint(&sl);//顺序表头插SLPushFront(&sl, 5);//5 1 2 3 4SLPushFront(&sl, 6);//6 5 1 2 3 4 SLPushFront(&sl, 7);//7 6 5 1 2 3 4SLPrint(&sl);//尾删SLPopBack(&sl);SLPrint(&sl);SLPopBack(&sl);SLPrint(&sl);//在指定位置前插入数据SLInsert(&sl, 1, 8);SLPrint(&sl);//删除指定位置的数据SLErase(&sl, 2);bool findRet = SLFind(&sl, 8);if (findRet) {printf("找到了!\n");SLPrint(&sl);}else{printf("没找到!\n");}SLDestroy(&sl);
}int main()
{SLTest();return 0;
}

我们先将整体的主函数创建出来然后对里面的每一种函数进行具体分析 

①关于SL sl的解释:关于SL请接着看下面的顺序表初始化中提到的内容,先提前告诉各位SL是一个重命名后的结构体类型名,SL sl表示我们创建了一共SL结构体类型的变量sl,当然你也可以叫它struct SeqList结构体类型的变量sl,只不过后者读起来麻烦。

②关于各个函数这个&sl的解释:当你声明一个结构体变量 sl 时,sl 的类型是 struct SeqList,它是这个结构体类型的一个实例,因此,sl 是整个结构体的变量,sl 的地址就是整个结构体 struct SeqList 的地址,通过sl->的方式你可以访问结构体中的每个成员变量。

顺序表的初始化

SeqList.h文件:

#pragma once
#include <stdio.h>
//动态顺序表
typedef int SLDataType;  //切换类型时只需要切换int即可
struct SeqList
{SLDataType* a;int size;			int capacity;  
};  
typedef struct SeqList SL;
//顺序表的初始化
void SLInit(SL* ps);

①关于“typedef int SLDataType”的解释:我们将int类型的重命名为SLDataType,后者有着与一样的大小等,增强了代码的可读性和可维护性:比如我们想要切换指针a的类型时只需要

②关于“SLDataType* a”的解释:在顺序表中,底层逻辑是使用数组来存储数据。成员变量 a 可以被看作是数组的首元素地址,通过指针 a 可以访问数组中的元素。(理解这一点很重要)

SeqList.c文件:

void SLInit(SL* ps) {ps->a = NULL;				  //ps->a表示指向结构体SL中的成员变量的指针ps->size = ps->capacity = 0;  
}

①关于SL* ps的解释:在前面的test.c文件中我们知道了SLInit函数的实参为&sl,即获取了整个结构体的地址,这里的形参用SL* ps来接收它,ps此时就是一个指向SL结构体类型的指针,我们可以通过它来访问SL结构体中的各个成员变量

②关于ps->a = NULL的解释:一般情况下我们使用指针访问结构体类型变量的时候都是使用->的方式,这里代表的意思是访问SL结构体中的a成员变量,然后又由于成员a的结构体变量为SLdataType*类型(实际是int*)的,所以在未使用指针变量a之前要先将它设置为空指针。

③关于ps->size = ps ->capacity = 0的解释:这里就是讲SL结构体中的成员变量size和capacity赋值为0,目的就是进行初始化

顺序表的释放

SeqList.h文件:

//顺序表的释放
void SLDestroy(SL* ps);

SeqList.c文件:

void SLDestroy(SL* ps) {if (ps->a)free(ps->a);    //释放ps->a = NULL;ps->size = ps->capacity = 0;
}

①关于if(ps->a)、free(ps->a)以及ps->a -NULL的解释:其实也没啥好解释的就是判断使用完a指针后a指针指向的内存空间是否为空(或者a是否为空指针),如果不为空就释放内存空间,释放完成后就像a指针设置为空指针就行

之后的所以函数开头的非空判断不做解释,自己理解

顺序表的尾部插入

SeqList.h文件:

//尾部插入
void SLPushBcak(SL* ps, SLDataType x);

SeqList.c文件:

//尾插
void SLPushBcak(SL* ps, SLDataType x) //传递结构体指针
{if (ps == NULL && SLIsEmpty(ps) != NULL){return;}//1、空间不够,扩容(一般以2倍或者1.5倍进行扩容)SLCheckCapacity(ps);//2、空间足够,直接尾插ps->a[ps->size++] = x;  //直接把它当成数组更容易理解
}

①关于(SL* ps,SLDataType x)的解释:ps不用解释它是指向整个结构体,SLDataType x表示的是我们要操作的数据。

②关于SLCheckCapacity(ps)的解释:该函数是用来处理空间不够的问题的其代码如下:

void SLCheckCapacity(SL* ps)
{if (ps->size == ps->capacity)//如果当两个值相等时,证明再要进行插入操作就需要进行开辟新的位置{int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * 2 * sizeof(SLDataType));if (tmp == NULL)//防止扩容失败{perror("realloc fail\n");return 1;}ps->a = tmp;ps->capacity = newCapacity;}
}

 1、关于if (ps->size == ps->capacity)的解释:我们知道顺序表的底层逻辑是数组,我们现在的尾插函数其实就是在数组的最后一个位置后面插入一个新的元素,这个数组的空间我们之前是以及开辟过的,但是当我们使用的时候可能不会记住后面到底还有多少空间没有被使用,申请的空间是否以及被填满了?它的判断条件就是顺序表中有效的数据个数size与顺序表当前所占空间大小capacity之间的关系,当二者相等时证明空间刚好被用完,此时想要在顺序表后面重新插入数据就需要在顺序表后面开辟新的内存空间

2、关于int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity的解释:这里是一个三目运算符,意思是如果此时capacity的值为零即顺序表现在所占空间为0那么就申请四个整型的内存空间,如果不为零那就将capacity的值扩大两倍即新内存空间是旧内存空间的两倍(注意这里只是表达了扩大两倍的想法还并未进行真正的扩容),判断完成的值会被赋值给newCapacity(其实这里的newCapacity也是在程序报错后才发现需要进行补充的,如果没有这一步会导致在下面真正开辟的时候由于顺序表初始化中ps->capacity=0的存在capacity的值为0最后开辟的内存空间就会为0了,创建一个newCapacity先进行简单的验证一下代码会更加的严谨)

3、关于 (SLDataType*)realloc(ps->a, newCapacity * 2 * sizeof(SLDataType))的解释:在上面我们将想要进行扩容多少内存空间的想法传递给了newCapacity,此时我们就要开始真正的内存开辟,前面我们学过了三种动态内存开辟的函数malloc、calloc以及realloc,在这里我们选择realloc函数因为它能使动态内存管理更加灵活,它的函数原型是void* realloc(void* ptr,size_t size);在这里的意思就是为结构体中的a成员变量开辟新的内存空间,调整后的空间大小为newCapacity*2*sizeof(SLDataType),后面乘上一个sizeof(SLDataType)的这是为了使开辟的新空间能让SLDataType*a类型数据都能够刚好放下(这样解释应该能理解吧~,如果你懂可以不用看关于这点的解释)

4、关于SLDataType* tmp 的解释:我们不能保证每次都成功开辟动态内存空间,所以我们采用一个临时的指针变量tmp指向我们动态开辟的内存空间的结果,如果我们开辟动态内存空间失败即tmp指针指向的内存空间为空(也可以理解为此时tmp指针为空指针)就用perror函数进行报错,如果我们开辟动态内存空间成功就将tmp指针指向的新内存空间的地址传递给a指针这样就实现了对a指针指向的内存空间的扩建,最后将表示此时内存空间大小的newCapacity赋值给capacity,到此动态内存空间的开辟完成了

③ 关于ps->a[ps->size++] = x的解释:因为是尾插,而在a指针指向的空间中a[size]其实就代表了最后一个数的位置,我们想要在这个数之和再插入一个数x,那么x的位置就应该是size+1,所以我们将[]内的内容写成了[ps->size++]即获取此时内存空间中元素的个数后再++,此时a指针指向下标为size++的位置,然后将要插入的数x赋值到该内存空间中(emm差强人意的解释,应该也能懂)

顺序表的头部插入

//头部插入
void SLPushFront(SL* ps, SLDataType x);

 SeqList.c文件:


//头插
void SLPushFront(SL* ps, SLDataType x) {if (ps == NULL && SLIsEmpty(ps) != NULL){return;}SLCheckCapacity(ps);//空间足够,原先数据后移一位for (size_t i = ps->size; i > 0; i--){ps->a[i] = ps->a[i - 1];//当i=1时表示ps->a[1] = ps->a[0],数组的第一个数字被赋值给了第二个数字}ps->a[0] = x;  //将要插入的内容插入a[0]的位置ps->size++;//此时的数组总个数增加一个
}

 ①关于ps->a[i] = ps->a[i-1]的解释:for循环的意义应该不用过多解释了,ps->a[i] = ps->a[i-1]的意思也很简单就是让前一个数赋值给后一个数然后最后会将a[0]的位置刚好空出来

②关于ps->a[0]=x的解释:这...就是把要插入的数x插入前面循环空出来的a[0]处

顺序表的头尾部删除

//头部删除
void SLPpopBack(SL* ps);

SeqList.c文件: 

//尾删
void SLPopBack(SL* ps) {if (ps == NULL ){return;}ps->size--;//不需要再进行赋值为0的操作了(ps->a[ps-soze-1] = 0)
}

 ①关于ps->size--的解释,这里既然是删去最后一个变量的值,也不需要将顺序表的最后一个值赋值为0后再删除,多此一举,直接将用于统计顺序表中有效数据个数的size--即可。

顺序表的尾部删除

//头部删除
void SLPpopFront(SL* ps);

SeqList.c文件: 

//头删
void SLPpopFront(SL* ps) {if (ps == NULL){return;}//让后面的数据往前挪动一位for (size_t i = 0; i < ps->size - 1;i++){//最后一次进来的i的值为size-1ps->a[i] = ps->a[i + 1];}ps->size--;//删除数据当前有效数据个数减一个
}

①关于整个for循环的解释:如果顺序表中有六个数据,那么最后一个数据的下标为size-1,我们想要的是将第一个数据删除,该数据下标为0,我们采用覆盖的办法实现这一目的,将第二个数据赋值给第一个数据,用下标来说就是将ps->a[0] = ps->a[0+1],可以看到的是二者之间的关系是后者比前者多1,所以整体循环的写法就是ps->a[i] = ps->a[i + 1],至于循环的次数问题请看下图:

可以发现六个数据中第六个数据的下标为[size-1=5],所以我们的循环次数应该是size-1次

顺序表的打印 

//打印函数
void SLPrint(SL* ps);

SeqList.c文件: 

//打印
void SLPrint(SL* ps)
{if (ps == NULL ){return;}//循环打印for (size_t i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}

判断顺序表中有效数据是否非空 

//判断非空
bool SLIsEmpty(SL* ps);

SeqList.c文件: 

//判断顺序表中有效数据是否非空
bool SLIsEmpty(SL* ps)  
{ if (ps == NULL){return;}return ps->size == 0;  
}

 在指定位置前插入数据

//在指定位置前插入
void SLInsert(SL* ps, int pos, SLDataType x);

SeqList.c文件: 


//在指定位置前插入
void SLInsert(SL* ps,int pos, SLDataType x)
{if (ps == NULL &&pos>=0&&pos<=ps->size){return;}SLCheckCapacity(ps);//把pos位置及以后的数据往后挪动一位//循环条件里i的初始值为size或者size-1都可以,但是不同初始值对应不同的结束条件for (size_t i= ps->size;i>pos;i--){ps->a[i] = ps->a[i - 1];  //把前面的数据赋值给后面}ps->a[pos] = x;ps->size++;
}

①关于pos的解释:int pos表示我们想要进行数据操作的具体位置

②关于for循环的解释:

如果pos等于3,那么for循环就会变成:for(size_t i = 6;i >3;i--) {ps-a[i] = ps->a[i-1] } 

删除指定位置的数据

//在指定位置删除
void SLErase(SL* ps, int pos);

SeqList.c文件: 

void SLErase(SL* ps,int pos)
{if (ps == NULL && pos >= 0 && pos <= ps->size){return;}SLCheckCapacity(ps);//把pos位置以及其之后的数据往前挪动一位//最后一次进来的i的数据ps->size-2for (int i = pos; i < ps->size - 1; i++){ps->a[i] = ps->a[i + 1];}ps->size--;
}

 ~不再做过多解释了~

在指定位置查找数据

//在指定位置查找
bool SLFind(SL* ps, SLDataType x);

SeqList.c文件: 

bool SLFind(SL* ps, SLDataType x)
{if (ps == NULL  ){return;}for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return true;}}return false;
}

 ~不再做过多解释了~

最终文件: 

SeqList.h文件

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>//更改类型为通讯录数据类型typedef int SLDataType;  //这里将int类型成名为了SLDataType,更换类型时只需要将int换成其它类型即可,它与int类型有着相同的语义和内存表示只是struct SeqList
{SLDataType* a;	//在顺序表中,a 指向存储数据的内存块的起始位置int size;			//顺序表中有效的数据个数int capacity;   //顺序表中当前空间大小
};  //为结构体重新取名为SL    //结构体类型,结构体的重命名,括号后面的那个到底代表什么?
typedef struct SeqList SL;//在顺序表中,底层逻辑是使用数组来存储数据。成员变量 a 可以被看作是数组的首元素地址,通过指针 a 可以访问数组中的元素。
//size 表示顺序表中有效的数据个数,它可以看作是数组的元素个数。在顺序表中,只有前 size 个元素是有效的,后面的元素是无效的或未使用的空间。
//capacity 表示顺序表中当前的空间大小,它可以看作是数组的容量。它表示了顺序表当前能够容纳的元素个数。当顺序表中的元素个数达到 capacity 时,可能需要进行扩容操作,以便存储更多的元素。
//所以,可以将 a 视为数组的首元素地址,size 视为数组的元素个数,capacity 视为数组的空间大小。通过操作指针 a,可以访问和操作数组中的元素。//顺序表的初始化
void SLInit(SL* ps);
//顺序表的释放
void SLDestroy(SL* ps);//头部/尾部 插入/删除
void SLPushBcak(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPpopFront(SL* ps);//打印函数
void SLPrint(SL* ps);
bool SLIsEmpty(SL* ps);
//在指定位置前插入
void SLInsert(SL* ps, int pos, SLDataType x);
//在指定位置删除
void SLErase(SL* ps, int pos);
//在指定位置查找
bool SLFind(SL* ps, SLDataType x);

test.c文件

#include "SeqList.h"void SLInit(SL* ps) {ps->a = NULL;				  //ps->a表示指向结构体SL中的成员变量的指针ps->size = ps->capacity = 0;  
}void SLDestroy(SL* ps) {if (ps->a)free(ps->a);    //释放ps->a = NULL;ps->size = ps->capacity = 0;
}//扩容函数
void SLCheckCapacity(SL* ps)
{if (ps->size == ps->capacity)//如果当两个值相等时旧证明再要进行插入操作就需要进行开辟新的位置{int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//因为初始化时capacity已经被初始化为0了SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * 2 * sizeof(SLDataType));if (tmp == NULL)//防止扩容失败{perror("realloc fail\n");return 1;}ps->a = tmp;ps->capacity *= newCapacity;}
}//尾插
void SLPushBcak(SL* ps, SLDataType x) //传递结构体指针o
{if (ps == NULL ){return;}//1、空间不够,扩容(一般以2倍或者1.5倍进行扩容)SLCheckCapacity(ps);//2、空间足够,直接尾插ps->a[ps->size++] = x;  //直接把它当成数组更容易理解
}//头插
void SLPushFront(SL* ps, SLDataType x) {if (ps == NULL){return;}SLCheckCapacity(ps);//空间足够,原先数据后移一位for (size_t i = ps->size; i > 0; i--){ps->a[i] = ps->a[i - 1];//当i=1时表示ps->a[1] = ps->a[0],数组的第一个数字被赋值给了第二个数字}ps->a[0] = x;  //将要插入的内容插入a[0]的位置ps->size++;//此时的数组总个数增加一个
}//尾删
void SLPopBack(SL* ps) {if (ps == NULL ){return;}ps->size--;//不需要再进行赋值为0的操作了(ps->a[ps-soze-1] = 0)
}//头删
void SLPpopFront(SL* ps) {if (ps == NULL){return;}//让后面的数据往前挪动一位for (size_t i = 0; i < ps->size - 1;i++){//最后一次进来的i的值为size-1ps->a[i] = ps->a[i + 1];}ps->size--;//删除数据当前有效数据个数减一个
}//打印
void SLPrint(SL* ps)
{if (ps == NULL ){return;}//循环打印for (size_t i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}//判断顺序表中有效数据非空
bool SLIsEmpty(SL* ps)  
{ if (ps == NULL){return;}return ps->size == 0;  
}//在指定位置前插入
void SLInsert(SL* ps,int pos, SLDataType x)
{if (ps == NULL &&pos>=0&&pos<=ps->size){return;}SLCheckCapacity(ps);//把pos位置及以后的数据往后挪动一位//循环条件里i的初始值为size或者size-1都可以,但是不同初始值对应不同的结束条件for (size_t i= ps->size;i>pos;i--){ps->a[i] = ps->a[i - 1];}ps->a[pos] = x;ps->size++;
}void SLErase(SL* ps,int pos)
{if (ps == NULL && pos >= 0 && pos <= ps->size){return;}SLCheckCapacity(ps);//最后一次进来的i的数据ps->size-2for (int i = pos; i < ps->size - 1; i++){ps->a[i] = ps->a[i + 1];}ps->size--;
}bool SLFind(SL* ps, SLDataType x)
{if (ps == NULL){return;}for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return true;}}return false;
}

SeqList.c文件

#include "SeqList.h"
void SLTest()
{SL sl;SLInit(&sl);  //对动态顺序表的初始化//顺序表尾插SLPushBcak(&sl, 1);SLPushBcak(&sl, 2);SLPushBcak(&sl, 3);SLPushBcak(&sl, 4);//1 2 3 4SLPrint(&sl);//顺序表头插SLPushFront(&sl, 5);//5 1 2 3 4SLPushFront(&sl, 6);//6 5 1 2 3 4 SLPushFront(&sl, 7);//7 6 5 1 2 3 4SLPrint(&sl);//尾删SLPopBack(&sl);SLPrint(&sl);SLPopBack(&sl);SLPrint(&sl);//在指定位置前插入数据SLInsert(&sl, 1, 8);SLPrint(&sl);//删除指定位置的数据SLErase(&sl, 2);bool findRet = SLFind(&sl, 8);if (findRet) {printf("找到了!\n");SLPrint(&sl);}else{printf("没找到!\n");}SLDestroy(&sl);
}int main()
{SLTest();return 0;
}

最终效果:

通讯录的实现:

通讯录底层就是顺序表

看似是在通讯录中增加的数据,实际上是在通讯录中增加的:

下面是所需要的所有文件:(自行理解吧┭┮﹏┭┮)

 Contact.h文件:

#pragma once
#include "SeqList.h"
#define NAME_MAX 100
#define SEX_MAX 10
#define TEL_MAX 15
#define ADDR_MAX 100
//创建保存联系人数据的结构
struct ContactInfo {//采用定长数组(长也长不到哪里去)char name[NAME_MAX];char sex[SEX_MAX];int age;char tel[TEL_MAX];char addr[ADDR_MAX];
};typedef struct ContactInfo CInfo;
//通讯录的底层是顺序表来实现,给顺序表套了一个壳子,后面的pcon其实就相当于ps
typedef struct SeqList contact;//通讯录的初始化和销毁
void ContactInit(contact* pcon);
void ContactDestory(contact* pcon);//添加联系人
void ContactAdd(contact* pcon);
//删除联系人
void ContactDel(contact* pcon);
//修改联系人
void ConatactModify(contact* pcon);
//查看通讯录
void ContactShow(contact* pcon);
//查找指定联系人
void ContactFind(contact* pcon);

SeqList.h文件:

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "Contact.h"//更改顺序表类型为通讯录数据类型
typedef struct ContactInfo SLDataType;//让结构体类型作为顺序表的数据类型(套娃)这样就可以在顺序表中使用结构体类型struct SeqList
{SLDataType* a;	//在顺序表中,a 指向存储数据的内存块的起始位置int size;			//顺序表中有效的数据个数int capacity;   //顺序表中当前空间大小
};  //为结构体重新取名为SL    //结构体类型,结构体的重命名,括号后面的那个到底代表什么?
typedef struct SeqList SL;//在顺序表中,底层逻辑是使用数组来存储数据。成员变量 a 可以被看作是数组的首元素地址,通过指针 a 可以访问数组中的元素。
//size 表示顺序表中有效的数据个数,它可以看作是数组的元素个数。在顺序表中,只有前 size 个元素是有效的,后面的元素是无效的或未使用的空间。
//capacity 表示顺序表中当前的空间大小,它可以看作是数组的容量。它表示了顺序表当前能够容纳的元素个数。当顺序表中的元素个数达到 capacity 时,可能需要进行扩容操作,以便存储更多的元素。
//所以,可以将 a 视为数组的首元素地址,size 视为数组的元素个数,capacity 视为数组的空间大小。通过操作指针 a,可以访问和操作数组中的元素。//顺序表的初始化
void SLInit(SL* ps);
//顺序表的释放
void SLDestroy(SL* ps);//头部/尾部 插入/删除
void SLPushBcak(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPpopFront(SL* ps);//打印函数
void SLPrint(SL* ps);
bool SLIsEmpty(SL* ps);
//在指定位置前插入
void SLInsert(SL* ps, int pos, SLDataType x);
//在指定位置删除
void SLErase(SL* ps, int pos);
//在指定位置查找
bool SLFind(SL* ps, SLDataType x);

test.c文件:

#include "SeqList.h"
void contact01()
{contact con;//通讯录初始化ContactInit(&con);//往通讯录中插入数据ContactAdd(&con);ContactShow(&con);ContactDel(&con);ContactShow(&con);ContactFind(&con);//通讯录的销毁ContactDestory(&con);}void menu() {printf("******************通讯录******************\n");printf("*******1、添加联系人  2、删除联系人*******\n");printf("*******3、修改联系人  4、查找联系人*******\n");printf("*******5、查看通讯录        0、退出*******\n");printf("******************************************\n");
}int main()
{int op = -1;//定义一个通讯录contact con;ContactInit(&con);do {menu();printf("请选择您的操作:\n");scanf("%d", &op);switch (op){case 1 :ContactAdd(&con);break;case 2:ContactDel(&con);break;case 3:ContactModify(&con);break;case 4:ContactFind(&con);break;case 5:ContactShow(&con);break;case 0:printf("感谢使用~\n");break;default:printf("输入非法,请重新输入\n");break;}} while (op != 0);//op为0退出通讯录ContactDestory(&con);return 0;
}

Contact.c文件:

#include "Contact.h"
#include "SeqList.h"//涉及统一函数的处理方法,但是如果实际传入的数据不同它所代表的意思就不一样
void ContactInit(contact* pcon) {//借用之前的顺序表的初始化对通讯录进行初始化SLInit(pcon);
}
void ContactDestory(contact* pcon) {//借用之前的顺序表的初始化对通讯录进行初始化SLDestroy(pcon);
}//添加联系人
void ContactAdd(contact* pcon) 
{//还是套娃CInfo info;printf("请输入联系人姓名:\n");scanf("%s",info.name);printf("请输入联系人的性别:\n");scanf("%s", info.sex);printf("请输入联系人的年龄:\n");scanf("%s", &info.age);printf("请输入联系人的电话:\n");scanf("%s", info.tel);printf("请输入联系人的地址:\n");scanf("%s", info.addr);//联系人数据都获取到了,并保存到了结构体info成员变量中//往通讯录(顺序表)中插入数据SLPushBcak(pcon,info); 
}//查找函数
int FindByName(contact* pcon,char name[])
{for (int i = 0; i < pcon->size; i++){if (strcmp(pcon->a[i].name, name) == 0){return i;}}return -1;
}//删除联系人
void ContactDel(contact* pcon)
{
//直接强制要求用户使用联系人名称来查找printf("请输入要删除的用户名称:\n");char name[NAME_MAX];scanf("%s", name);int find = FindByName(pcon, name);if (find < 0){printf("要删除的联系人不存在!\n");return;}//找到了,要删除findidx位置的数据SLErase(pcon, find);
}
//修改联系人
void ContactModify(contact* pcon) {char name[NAME_MAX];printf("请输入要修改的用户名称:\n");scanf("%s", name);//获取到的通讯里(顺序表)下标的位置int find = FindByName(pcon, name);if (find < 0){printf("要修改的用户不存在!\n");return;}printf("请输入新的用户名称:\n");scanf("%s", pcon->a[find].name);printf("请输入新的用户性别:\n");scanf("%s", pcon->a[find].sex);printf("请输入新的用户年龄:\n");scanf("%d", pcon->a[find].age);printf("请输入新的用户电话:\n");scanf("%s", pcon->a[find].tel);printf("请输入新的用户地址:\n");scanf("%s", pcon->a[find].addr);printf("修改成功!\n");}
//查看通讯录
void ContactShow(contact* pcon)
{//打印通讯录所有的数据//先打印表头文字printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");for (int i = 0; i < pcon->size; i++){printf("%-4s %-4s %-4d %-4s %-4s\n",pcon->a[i].name,pcon->a[i].sex,pcon->a[i].age,pcon->a[i].tel,pcon->a[i].addr);}}//查找指定联系人
void ContactFind(contact* pcon) {char name[NAME_MAX];printf("请输入要查找的用户名称:\n");scanf("%s", &name);int find = FindByName(pcon, name);if (find < 0){printf("该联系人不存在!\n");}//打印当前找到的联系人printf("%-4s %-4s %-4d %-4s %-4s\n",pcon->a[find].name,pcon->a[find].sex,pcon->a[find].age,pcon->a[find].tel,pcon->a[find].addr);}

 SeqList.c文件:

#include "SeqList.h"void SLInit(SL* ps) {ps->a = NULL;				  //ps->a表示指向结构体SL中的成员变量的指针ps->size = ps->capacity = 0;  
}void SLDestroy(SL* ps) {if (ps->a)free(ps->a);    //释放ps->a = NULL;ps->size = ps->capacity = 0;
}//扩容函数
void SLCheckCapacity(SL* ps)
{if (ps->size == ps->capacity)//如果当两个值相等时旧证明再要进行插入操作就需要进行开辟新的位置{int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//因为初始化时capacity已经被初始化为0了SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * 2 * sizeof(SLDataType));if (tmp == NULL)//防止扩容失败{perror("realloc fail\n");return 1;}ps->a = tmp;ps->capacity *= newCapacity;}
}//尾插
void SLPushBcak(SL* ps, SLDataType x) //传递结构体指针o
{if (ps == NULL ){return;}//1、空间不够,扩容(一般以2倍或者1.5倍进行扩容)SLCheckCapacity(ps);//2、空间足够,直接尾插ps->a[ps->size++] = x;  //直接把它当成数组更容易理解
}//头插
void SLPushFront(SL* ps, SLDataType x) {if (ps == NULL){return;}SLCheckCapacity(ps);//空间足够,原先数据后移一位for (size_t i = ps->size; i > 0; i--){ps->a[i] = ps->a[i - 1];//当i=1时表示ps->a[1] = ps->a[0],数组的第一个数字被赋值给了第二个数字}ps->a[0] = x;  //将要插入的内容插入a[0]的位置ps->size++;//此时的数组总个数增加一个
}//尾删
void SLPopBack(SL* ps) {if (ps == NULL ){return;}ps->size--;//不需要再进行赋值为0的操作了(ps->a[ps-soze-1] = 0)
}//头删
void SLPpopFront(SL* ps) {if (ps == NULL){return;}//让后面的数据往前挪动一位for (size_t i = 0; i < ps->size - 1;i++){//最后一次进来的i的值为size-1ps->a[i] = ps->a[i + 1];}ps->size--;//删除数据当前有效数据个数减一个
}//打印
void SLPrint(SL* ps)
{if (ps == NULL ){return;}//循环打印for (size_t i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}//判断顺序表中有效数据非空
bool SLIsEmpty(SL* ps)  
{ if (ps == NULL){return;}return ps->size == 0;  
}//在指定位置前插入
void SLInsert(SL* ps,int pos, SLDataType x)
{if (ps == NULL &&pos>=0&&pos<=ps->size){return;}SLCheckCapacity(ps);//把pos位置及以后的数据往后挪动一位//循环条件里i的初始值为size或者size-1都可以,但是不同初始值对应不同的结束条件for (size_t i= ps->size;i>pos;i--){ps->a[i] = ps->a[i - 1];}ps->a[pos] = x;ps->size++;
}void SLErase(SL* ps,int pos)
{if (ps == NULL && pos >= 0 && pos <= ps->size){return;}SLCheckCapacity(ps);//最后一次进来的i的数据ps->size-2for (int i = pos; i < ps->size - 1; i++){ps->a[i] = ps->a[i + 1];}ps->size--;
}//先隐藏着吧这个在通讯录里面没用
//bool SLFind(SL* ps, SLDataType x)
//{
//	if (ps == NULL)
//	{
//		return;
//	}
//	for (int i = 0; i < ps->size; i++)
//	{
//		if (ps->a[i] == x)
//		{
//			return true;
//		}
//	}
//	return false;
//}

 哪点看不懂的可以在评论区留言┭┮﹏┭┮,有空会写详细的解释

顺序表经典算法OJ题:

移除元素:

题目:给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回移除后数组的新长度。不能使用额外的数组空间,你必须仅使用0(1)额外空间并原地修改数组,元素的顺序可以改变,你不需要考虑数组中超出新长度后面的元素。

伪双指针法:

#include <stdio.h>
int rempveElement(int* nums, int numSize, int val)
{int src = 0, dst = 0;while (src < numSize) {if (nums[src] == val){src++;}else{nums[dst] = nums[src];dst++;src++;}}return dst;
}int main()
{int arr[4] = { 3,2,2,3 };int sz = sizeof(arr) / sizeof(arr[0]);int num = rempveElement(arr, sz, 3);printf("%d", num);return 0;
}

合并两个数组:

题目:给你两个按非递减顺序排列的整数数组nums1和nums2,另有两个整数m和n,分别表示nums1和nums2中的元素数目,请你合并nums2到nums1中,使合并后的数组同样按照非递减顺序排列。

注意:最终合并后的数组不应由函数返回,而是存储在数组nums1中,为了应对这种情况,nums1的初始长度伪m+n,其中m个元素表示应合并的元素,后n给元素为0,应忽略。nums2的程度为n。

void merge(int* nums1, int numSize, int m, int* nums2, int num2Size, int n)
{int l1 = m - 1, l2 = n - 1;int l3 = m + n - 1;while (l1 >= 0 && l2 >= 0){if (nums1[l1] > nums2[l2]){nums1[l3--] = nums1[l1--];}else{nums1[l3--] = nums2[l2--];}}while (l2 >= 0){nums1[l3--] = nums2[l2--];}
}int main()
{int nums1[] = { 1, 2, 3, 0, 0, 0 };int m = 3;int nums2[] = { 2, 5, 6 };int n = 3;int sz1 = sizeof(nums1) / sizeof(nums1[0]);int sz2 = sizeof(nums2) / sizeof(nums2[0]);merge(nums1, sz1, m, nums2, sz2, n);for (int i = 0; i < m + n; i++){printf("%d ", nums1[i]);}printf("\n");return 0;
}

顺序表的问题及思考

1. 中间/头部的插⼊删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷⻉数据,释放旧空间。会有不⼩的消耗。
3. 增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?

~over~ 

这篇关于顺序表之(条条有注释助你吃透顺序表以及基于顺序表实现的通讯录)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/214747

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”