1.为什么要学习数据结构
为了提高程序的运行效率
2.什么是数据?
数据是指对事物的进行的记录并可以鉴别的符号,可以是文字,图片,字母,特殊字符,视频,音频…,对我们有用的信息都可以称为数据。
3.什么是结构?
结构体是指元素(数据)的相互关联和相互作用的关系,比如学生,图书,交通干线等等
4.根据数据元素之间的关系,分成4个基本结构
-
集合:结构中的数据是在同属空间内,没有其它关系,比如刚入学的学生
-
线性结构:数据为一一对应的关系,比如已分发学号的学生,图书馆的图书
-
树状结构(层次关系):数据为一对多的关系,家族的族谱,公司的架构
-
图状结构(网状结构):数据为多对多的关系,城市与城市之间的交通干线
5.什么是数据结构
数据结构体 = 数据+数据与数据之间的关系
6.数据结构的组成部分
- 逻辑结构
- 数据之间的内在关系,通常有集合,线性表(一一对应的关系),树(一对多关系),图(多对多的关系)
- 存储结构
- 顺序存储(数组),链式存储(链表),不同的存储方式对数据的处理效率有很大的影响
7.什么是算法
算法是为了实现计算的目的而制定一系列的计算步骤
8.数据结构与算法的关系
数据结构其实就是研究如何有效的组织数据,并提高数据的处理效率,通过研究各种数据之间的内部关系,使用某种特定的方式存储,在此基础上对数据实施各种操作,这种方式称为算法。
9.算法的分析
算法分析是指在正常程序工作下,对其优略分析,一个好的算法指:
1.算法对应程序的消耗时间少
2.算法对应程序的消耗空间少
3.算法结构性好,易读,易移植,易调试(少bug)
10.时间复杂度(应对笔试)
描述程序运行的时间复杂度BigO即大O表示法,计算的是程序运行的次数而不是程序运行的时间,时间复杂度 = 程序运行的次数
公式: T(n) = O(f(n))
T(n) : 表示算法的渐进时间复杂度
f(n) : 程序运行的次数
O : 表示正比关系
例子1:
for(int i = 1; i <= n; i++)
{
x++;
}
// 1 + n + n + n==> 1+3n==>如果n是无限大,那么1和3可以忽略不记,所以,此算法的时间复杂度为
// O(n)
例子2:
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
x++;
}
}
O(n*n)==O(n^2)
例子3:
for(int i = 1; i <= n; i++) // 1000
{
x++;
}
for(int i = 1; i <= n; i++) // 1000*1000
{
for(int j = 1; j <= n; j++)
{
x++;
}
}
O(n+n^2)==>n无限大的时候,n是常数,所以时间复杂度是O(n^2)
常用的时间复杂度
1.常数阶O(1)
int a = 1;
int b = a;
2.对数阶O(logN)==》2^k = N ==> K = logN,k为程序运行的次数
int i = 1;
while(i < n)
i = 2*i;
3.线性阶O(N)
for(int i = 1; i <= n; i++)
x++
4.线性对数阶O(NlogN)
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <=n; j*=2)
{
x++;
}
}
5.平方阶(n^2)
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
x++;
}
总结:
程序 = 数据结构体 + 算法
11.练习,计算以下两个片段的时间复杂度
//片段1
for(int i = 1; i <= n; i=i*2)
{
printf("%d\t",i);
}
// 2^i=n ==》 i = logN
// 片段2
int i = 0;
int s = 0;
while(s < n)
{
++i;
s+=i;
}
// 1+2+3+...+k = n ==> k(k+1)/2 = n ==> k^2+k = 2n ==>2和k都是常量所以 k = 根号n
1.线性表
1.概念
一对一的数据所组成的关系称为线性表,注意线性表是逻辑结构
线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同的特性,即同一数据对象(26个英文字母),相邻的数据之间是一对一的关系,比如B字母节点(数据节点)前面是A字母节点,后面是C字母节点,线性表的特性如下:
(1) 存在唯一的一个被称为"第一个"的数据元素
(2) 存在唯一的一个被称为"最后一个"的数据元素
(3) 处了第一个外,集合中每一个数据元素只有一个前驱节点
(4) 除了最后一个外,集合中每个数据元素均只有一个后驱节点
2.举例
生活中的线性表例子非常多,比如一个班级中的以学号编排的学生一座图书馆中的以序号编排的图书、一条正常排队等候的队列、一摞从上到下堆叠的餐盘,这些都是线性表。他们的特点都是:除了首尾两个元素,其余任何一个元素前后都对应相邻的另一个元素。
注意:
1.线性表是一种数据内部的逻辑关系,与存储形式无关
2.线性表既可以采用连续的顺序存储(数组),也可以采用离散的链式存储(链表)
3.顺序表和链表都称为线性表
2.顺序表
1.概念
顺序表:顺序存储的线性表
链式表:链式存储的线性表,简称链表
顺序存储就是将数据存储到一片连续的内存中,在C语言环境下,可以是具名的栈数组,或者是匿名的堆数组。
存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系。当采用顺序存储的方式来存放数据时,唯一能用来表达数据间本身的逻辑关系的就是存储位置。比如队列中的两个人,小明和小花,如果小明在逻辑上排在相邻的小花的前面,那么在存储位置上也必须把小明存放在相邻的小花的前面。
栈空间:char buf[4];
自动申请空间,函数结束后自动释放,{}内定义的局部变量,{}过后自动释放
空间大小为8M
堆空间:malloc(16) calloc(4,sizeof(int)) realloc()
手动分配空间,手动释放空间,空间大小为实际物理内存,空间生命周期为整个程序的生命周期
2.基本操作
1.顺序表设计
一般而言,为了方便操作顺序表,需要一个专门管理顺序表 的"管理结构体",管理结构体一般会包括:
(1) 顺序表总容量
(2) 顺序表当前最末元素下标位置
(3) 顺序表指针
管理结构体示例代码
typedef struct
{
int capacity; // 顺序表容量
int last; // 最末元素下标
int * data; // 顺序表,以整型数据为例
}sequenceList;
初始化
所谓初始化就是建立一个不包含任何元素的顺序表,设置好管理结构体中 的表的总容量、末元素下标,申请好顺序表内存空间等系列准备工作。
下面是初始化顺序表的示例代码:
// 初始化顺序表
sequenceList * init(int cap)
{
sequenceList *s = malloc(sizeof(sequenceList));
if(s == NULL)
return NULL;
s->capacity = cap;
s->last = -1;
s->data = malloc(cap * sizeof(int));
if(s->data == NULL)
{
free(s);
return NULL;
}
return s;
}
增删节点
在顺序表中增加一个数据,可以有多种方式,比如在原数组的末尾增加,或者在原素组的头部增加,或者在中间任意一个位置增加,更具需求来定。
下面是增删示例代码:
// 在表尾插入内容
bool insert(sequenceList *s,int data)
{
if(isFull(s))
return false;
s->data[++s->last] = data;
return true;
}
// 删除数据
bool removeData(sequenceList *s,int data)
{
if(isEmpty(s))
return false;
// 找到要删除的内容
int i = 0;
while(i < s->last)
{
if(s->data[i] == data)
{
break;
}
i++;
}
for(int j = i; j < s->last; j++)
{
s->data[j] = s->data[j+1];
}
s->last--;
return true;
}
销毁顺序表
一个顺序最后不再需要,应当要释放其所占用的内存空间,这被称为顺序表的销毁。
示例代码如下:
void destroy(sequenceList *s)
{
if(s == NULL)
return;
free(s->data);
free(s);
}
demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef int dataType;
// 设计顺序表管理结构体
typedef struct seqList
{
int cap; // 空间个数
int last;// 记录空间的位置
dataType *data; // 指向首元素地址
}mc_seqList;
// 初始化顺序表
mc_seqList *init_seqList(int cap)
{
mc_seqList *mc_sl = malloc(sizeof(mc_seqList));
if(mc_sl == NULL)
return NULL;
// 初始化结构体成员
mc_sl->cap = cap;
mc_sl->last = -1;//初始没有数据
mc_sl->data = calloc(cap,sizeof(dataType));
if(mc_sl->data == NULL)
return NULL;
return mc_sl;
}
bool isFull(mc_seqList *list)
{
if(list->cap == list->last+1)
return true;
return false;
}
bool insert_list(dataType data,mc_seqList *list)
{
if(list == NULL)
return false;
// 判断表是否满
if(isFull(list))
{
return false;
}
list->data[++list->last] = data;
//*(list->data+(++list->last)) = data;
return true;
}
// 判断表是否为空
bool isEmpty(mc_seqList *list)
{
if(list->last == -1)
return true;
return false;
}
// 显示
void showList(struct seqList *list)
{
// 判断表是否为空
if(isEmpty(list))
{
printf("list is NULL\n");
return;
}
for(int i = 0; i <= list->last; i++)
{
printf("%d\t",list->data[i]);
}
printf("\n");
}
// 删除数据
mc_seqList *remove_data(dataType data, mc_seqList *list)
{
if(isEmpty(list))
return NULL;
// 查找需要删除的数据
int i = 0;
for(; i <= list->last; i++)
{
if(list->data[i] == data)
break;
}
// 后面进行对前面的覆盖
for(int j = i; j < list->last; j++)
{
list->data[j] = list->data[j+1];
}
list->last--;
return list;
}
// 销毁顺序表
void destroy_list(mc_seqList *list)
{
free(list->data);
list->data = NULL;
free(list);
list = NULL;
}
int main(int argc, char const *argv[])
{
// 初始化管理结构体
mc_seqList *list = init_seqList(10);
if(list == NULL)
{
printf("init seq list failed: \n");
free(list);
return -1;
}
for(int i = 0; i < 5; i++)
{
dataType data;
scanf("%d",&data);
// 将数据插入到顺序表
insert_list(data,list);
}
// 删除数据
list = remove_data(10,list);
list = remove_data(20,list);
list = remove_data(50,list);
// 显示
showList(list);
// 销毁表
destroy_list(list);
return 0;
}
作业:
创建一个顺序表,并从键盘接收数字输入,将输入的正整数按从小到大的顺序插入顺序表,并在输入负整数的时候将其绝对值数据删除。每次输入后,将顺序表的内容打印到屏幕上。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef int dataType;
// 设计顺序表管理结构体
typedef struct seqList
{
int cap; // 空间个数
int last;// 记录空间的位置
dataType *data; // 指向首元素地址
}mc_seqList;
// 初始化顺序表
mc_seqList *init_seqList(int cap)
{
mc_seqList *mc_sl = malloc(sizeof(mc_seqList));
if(mc_sl == NULL)
return NULL;
// 初始化结构体成员
mc_sl->cap = cap;
mc_sl->last = -1;//初始没有数据
mc_sl->data = calloc(cap,sizeof(dataType));
if(mc_sl->data == NULL)
return NULL;
return mc_sl;
}
bool isFull(mc_seqList *list)
{
if(list->cap == list->last+1)
return true;
return false;
}
// 判断表是否为空
bool isEmpty(mc_seqList *list)
{
if(list->last == -1)
return true;
return false;
}
// 找位置
int getPost(dataType data, mc_seqList *list)
{
int post = 0;
while(post <= list->last && data > list->data[post])
post++;
return post;
}
// 插入数据
bool insert_list(dataType data, mc_seqList *list)
{
if(list == NULL || isFull(list))
return false;
// 找位置插入
int post = getPost(data, list);
// 将post以及post后面的数据集体往后移动
for(int i = list->last; i >= post; i--)
{
list->data[i+1] = list->data[i];
}
// 将data放到原来post位置
list->data[post] = data;
// 数据+1
list->last++;
return true;
}
// 显示
void show_list(struct seqList *list)
{
// 判断表是否为空
if(isEmpty(list))
{
printf("list is NULL\n");
return;
}
for(int i = 0; i <= list->last; i++)
{
printf("%d\t",list->data[i]);
}
printf("\n");
}
// 删除数据
mc_seqList *remove_data(dataType data, mc_seqList *list)
{
if(isEmpty(list))
return NULL;
// 查找需要删除的数据
int i = 0;
for(; i <= list->last; i++)
{
if(list->data[i] == data)
break;
}
if(i > list->last)
return list;
// 后面进行对前面的覆盖
for(int j = i; j < list->last; j++)
{
list->data[j] = list->data[j+1];
}
list->last--;
return list;
}
// 销毁顺序表
void destroy_list(mc_seqList *list)
{
free(list->data);
list->data = NULL;
free(list);
list = NULL;
}
int main(int argc, char const *argv[])
{
// 初始化管理结构体
mc_seqList *list = init_seqList(10);
if(list == NULL)
{
printf("init seq list failed:");
return -1;
}
while(1)
{
dataType data;
if(scanf("%d",&data) == 0)
break;
if(data > 0)
{
bool ret = insert_list(data,list);
if(!ret)
{
printf("数据已满\n");
break;
}
show_list(list);
}
else if(data < 0)
{
bool ret = remove_data(-data,list);
if(!ret)
{
printf("数据为空\n");
break;
}
show_list(list);
}
}
// 销毁顺序表
destroy_list(list);
return 0;
}
3.顺序表的优缺点总结
顺序存储中,由于逻辑关系是用物理位置来表达的,因此从上述示例代码可以很清楚看到,增删数据都是比较繁琐的,需要成片的移动数据。顺序表对数据节点的增删操作是很不友好。
总结其特点如下:
1.优点
不需要多余的信息来记录数据的关系,存储密度高
所有数据顺序存储在一片连续的内存中,支持立即访问任意一个随机数据,比如上述顺序表中第i个节点是s->data[i]
2.缺点
插入、删除时需要保持数据的物理位置反映其逻辑关系,需要成片移动数据
当数据节点较多时,需要一整片较大的连续内存空间