嵌入式解谜日志之数据结构—基本概念
数据结构
(1)定义:相互之间存在一种或多种特定关系的数据元素的集合
(2)逻辑结构分类:
- 集合:所有数据在同一集合中,关系平等
- 线性:数据和数据之间一对一的关系
- 树:一对多(目录)
- 图:多对多(地图,网)
(3)物理结构(在内存当中的存储关系)
- 顺序存储:数据存放在连续的存储单位中。逻辑关系和物理关系一致
- 链式:数据存放的存储单位是随机或任意的,可以连续也可以不连续
struct Per数据元素
{char name;//数据项int age;char phone;
}struct Per list[100]; //数据对象
数据对象为一个集合——》包含了多个数据元素——》数据元素有多个数据项组成——》数据项由变量组成
(4)数据的类型:ADT(abstruct datatype):是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。
抽象数据类型=数学模型+操作
(5)程序=数据+算法
1.算法
- 是解决特定问题求解步骤的描述,计算机中表现为指令的有限序列,每条指令表示一个或多个操作。(函数及函数的调用)
①算法的特征
- 输入输出特性:输入是可选的,输出是必须的(可以没有传参)
- 有穷性:执行的步骤会自动结束,不能是死循环,并且每一步是在可以接受的时间内完成(不能死循环,并且每一步都可以在接受的时间内完成)
- 确定性:同一个输入,会得到唯一的输出
- 可行性:每一个步骤都是可以实现的
②算法设计要求
正确性
- 语法正确
- 合法的输入能得到合理的结果
- 对非法的输入,给出满足要求的规格说明
- 对精心选择,甚至刁难的测试都能正常运行,结果正确
可读性:便于交流,阅读,理解(画流程图,给程序备注)
健壮性:输入非法数据,能进行相应的处理,而不是产生异常
高效性:存储低,效率高(代码简化)
③算法时间复杂度分析(衡量算法的好坏)
算法时间复杂度是执行这个算法所花时间的度量。
④时间复杂度推导规则:(事前分析法)
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行函数中,只保留最高阶项
- 如果最高阶存在且不是1,则取除这个项相乘的常数
⑤常见时间复杂度排序
O(1) < O(logn) < O(N) < O(nlogn) < O(n²) (for嵌套)< O(n³) < O(2ⁿ) < O(n!) < O(nⁿ)
时间复杂度越小越好算法越好
// 示例1:基本循环 int i, sum; // 8 for(10000) // n次循环 {sum = i+sum; // 2n +3 } a = c; b = c; a = d; // 时间复杂度:O(n)// 示例2:数学公式计算 int n; // 4 n = (n+1)*n/2; // 4 // 时间复杂度:O(1)// 示例3:嵌套循环 for(i=0; i<n; i++) // n次 {for(j=0; j<n; j++) // n次{// 执行操作} } // 时间复杂度:O(n²)
线性表:零个或多个数据元素的有限序列
顺序表:存储空间是连续的
特征:支持随机访问--head+5 head[0] O(1)
劣势:①插入,删除需要整体移动 O(N)
②不支持动态存储功能。(空间不能随着使用量自动变化,需要手动修改)
(1)线性表特征
- 元素之间是有顺序的
- 如果存在多个元素,第一个元素无前驱,最后一个没有后继
- 其他的元素只有一个前驱和一个后继
- 当线性表元素的个数n(n>=0)定义为线性表的长度
- 当n=0时,为空表
- 在非空的表中每个元素都有一个确定的位置
(2)线性表的常规操作ADT
typedef struct person
{char name[32];char sex;int age;int score;
}DATATYPE; //给结构体struct person取个别名:DATATYPEtypedef struct list
{DATATYPE *head; //数组指针int Tlen; //数组的容量int Clen; //数组有效元素个数
}Seqlist; //表头结构
/** 创建顺序表* 参数:len - 顺序表的初始容量* 返回值:成功返回创建的顺序表指针,失败返回NULL*/
SeqList *CreateSeqList(int len);/** 销毁顺序表* 参数:list - 要销毁的顺序表指针* 返回值:0表示成功,非0表示失败*/
int DestroySeqList(SeqList *list);/** 显示顺序表中的所有元素* 参数:list - 要显示的顺序表指针* 返回值:0表示成功,非0表示失败(如顺序表为空或不存在)*/
int ShowSeqList(SeqList *list);/** 在顺序表尾部插入元素* 参数:list - 目标顺序表指针;data - 要插入的数据元素* 返回值:0表示成功,非0表示失败(如顺序表已满)*/
int InsertTailSeqList(SeqList *list, DATATYPE data);/** 判断顺序表是否已满* 参数:list - 要判断的顺序表指针* 返回值:1表示已满,0表示未满,-1表示参数无效*/
int IsFullSeqList(SeqList *list);/** 判断顺序表是否为空* 参数:list - 要判断的顺序表指针* 返回值:1表示为空,0表示非空,-1表示参数无效*/
int IsEmptySeqList(SeqList *list);/** 在顺序表指定位置插入元素* 参数:list - 目标顺序表指针;data - 要插入的数据元素;pos - 插入位置(从0开始)* 返回值:0表示成功,非0表示失败(如位置无效或表已满)*/
int InsertPosSeqList(SeqList *list, DATATYPE data, int pos);/** 在顺序表中查找指定名称的元素* 参数:list - 要查找的顺序表指针;name - 要查找的名称字符串* 返回值:成功返回元素位置索引(从0开始),失败返回-1*/
int FindSeqList(SeqList *list, char *name);/** 修改顺序表中指定名称元素的数据* 参数:list - 目标顺序表指针;old - 要修改的元素名称;new - 新的数据* 返回值:0表示成功,非0表示失败(如未找到对应元素)*/
int ModifySeqList(SeqList *list, char *old, DATATYPE new);/** 删除顺序表中指定名称的元素* 参数:list - 目标顺序表指针;name - 要删除的元素名称* 返回值:0表示成功,非0表示失败(如未找到对应元素)*/
int DeleteSeqList(SeqList *list, char *name);/** 清空顺序表中的所有元素(保留表结构)* 参数:list - 要清空的顺序表指针* 返回值:0表示成功,非0表示失败*/
int ClearSeqList(SeqList *list);
相关函数:
代码示例
01typedef.c
typedef unsigned int u32_t; // 定义别名
#include <string.h>typedef struct Per
{char name[50];int age;char phone[50];
} PER;int main(int argc, char *argv[])
{u32_t a; // 使用别名定义变量unsigned int b; // 直接使用原始类型定义变量struct Per per; // 使用原始结构体名strcpy(per.name, "zhangsan"); // 1000per.age = 20;PER p; // 使用别名定义结构体变量return 0;
}
运行结果:程序正常编译运行,无输出
结构体的引用:①结构体的定义是变量的引用:(per.age)
②结构体定义的是指针的引用:(per->ag)
内存泄露检测工具
sudo apt-get install valgrind
valgrind ./all
seqlist.h
.h:对应的头文件
目的:防止头文件重复包含带来的重复定义问题
格式:
#ifndef _SEQLIST_H_(文件名转换的)
#define _SEQLIST_H_(声明,宏定义)#endif
#ifndef _SEQLIST_H_
#define _SEQLIST_H_typedef struct person
{char name[32];char sex;int age;int score;
} DATATYPE;typedef struct list
{// 数组指针(指针指向这个存储数据的连续内存:结构体(数组首元素))DATATYPE *head;//head:指向数据存储(DATATYPE)的指针,相当于动态数组的首地址// 数组的容量int tlen;// 数组的有效元素个数int clen;
} SeqList;// 创建顺序表
SeqList *CreateSeqList(int len);// 显示顺序表数据
int ShowSeqList(SeqList *list);// 顺序表尾插
int InsertTailSeqList(SeqList *list, DATATYPE *data);// 是否满
int IsFullSeqList(SeqList *list);// 是否空
int IsEmptySeqList(SeqList *list);// 按位置插入
int InsertPosSeqList(SeqList *list, DATATYPE *data, int pos);// 查找顺序表
int FindSeqList(SeqList *list, char *name);// 修改顺序表
int ModifySeqList(SeqList *list, char *old, DATATYPE *newdata);// 清空表
int ClearSeqList(SeqList *list);// 销毁顺序表
int DestroySeqList(SeqList *list);// 删除一个元素
int DeleteSeqList(SeqList *list, char *name);
//获得有效元素个数
int GetSizeSeqList(SeqList *list);DATATYPE* GetItemSeqlist(SeqList*list, int pos);#endif
seqlist.c
#include "seqlist.h"//包入自己写的头文件,用""引用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
SeqList *CreateSeqList(int len)//创建顺序表
{SeqList *sl = malloc(sizeof(SeqList)); // 分配顺序表结构体内存//解释sizeof(Seqlist):包含数据存储空间和tlen,clenif (NULL == sl){perror("CreateSeqList malloc error\n"); // 内存分配失败处理return NULL;}sl->head = malloc(sizeof(DATATYPE) * len); // 分配数据存储空间//解释sizeof(DATATYPE)*len:相当于有len(传入的个数)个DATATYPE数组if (NULL == sl->head){perror("CreateSeqList malloc2 error\n"); // 数据空间分配失败处理return NULL;}sl->tlen = len; // 设置总长度sl->clen = 0; // 初始化当前长度为0return sl;
//两个结构体的内存空间
}
//list->head:结构名指向结构体中的元素
int InsertTailSeqList(SeqList *list, DATATYPE *data)//顺序表尾插
{// list->head[list->clen] =*data;//list->clen代表顺序表的数据当前位置,当前位置的数组下标位置if (IsFullSeqList(list)) // 检查是否已满{printf("seqlist is full\n"); // 已满提示return 1;}/*list->head[list->clen]:表示当前顺序表位置sizeof(DATATYPE):表示DATATYPE结构体大小data是DATAYPE结构体类型*/memcpy(&list->head[list->clen], data, sizeof(DATATYPE)); // 复制数据到尾部list->clen++; // 当前长度加1return 0;
}int IsFullSeqList(SeqList *list)//判断顺序表是否满
{return list->clen == list->tlen; // 判断当前有效长度是否等于数组总容量
}int ShowSeqList(SeqList *list)//遍历顺序表中的数据
{int len = GetSizeSeqList(list); // 获取当前元素个数int i = 0;for (i = 0; i < len; ++i){//解释list->head[i].name:顺序表名指向结构体元素head,但head(数组的首地址)为指针指向另一个结构体,继续指向结构体中的元素nameprintf("name:%s sex:%c age:%d score:%d\n", list->head[i].name,list->head[i].sex, list->head[i].age, list->head[i].score);};return 0;
}/*** @brief Get the Size Seq List object** @param list 被查询的顺序表* @return int 顺序表有效元素的个数*/
int GetSizeSeqList(SeqList *list)
{return list->clen; // 返回当前元素个数
}int IsEmptySeqList(SeqList *list)//判断当前长度是否为0
{return 0 == list->clen;
}/*** @brief 按位置插入元素** @param list 待插入的顺序表* @param data 需要插入的数据* @param pos 顺序表的下标* @return int 0 表示成功 1表示失败*/
int InsertPosSeqList(SeqList *list, DATATYPE *data, int pos)//按位置(pos)插入元素
{if (IsFullSeqList(list)) // 检查是否已满{printf("seqlist is full\n");return 1;}int len = GetSizeSeqList(list);if (pos < 0 || pos > len) // 检查位置是否合法{printf("pos is incorrect\n");return 1;}// 整体后移,让出位置for (int i = list->clen; i > pos; i--){// list->head[i] = list->head[i - 1];memcpy(&list->head[i], &list->head[i - 1], sizeof(DATATYPE)); // 后移元素}memcpy(&list->head[pos], data, sizeof(DATATYPE)); // 插入新元素list->clen++; // 当前长度加1return 0;
}/*** @brief 按名字查找信息** @param list 待查询的顺序表* @param name 要查询的人员信息名字* @return int 成功的 0>=len-1 失败 -1*/
int FindSeqList(SeqList *list, char *name)
{int len = GetSizeSeqList(list);for (int i = 0; i < len; i++) // 遍历查找{if (0 == strcmp(list->head[i].name, name)) // 比较名字{return i; // 找到返回位置}}return -1; // 未找到返回-1
}DATATYPE* GetItemSeqlist(SeqList*list, int pos)// 检查位置是否合法
{int len = GetSizeSeqList(list);if (pos < 0 || pos > len) {printf("pos is incorrect\n");return NULL;//返回值为指针类型}return &list->head[pos]; // 返回指定位置元素的指针
}int ModifySeqList(SeqList *list, char *oldname, DATATYPE *newdata)//修改元素
{int ret = FindSeqList(list, oldname); // 查找要修改的元素if(-1 == ret) // 未找到{printf("modify failure...\n");return 1;}memcpy(&list->head[ret], newdata, sizeof(DATATYPE)); // 修改数据return 0;
}// 清空表:原理:将数据重新定位到开头,用新的数据去覆盖
int ClearSeqList(SeqList *list)
{list->clen = 0; // 将当前长度设为0return 0;
}// 销毁顺序表
int DestroySeqList(SeqList *list)
{free(list->head); // 空间使用的是堆,需要释放数据存储空间free(list); // 释放顺序表结构体return 0;
}
int DeleteSeqList(SeqList *list, char *name)//删除一个元素
{int pos=FindSeqList(list, name);//按名字查找if(-1==pos){printf("no found per %s\n",name);return -1;}//将该位置之后的说有元素向前移动一位int i=0;for (i=pos;i<list->clen-1;i++){memccpy(&list->head[i],&list->head[i+1],sizeof(DATATYPE));}list->clen--;//长度减一return 0;}
内存泄漏检测工具(命令):sudo apt-get install valgrind
编译:valgrind ./all
堆的空间释放顺序:先释放head,在释放顺序表
main.c
#include<stdio.h>
#include<stdlib.h>
#include"seqlist.h"
int main(int argc, char **argv)
{SeqList *s1=CreateSeqList(5);//创建一个顺序表DATATYPE data[] = {{"zhangsan", 'm', 20, 80}, // 定义测试数据{"lisi", 'f', 23, 84},{"wangmaizi", 'f', 32, 90},{"guanerge", 'm', 50, 91},{"liubei", 'm', 51, 92},};InsertTailSeqList(s1, &data[0]); // 将data[i]中的数据尾部插入数据InsertTailSeqList(s1, &data[1]); //dat[i]相当于代表二维数组一整排的元素,所以不用在指向data中的元素InsertTailSeqList(s1, &data[2]);InsertPosSeqList(s1,&data[2],1);ShowSeqList(s1);char want_name[20]="lisi";int ret=FindSeqList(s1, want_name);//找lisi的位置,并把位置放在ret中if(-1==ret){printf("can't find per %s\n",want_name);}else{DATATYPE *tmp=GetItemSeqlist(s1,ret);//检查位置在数据中是否合法printf("find per,%s %d\n",tmp->name,tmp->score);}ModifySeqList(s1,"list",&data[4]);//修改元素ShowSeqList(s1);//遍历DestroySeqList(s1);//删除顺序表//system("pause");return 0;
}
}
Makefile:工程管理工具(代替gcc用make编译)
1.第一个版本:
#目标名:依赖文件
#第二行的开头是一个tab
all:main.c seqlist.cgcc main.c seqlist.c -o all
clean://clean为清除,重新编译//命令 make cleanrm all
2.第二个版本:
#目标名:依赖文件
#第二行的开头是一个tab
#在一条规则中,$^代表依赖文件 $@代表规则中的目标名
#$^,$@都是Makefile的内部变量
all:main.c seqlist.cgcc $^ -o $@ #使用内部变量简化命令cp main.c 1.c #添加复制命令
clean:rm all $^ # 删除可执行文件和源文件
3.第三个版本:(最终版本)
# .o 目标文件
SRC= main.o # 定义源文件列表
SRC+= seqlist.o
DST=all # 定义目标文件名
CC=gcc # 定义编译器
LIB= -lm # 定义链接库%.o:%.c$(CC) -c $^ -o $@ # 编译规则:将.c文件编译为.o文件$(DST):$(SRC)$(CC) $^ -o $@ $(LIB) # 链接规则:将.o文件链接为可执行文件clean:rm $(DST) *.o # 清理规则:删除可执行文件和目标文件