0.关于
数据结构 的作用是分析,组织储存数据,它不会直接解决问题但它却是算法不可分割的一部分
它将杂乱无章的数据组织起来便于算法高效访问和处理数据,减少时间和空间复杂度
- 储存的空间效率
问:如何储存一个地图?
简单的用二维矩阵(i,j)表示i点和j点的链接关系,用1表示可连接0表示不可连接
这虽然简单访问快但一个稀疏矩阵储存太浪费,因为大多数0没有意义
这就需要更有效率的数据结构 - 访问的效率
假如我脸滚键盘输入一大堆数字,要查找到某个数据,就只能一个一个试,需要的时间是O(n)
但如果先把他们排序,处理起来就会很有效率。比如用折半查找,就可以在O(log2n)时间内找到
数据结构有以下三个要素。
- 1.数据的逻辑结构
- 线性结构:数组,栈,队列,链表
- 非线性结构:集合,图
- 2.数据的储存结构
- 顺序储存
- 链式储存
- 索引储存
- 散列储存
- 3.数据的运算
- 初始化
- 判空
- 统计
- 查找
- 遍历
- 插入
- 删除
- 更新
1.并查集
介绍
并查集是一种精巧且实用的数据结构。它主要用于处理一些不相交集合的合并问题。
举个例子。一个城市有n个人,他们分成不同的帮派。给出一些人之间的关系。如1号2号是朋友。1号3号也是朋友,那么他们就属于一个帮派。在分析完所有的朋友关系之后,问有多少个帮派每人属于哪个帮派的。这种题如果用并查集来实现不仅代码简单,而且复杂度只有O(log2n)
例题
- How Many Tables HDU - 1213
今天是伊格纳修斯的生日。他邀请了很多朋友。现在是晚饭时间。伊格内修斯想知道他至少需要多少张桌子。您必须注意,并不是所有的朋友都彼此认识,并且所有的朋友都不想和陌生人呆在一起。
解决此问题的一条重要规则是,如果我告诉您A认识B,而B认识C,则意味着A,B,C彼此认识,因此他们可以呆在一张桌子上。
例如:如果我告诉你A知道B,B知道C,D知道E,那么A,B,C可以留在一个表中,而D,E则必须留在另一个表中。因此,伊格纳修斯至少需要2张桌子。
输入
输入以整数T(1 <= T <= 25)开头,该整数表示测试用例的数量。然后是T测试用例。每个测试用例均以两个整数N和M(1 <= N,M <= 1000)开头。 N表示朋友的数量,朋友从1到N标记。然后跟随M行。每行包含两个整数A和B(A!= B),这意味着朋友A和朋友B彼此认识。两种情况之间将有一个空白行。
输出
对于每个测试用例,只需输出伊格纳修斯至少需要多少个桌子。请勿打印任何空白。
样本输入
2
5 3
1 2
2 3
4 5
5 1
2 5
样本输出
2
4
思路
- 如果用从0到N以加的方式算组数的话,每输入2个数就要对比所有数,会造成极大的时间复杂度
- 所以将一开始组数设为N,出现组就就减
- 一开始思路是如果2个不一样就把他们置0(用过就减1,有<=1就换成小的),但这样假如一堆0和-1后有2个好友,并不能使一群0变成-1,只有那一个而已
- 用数组a[n]位存n,表示一开始所以人的根都是自己
- 比如一开始1和2好友,就将a[1的根]=2的根,就是a[1]=2,1的根为2
- 然后是1和3好友,a[1的根]=3的根,就是a[2]=3,2的根是3
- 同理4和5,4和6好友,4的根是5,5的根是6
- 然后1和4好友,最终只会是3的根是6,这样查1~6都是一个集合的
- 还有2个优化问题(不优化也能过)当2个树都大时,2个叶子查找是就很废时间了,要使沿途节点都指向根节点,这要查找就会简单,所以当查找根时顺便将节点指向根,这叫路径压缩
- 还有一个是合并树时将高度低的合并到高度高的上,能减小树的高度从而节省时间,这叫合并优化
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
const int maxn = 1e5 + 10;
#define re(x) for(int i=0;i<x;++i)
#define rey(x) for(int j=0;j<x;++j)
#define Cheak(x,y) (x<W&&x>=0&&y>=0&&y<H)
typedef long long LL;
using namespace std;
int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };
int T, n, a[maxn], m, * p, flag = 1;
int root(int n) {//找出根节点,如果a[n]=n是就是根,否则以a[n]的值继续找根
return n == a[n] ? n :a[n]=root(a[n]);
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin >> T;
while (T--)
{
cin >> m >> n;
re(m)a[i+1] = i + 1;//初始化是a[n]=n
int q, w,e=0;
re(n) {
cin >> q >> w;
if (root(q)!=root(w)) {//当2个位置根不相同时
a[root(q)] = root(w);//一个的根指向另一个根
--m;//根不相同说明可以合并
}
}
cout<<m<<endl;
}
}
2.二叉树
标准二叉树
介绍
二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树
树是非线性结构,能很好的体现出层次性比如目录
- 特殊类型
1、满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树
2、完全二叉树:深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树 - 储存结构
1.结构体:里面有值和2个结构体指针
2.数组:一般用于完全二叉树,数组来表示父节点和子节点的关系非常方便 - 遍历
1.先序遍历:先根节点在左子树右子树
2.中序遍历:先左子树在根节点右子树
2.后序遍历:先左子树在右子树根节点
例题
Binary Tree Traversals HDU - 1710
二叉树是一组有限的顶点,这些顶点为空或由根r和两个不相交的二叉树组成,分别称为左子树和右子树。二进制树的顶点可以通过三种最重要的方式进行系统遍历或排序。它们是预购,订购和后购。令T为具有根r和子树T1,T2的二叉树。
在T的顶点的先遍历中,我们访问根r,然后按顺序访问T1的顶点,然后按顺序访问T2的顶点。
在T的顶点的中序遍历中,我们按顺序访问T1的顶点,然后依次访问根r和按顺序访问T2的顶点。
在对T的顶点进行后遍历时,我们先访问T1的顶点,然后再访问T2的顶点,最后访问r。
现在,您将获得某个二叉树的先序序列和中序序列。尝试找出其后序序列。
输入
输入包含几个测试用例。每个测试用例的第一行包含一个整数n(1 <= n <= 1000),即二叉树的顶点数。后接两行,分别指示先排序序列和中序序列。您可以假设它们始终与排他的二叉树相对应。
输出
对于每个测试用例,请打印一行以指定相应的后置顺序。
样本输入
9
1 2 4 7 3 5 8 9 6
4 7 2 1 8 5 9 3 6
样本输出
7 4 2 8 9 5 6 3 1
思路
- 首先先序的第一位是根,在中序找到根,左边为左子树右边为右子树
- 在左子树中,中序第二个为根,在中序的左子树中找到根,左边为新的左子树右边为新的右子树
- 递归这个过程,直到左右子树都是NULL时
代码
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
const int maxn = 1e5 + 10;
#define re(x) for(int i=0;i<x;++i)
#define rey(x) for(int j=0;j<x;++j)
#define Cheak(x,y) (x<W&&x>=0&&y>=0&&y<H)
typedef long long LL;
using namespace std;
int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };
const int N = 1005;
vector<int> V; //存放父节点
int pre[N], in[N], a[N]; //先序数组和后序数组
void make(int preleft, int preright, int inleft, int inright)
{
int parent, leftsize, rightsize;
for (parent = inleft; parent <= inright; parent++)
if (in[parent] == pre[preleft])
break; //找到父节点在中序遍历的位置parent
leftsize = parent - inleft;
rightsize = inright - parent; //获得左树和右树的大小
if (leftsize > 0)
make(preleft + 1, preleft + leftsize, inleft, parent - 1); //如果有左子树,递归重建左子树
if (rightsize > 0)
make(preleft + leftsize + 1, preright, parent + 1, inright); //如果有右子树,递归重建右子树
//父节点入栈,如果在连个if中间则为中序遍历,之前则为前序遍历
V.push_back(in[parent]);
}
int main()
{
int i, n, num;
// freopen("in.txt", "r", stdin);
while (scanf("%d", &n) != EOF)
{
// 清空向量,注意empty一定要声明为局部变量,否则交换两次后empty便不为空
V.clear();
for (i = 0; i < n; i++)
scanf("%d", &pre[i]);
for (i = 0; i < n; i++)
scanf("%d", &in[i]); //输入
make(0, n - 1, 0, n - 1); //建树
for (int i = 0; i < V.size(); i++)
{
printf("%d%c", V[i], i != V.size() - 1 ? ' ' : '\n');
}
}
return 0;
}
二叉搜索树
介绍
BST(Binary Serach Tree)二叉搜索树访问非常高效.因为每个元素有唯一的键值,键值能比大小,任意一个键值,都比左子树所以键值大,比右子树所以的键值小,能通过键值快速查找
例题
The order of a Tree HDU - 3999
- 众所周知,二叉搜索树的形状与我们插入的键的顺序密切相关。确切地说:
1.将密钥k插入一棵空树,然后将树变成具有
仅一个节点;
2.将密钥k插入非空树,如果k小于根,则插入
将其插入左侧子树;否则将k插入右侧子树。
我们将键的顺序称为“一棵树的顺序”,您的任务是,给定一棵树的顺序,找到字典顺序最小的一棵树的顺序,该顺序生成同一棵树。如果两棵树相同,则并且只有它们具有相同的形状。
输入
输入文件中有多个测试用例。每个测试用例的第一行是整数n(n <= 100,000),表示节点数。第二行具有n个整数,k1至kn,表示树的顺序。是1到n的序列。
输出
一条带有n个整数的行,这是一棵树的顺序,该树生成具有最少词典顺序的同一棵树。 - 样本输入
4
1 3 4 2 - 样本输出
1 3 2 4
思路 - 在写了在写了(咕咕咕)
代码
在这里插入代码片