信息学竞赛 = 算法 + 数据结构
什么是数据结构?
数据结构是储存数据的方式
数据结构和算法之间的关系
按照某种方式储存的数据可以更加方便快速地执行某些算法
数据结构的简单分类
线性数据结构
图
树型结构
一个简单的例子
struct Student
{
string name;
int age;
bool gender;
};
这也可以算作一种数据结构,通过这种结构方式,我们可以很容易的通过排序算法来把一堆学生按照年龄或者姓名进行排序
线性数据结构
线性数据结构像是数组这样的,每个数据都有明确的“上一个数据”和“下一个数据”,这样的数据组成形式
栈
设定一个数组,所有的数据都只能从其中一个方向进入和移出,满足先进先出规则
STL模板中的栈
int main()
{
stack<int> test;
test.push(5);
cout << test.top();
test.pop();
}
STL模板中的栈是容器适配器,不是容器
计算机中的栈
栈空间:计算机在编译阶段就完成的空间分配,一般有大小限制
递归调用栈的原理:
C++程序在编译的过程中是先翻译成汇编语言,再编译成可直接执行的二进制程序(exe)
汇编语言和C++一样从上到下执行指令,执行指令时会把整个程序拷贝到内存栈空间中,但是每个函数在栈空间中的地址不一样,所以在调用函数时需要从内存的一个位置跳转到另一个位置,并记录下原来的地址,在执行完调用的函数后跳回原本的函数
这个记录下的原地址就储存在提前分配好大小的栈空间中,所以如果递归函数调用自身的次数过多,就会使数据超出规定的栈空间大小,造成内存泄漏
此时如果你使用的是dev,那么程序就会默不作声地结束掉,就像是自己的程序没有输出结果一样
练习:
不做具体题目了,用数组自行实现一个栈结构,要求功能:
- push()
- pop()
- top()
- 正确处理空栈时执行pop()的错误
考虑一下栈和计算表达式中的括号匹配的关系
队列
队列和栈比较相似,其满足的性质是:数据从一边进入,从另一边移出
相比栈在计算机中的基本应用,队列更多的是在算法层面提供帮助
通过一边进一边出并且不改变数据的顺序,我们可以通过队列实现对某些非线性数据结构的线性处理
比如之后要说的:BFS算法
单独考队列也不太可能,所以如果没有特别需求同样可以使用STL模板中的queue满足基本使用
int main()
{
queue<int> test;
test.push(5);
cout << test.front();
cout << test.back();
test.pop();
}
练习:
如果使用数组实现队列,在数组装满后,移出了一些数据,使得数组的前半部分有空余空间,此时再插入数据的话,数据将从数组的头部进入队列,实现一个头尾相接,这样的数组称为循环队列
尝试使用数组实现循环队列,要求功能:
- push()
- pop()
- front()
- back()
数组
数组本质不是指针,而是包含了一段连续内存空间的基址指针,和数组大小的一种数据结构
直接使用数组名,数组会返回该数组的基址指针
*(a+10) == a[10]
因为数组在内存空间中是连续的,所以可以使用下标进行随机访问
动态数组:要学就连上STL中的基础容器一起学
链表
数组在内存中是连续的,所以可以通过 ∗ ( a + 10 ) *(a+10) ∗(a+10)这样的指针计算直接得到某个位置的数据,而链表在内存中是碎片化储存的,通过指针将其链接起来
这种结构使得链表不能像数组一样通过计算定位某个元素的位置,所以要得到任何元素都必须通过遍历的方式
相对应的,其优点是进行批量的数据删除、添加可以比数组更快、更方便,并且链表数据容量是不固定的,可以随时加入更多的元素,而数组在编译阶段就固定了数据容量
计算机中的链表
我们知道(至少你现在知道了),数据在内存中是碎片化储存的,当我们需要分配一块新的内存时,需要从碎片化的空闲内存中找出一片合适的内存,并从中分割内存
当一块占用内存使用完毕需要释放后,我们需要把他和相邻的空闲内存合并
管理这样分散的内存块很明显使用链表会更加方便,这样在计算机系统中用于管理内存的链表我们称为空闲链表,是计算机内存管理的重要组成
链表的使用
链表同样一般不会出单独的题目进行考察,如果没有特殊需要推荐使用STL中的List容器
int main()
{
list<int> test;
test.push_back(5);
test.push_front(6);
test.pop_back();
test.pop_front();
test.insert(test.begin(), 5);
}
链表的实现可以使用指针和类,进行比较标准的实现,如果不熟悉面向对象编程和指针,也可以使用数组进行实现
在oi竞赛里用数组模拟链表、树这类需要指针的情况非常常见,一般说法是:用数组模拟可以比用指针更快,空间占用更多,但是如果按照更规范的写法,仍然使用指针和类实现
练习
按照你的习惯,编写一个链表的基本实现,功能要求:
- insert()//从中间插入数据
- remove()//从中间删除数据
线性结构相关题
简单题:
P1449 后缀表达式
P1996 约瑟夫问题
图
什么是图?
一种用节点和边链接而成的非线性结构
连通块:节点之间满足两两联通的部分
什么是树?
满足边的数量=节点数量 - 1的联通图
图的储存方式
邻接矩阵:
实现方式:二位数组
邻接表:
每个节点建立一个数组(链表),储存以该节点为起点,通往相邻节点的边
树的储存方式
和图一样
练习
尝试上面的建图方法,并且输出每个节点所连接的边的数量
二叉树
每个节点都最多只有两个儿子的树
二叉树可以用数组的方式储存