一、什么是数据结构
1.数据结构的起源:
1968年,美国高德纳教授,《计算机程序设计艺术》 第一卷 《基本算法》,开创了数据结构和算法的先河
数据结构是研究数据之间关系和操作的学科,而非计算方法
数据结构 + 算法 = 程序 美国沃斯提出,揭示了程序的本质
2.数据结构相关基础概念:
数据:所有能够输入到计算机中,能够被程序处理的描述客观事物的符号
数据项:有独立含义的数据的最小单位,也称为域;
数据元素:组成数据的有一定含义的基本单位
结点(节点、记录)
数据结构:相互之间存在一种或多种特点关系的数据元素的集合
算法:数据结构中所具备的功能、解决某种特定问题的方法
3.数据结构的三大方面
1.数据的逻辑关系
2.数据的存储关系
3.数据结构的运算
二、数据结构的逻辑和存储关系
逻辑关系:
集合: 数据元素同属于一个集体,但是元素之间没有任何关系
线性结构: 数据元素之间存在一对一的关系
树形结构: 数据元素之间存在一对多的关系
图形结构: 数据元素之间存在多对多的关系
存储关系:
顺序结构:数据元素存储在连续的内存中,用数据元素的相对位置来表示关系
优点:支持随机访问、访问,查找的效率高、适合用于频繁的查找
缺点:对插入,删除的效率低
对空间的利用率低、对内存空间大小的要求高
链式结构:数据元素存储在彼此相互独立的内存中,每个独立的元素也叫结点,每个结点增加
一项数据项用于存储其它相关结点的地址,以此表示结点之间的关系
优点:插入,删除更快,更方便;空间利用率高,对内存要求不高(对比)
缺点:查找访问效率极低,只能从头到尾逐个访问
注意:存储结构、逻辑结构采用哪种根据实现难度,时间,空间,操作习惯等综合分析后使用
逻辑结构与存储结构的关系
线性表 顺序 链式
树 链式 顺序
图 顺序+链式
三、数据结构的运算
1.创建数据结构 create(creat)
2.销毁数据结构 destory
3.清空数据结构 clean
4.数据结构排序 sort
5.插入元素 insert
6.删除元素 delete
7.访问元素 access
8.查询元素 query
9.修改元素 modify
10.遍历数据结构 show ergodic print
四、线性表和链式表
线性表
数据项:
存储元素的连续首地址
表的容量
当前元素的数量
#define TYPE int
tpyedef struct Array
{
TYPE *ptr;
size_t cal;
size_t cnt;
}Array;
运算:
1.创建数据结构 create(creat)
Array* create_array(size_t size)
{
//分配顺序表结构的内存
Array* arr = malloc(sizeof(Array));
//分配存储元素的内存
arr->ptr = malloc(sizeof(TYPE)*size);
//记录表容量
arr->cal = size;
//初始化元素数量
arr->cnt = 0;
return arr;
}
2.销毁数据结构 destory
3.清空数据结构 clean
4.数据结构排序 sort
5.插入元素 insert
6.删除元素 delete
7.访问元素 access
8.查询元素 query
9.修改元素 modify
10.遍历数据结构 show ergodic print
注意:1.要保持数据元素的连续性
2.不要越界
链表 list
元素(结点)的数据项:
数据域:可以放任何类型的若干个数据项
指针域:指向下一个结点
由若干个结点通过指针域连接到一起形成链表
#define TYPE int
typedef struct Node
{
TYPE data;
struct List *next;
}Node;
不带头结点的单链表:
第一个结点的数据域存储有效数据
缺点:添加,删除结点时,可能修改指向第一个结点的指针,参数需要使用二级指针,
才能更改指针的指向,比较麻烦。
Node* create_node(TYPE data)
{
Node *node = malloc(sizeof(Node));
node->data = data;
node->next = NULL;
return node;
}
带头结点的单链表:
第一个结点的数据域不存储有效数据,仅仅只是使用它的指针域永远指向链表第一个有效的结点
优点:添加,删除时会比不带头结点的链表更方便;
注意:其他操作要从头结点的下一个结点开始;
Node* create_node(TYPE data)
{
Node *node = malloc(sizeof(Node));
node->data = data;
node->next = NULL;
return node;
}
五、功能受限的表结构
对表结构的部分功能加以限制,形成特殊的表结构
栈:
只有一个进出口的表结构,先进后出,FILO,
顺序栈:
数据域:
存储元素的内存首地址;
栈的容量;
栈顶位置;
#define TYPE int
typedef struct ArrayList
{
TYPE *ptr;
size_t cal;
size_t top;
}ArrayList;
运算:
创建,销毁,入栈,出栈,栈顶,栈满,栈空
ArrayStack* create_array_stack(size_t cal)
{
//为栈结构分配内存
ArrayStack* stack = malloc(sizeof(ArrayStack));
//分配存储元素的内存
stack->ptr = malloc(sizeof(TYPE)*cal);
//记录栈容量
stack->cal = cal;
//记录栈顶位置,接下来要入栈的位置
stack->top = 0;
return stack;
}
链式栈:
数据域:
栈顶结点;
结点数量;
typedef struct Liststack
{
Node *top; //栈顶结点
size_t cnt; //结点数量
} Liststack;
运算:
创建,销毁,入栈,出栈,栈顶,栈空
//创建
Liststack *create_list_stack(void)
{
Liststack *stack = malloc(sizeof(Liststack));
stack->top = NULL;
stack->cnt = 0;
return stack;
}
栈的应用:
1.函数的调用,栈内存特点
2.生产者和消费者模型(仓库 -> 栈)
3.表达式解析
栈的常考题:
1.某个系列是一个栈的入栈顺序,判断哪个是正确的出栈顺序?
可能边进边出
2.两个顺序栈,如何能够让空间利用用最高
让两个栈相对着入栈
顺序栈和链式栈的区别
队列:FIFO
有两个端口,一个端口进,另一个端口出;先进先出
顺序队列
数据项:
存储元素的内存首地址
容量 cal
对头下标 front //出队
队尾下标 tail //入队
typedef struct ArrayQueue
{
TYPE* ptr;
size_t cal;
size_t front; //队头
size_t tail; //队尾要入队的位置
}ArrayQueue;
运算:
创建,销毁,入队,出队,队空,队满,查队头,查队尾,数量,
//创建
ArrayQueue* create_array_queue(size_t cal)
{
ArrayQueue* queue = malloc(sizeof(ArrayQueue));
//多申请一个内存,解决队空
queue->ptr = malloc(sizeof(TYPE)*(cal+1));
queue->cal = cal+1;
queue->front = 0;
queue->tail = 0;
return queue;
}
注意:
1.由一维数组+队头下标front+队尾下标tail组成;
入队:tail++;
出队:front++;
为了让队列能够反复使用,我们把队列想象为一个环,当front和tail加一后
用队列容量求余再重新赋值。
2.如何判断队空,队满
1.额外多申请一个元素的内存(代价是空了一个位置,计算数量麻烦)
队空:front==tail
队满:(tail+1)%cal==front
计算数量:(tail-front+cal)%cal;
2.在队列结构设计项中增加一项数据项,用于记录队列中元素个数,
判断空或满时,判断该数据项就可以了。
链式队列:
有链表结点组成的队列结构
数据项:
队头指针:
队尾指针:
结点数量:
运算:
创建、销毁、队空、入队、出队、队头、队尾
队列的应用:
1.消息队列
2.数的层序遍历
3.图的广度优先遍历
4.封装线程池、数据池