目录
数据结构之链表:C语言之链表-优快云博客文章浏览阅读711次,点赞18次,收藏7次。数据结构中单链表和双链表的详细介绍https://blog.youkuaiyun.com/2401_87944878/article/details/144423576
https://blog.youkuaiyun.com/2401_87944878/article/details/144423576
基础概念
数据结构:计算机存储,组织数据的方式;数组是最基本的数据结构。
线性表:具有相同性质的数据集合,其在物理(位置,地址)上不一定连续,但是在逻辑(由上一个元素找到的是下一个元素)上是一定连续的;就好比:一本书的目录上有不同章节,在书本中章与章之间有内容间隔,所以其在物理上是不连续的,但是可以通过第一章找到第二章,他在逻辑上是连续的;
顺序表是线性表的一种;顺序表的底层就是数组;所以顺序表在物理和逻辑上都是连续的;
顺序表
1)静态顺序表
静态顺序表是由一个定长数组和一个存储该顺序表中有效数据个数的变量;
typedef int SqlistDateType;
//静态顺序表
struct Sqlist
{
SqlistDateType arr[100]; //一个定长数组
int size; //顺序表中有效元素个数
};
静态顺序表的形式较为简单,不再过多赘述。因为静态顺序表存储数据个数有限且不能调整,所以我们更多的使用动态顺序表。
2)动态顺序表
动态顺序表与静态顺序表相比,将定长数组改成了指针,还增加了当前空间的大小;
typedef int SqlistDateType;
//动态顺序表的定义
typedef struct Sqlist
{
SqlistDateType* arr; //指针
int size; //顺序表中有效数据的个数
int capacity; //当前空间的大小
}SL;
动态顺序表功能的基本实现
1)顺序表的初始化
//顺序表的初始化
void Sqlist_start(SL* ps)
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
注意: 此处传的是一级指针;因为我们在函数中直接定义了一个结构体(SL p;),而不是一个地址,此处要区别于链表;
2)顺序表的销毁
释放ps->arr的空间,将数组地址置为空,将size和capacity置为0;
//顺序表的销毁
void Sqlist_Destory(SL* ps)
{
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
3)顺序表的打印
此处只是整形打印;
//打印顺序表
void SLprint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
printf("%d ", ps->arr[i]);
printf("\n");
}
4)给顺序表申请空间(扩容)
再对顺序表的数据进行插入的时候,首先要判断空间够不够,不够要先申请空间;
在扩容的时候,我们通常是对空间进行成倍的增加,而不是缺了就加一个,不够再加一个;因为relloc再开辟空间的时候,如果后面的空间不够开辟,他就会重新找一块空间重新开辟,释放原来空间,如果频繁这样就会导致程序运行效率大大降低;
//顺序表扩容
void SLBuyplace(SL* ps)
{
//先检查指针的有效性
assert(ps);
//判断顺序表的空间是否位空,如果是空开辟4个直接的空间,不是空对空间进行翻倍
//运用三目操作符
int newcapacity =( ps->capacity==0 ? 4 : (ps->capacity) * 2);
//开辟空间
SqlistDateType* arr_copy; //此处创建一个临时指针是为了防止空间开辟失败,导致ps->arr变成空
arr_copy = (SqlistDateType*)realloc(ps->arr, newcapacity*sizeof(SqlistDateType));
if (arr_copy == NULL)
perror("realloc fail");
//将顺序表中的数组地址,空间大小进行更新
ps->arr = arr_copy;
ps->capacity = newcapacity;
}
5)尾插
将数据x插入到顺序表的尾部;
//顺序表尾插
void SLpushback(SL* ps, SqlistDateType x)
{
//先检查指针
assert(ps);
//判断空间是否够用
if (ps->capacity == ps->size)
SLBuyplace(ps);
//插入新的数据
*((ps->arr) + ps->size) = x;
//更新顺序表的有效数据
ps->size++;
}
6)头插
头插与尾插相似,只不过要先对数组元素整体先后移动;
//顺序表的头插
void SLpushfront(SL* ps, SqlistDateType x)
{
assert(ps);
if (ps->capacity == ps->size)
SLBuyplace(ps);
//对数组元素进行平移
for (int i = ps->size; i > 0; i--)
ps->arr[i] = ps->arr[i-1];
//重新定义数组第一个元素
ps->arr[0] = x;
ps->size++;
}
7)尾删
尾删比较简单,只需要对顺序表中的实际存储元素-1即可;不需要对数组中之后一个元素进行操作,只需要将其看作无效数据就可以了。
//顺序表的尾删
void SLDelback(SL* ps)
{
//检查指针有效性;
//顺序表有效数据个数不能是0
assert(ps);
assert(ps->size);
ps->size--;
}
8)头删
头删与尾删相比还是多了一步:对数组元素向前整体移动一位;
//顺序表的头删
void SLDelfront(SL* ps)
{
assert(ps);
assert(ps->size);
//判断空间够不够
if (ps->size == ps->capacity)
SLBuyplace(ps);
//对数组元素向前平移
for (int i = 0; i<ps->size; i++)
ps->arr[i] = ps->arr[i + 1];
//最后一组:arr[size-1]=arr[size];
ps->size--;
}
9)下标查找
下标查找是指定位置插入和删除中,不可缺少的一部分;
运用循环,遍历顺序表找到数组下标;
//顺序表的查找
int SLFind(SL* ps,SqlistDateType x)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
return i;
}
//没有找到,则返回一个无效下标
return -1;
}
10)指定下标插入
与头插相比,此时我们只需要对,指定下标之后的元素向后移动就可以了;
//指定下标插入
void SLInsert(SL* ps, int pos, SqlistDateType x)
{
assert(ps);
//判断空间够不够
if (ps->size == ps->capacity)
SLBuyplace(ps);
//对pos下标之后的元素向后移动
for (int i = ps->size; i > pos; i--)
ps->arr[i] = ps->arr[i-1];
//最后一组是:arr[pos+1]=arr[pos];
ps->arr[pos] = x;
//对顺序表有小元素个数进行调整
ps->size++;
}
11)指定下标删除
指定下标删除与头删的区别在于,其是将下标之后的元素整体先前移动,而不是将所有的元素都向前移动;
//指定下标删除
void SLDEL(SL* ps, int pos)
{
assert(ps);
assert(ps->size);
//将pos后的数据全部向前移动
for (int i = pos; i<ps->size-1; i++)
ps->arr[i] = ps->arr[i + 1];
//最后一组是:arr[size-2]=arr[size-1]
ps->size--;
}
动态顺序表的基本功能已经实现了;基于这些基本功能,我们可以自行创作一个通讯录项目;
通讯录项目
我们会使用三个文件来完成通讯录项目;
Contact.h用于声明函数,定义结构体和顺序表;Contact.c用于定义函数;
通讯录于顺序表相比就是将,数据类型进行了变化;上面对于顺序表的数据类型我们使用的是int,而在通讯录中数据类型我们会使用结构体替代int;用结构体来存储用户的姓名,性别,电话,地址等数据。
1)定义通讯录
//先进行通讯录的定义
//有联系人的姓名,性别,电话,住址;
#define NAME_MAX 50
#define GENDER 10
#define TEL 20
#define ADRE 80
typedef struct Contact_List
{
char name[NAME_MAX];
char gender[GENDER];
char tel[TEL];
char adress[ADRE];
}ContList;
typedef ContList SqlistDateType;
//动态顺序表的定义
typedef struct Sqlist
{
SqlistDateType* arr; //指针
int size; //顺序表中有效数据的个数
int capacity; //当前空间的大小
}SL;
2)初始化
注意:此处我们要在初始化函数的形参中使用到SL*的指针,但是Contact_list.h文件是没有定义SL的(在Sqlist.h中定义的),Sqlist.h头文件又包含了Contact_list.h头文件,不能再让Contact_list.h文件包含Sqlist.h文件了,否则会出现循环嵌套;所以在这里,我们使用前置声明并且把struct Sqlist重新命名为Con,而不再是SL;
通讯录的初始化,我们直接调用顺序表的初始化即可;
//注意这个Contact.h文件中是没有定义SL的所以我们要进行前置声明
typedef struct Sqlist Con;
//此处进行前置声明并且将struct Sqlist 重命名位Con
//通讯录的初始化
void ContL_start(Con* con)
//此处我们可以直接调用顺序表的初始化;
{
Sqlist_start(con);
}
3)为通讯录申请空间(扩容)
通讯录的扩容直接调用顺序表的扩容即可;
//为通讯录申请空间(扩容)
void ConBuyplace(Con* con)
{
//此处还是直接调用
SLBuyplace(con);
}
4)通讯录的打印
在写通讯录的添加之前,先完成通讯录的打印;
//通讯录的打印
Conprintf(Con* con)
{
assert(con);
assert(con->size);
//先打印表头和边框
printf("%-10s %-10s %-20s %-10s\n", "姓名", "性别", "电话", "地址");
printf("---------------------------------------------------------\n");
//进行循环打印
for (int i = 0; i < con->size; i++)
{
printf("%-10s", con->arr[i].name);
printf("%-10s", con->arr[i].gender);
printf("%-20s", con->arr[i].tel);
printf("%-10s", con->arr[i].adress);
printf("\n");
}
printf("\n");
}
5)通讯录的添加
默认尾插;
//通讯录的添加
void Conpushback(Con* con)
{
//检验指针的有效性
assert(con);
//检验空间是否足够
if (con->capacity == con->size)
ConBuyplace(con);
//储存新插入的联系人数据
ContList newperson;
printf("请输入联系人姓名:");
scanf("%s", newperson.name);
printf("请输入联系人的性别:");
scanf("%s", newperson.gender);
printf("请输入联系人电话:");
scanf("%s", newperson.tel);
printf("请输入联系人的地址:");
scanf("%s", newperson.adress);
//联系人的所有数据都已经存储到newperson中了
//再调用顺序表的尾插函数,完成尾插
SLpushback(con, newperson);
printf("\n\n");
}
6)通讯录查找
此处以姓名查找为例。
//通讯录的查找
int Confind_byname(Con* con,char name[NAME_MAX])
{
//通讯录可以用名字,电话,地址等方式查找。
//此处我们以名字查找为例;
assert(con);
assert(con->size);
for (int i = 0; i < con->size; i++)
{
int c = strcmp(con->arr[i].name, name);
if (c == 0)
return i;
}
//没有找到,返回一个无效地址
return -1;
}
7)通讯录删除指定人员数据
先查找要删除人员的位置,在调用顺序表指定位置删除函数,完成通讯录指定人员的删除;
//通讯录指定人员删除
void ConDel(Con* con)
{
assert(con);
assert(con->size);
//先查找要删除的人
char del[NAME_MAX];
printf("输入要删除人员的姓名:");
scanf("%s", del);
int c = Confind_byname(con, del);
//判断是否找到
if (c < 0)
printf("没找到");
else
{
//调用顺序表指定位置删除函数
SLDEL(con, c);
}
}
8)通讯录的修改
对通讯录已有人员进行修改;
//通讯录已有人员的修改
void ConModify(Con* con)
{
assert(con);
assert(con->size);
//先找到要修改的人员
char mod[NAME_MAX];
printf("输入要修改人员的名称:");
scanf("%s", mod);
int c = Confind_byname(con, mod);
//判断是否找到
if (c < 0)
printf("没找到");
else
{
printf("输入修改电话:");
scanf("%s", con->arr[c].tel);
}
}
通讯录的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<windows.h>
#include"Sqlist_boke.h"
void menu(void)
{
printf("**********************************************\n");
printf("********** 1.添加 2.删除 *********\n");
printf("********** 3.查找 4.修改 *********\n");
printf("********** 5.打印 0.退出 *********\n");
printf("**********************************************\n");
}
int main()
{
//先设置一个通讯录,再初始化
Con p;
ContL_start(&p);
int c;
//运用循环,多次使用;
do
{
//先打印目录
menu();
printf("请选择:");
scanf("%d", &c);
switch (c)
{
case 1:
system("cls");
Conpushback(&p);
system("cls");
break;
case 2:
system("cls");
ConDel(&p);
system("cls");
break;
case 3:
{
system("cls");
char name[NAME_MAX];
printf("输入查找人员姓名:");
scanf("%s", name);
int a = Confind_byname(&p,name);
printf("%-10s", p.arr[a].name);
printf("%-10s", p.arr[a].gender);
printf("%-20s", p.arr[a].tel);
printf("%-10s", p.arr[a].adress);
printf("\n");
break;
}
case 4:
system("cls");
ConModify(&p);
system("cls");
break;
case 5:
system("cls");
Conprintf(&p);
break;
case 0:
break;
default :
printf("输入错误,请重新输入");
}
} while (c);
return 0;
}