RE:从零开始的算法之路第三章

0.关于

数据结构 的作用是分析,组织储存数据,它不会直接解决问题但它却是算法不可分割的一部分
它将杂乱无章的数据组织起来便于算法高效访问和处理数据,减少时间和空间复杂度

  • 储存的空间效率
    问:如何储存一个地图?
    简单的用二维矩阵(i,j)表示i点和j点的链接关系,用1表示可连接0表示不可连接
    这虽然简单访问快但一个稀疏矩阵储存太浪费,因为大多数0没有意义
    这就需要更有效率的数据结构
  • 访问的效率
    假如我脸滚键盘输入一大堆数字,要查找到某个数据,就只能一个一个试,需要的时间是O(n)
    但如果先把他们排序,处理起来就会很有效率。比如用折半查找,就可以在O(log2n)时间内找到

数据结构有以下三个要素。

  • 1.数据的逻辑结构
    • 线性结构:数组,栈,队列,链表
    • 非线性结构:集合,图
  • 2.数据的储存结构
    • 顺序储存
    • 链式储存
    • 索引储存
    • 散列储存
  • 3.数据的运算
    • 初始化
    • 判空
    • 统计
    • 查找
    • 遍历
    • 插入
    • 删除
    • 更新

1.并查集

介绍

并查集是一种精巧且实用的数据结构。它主要用于处理一些不相交集合的合并问题
举个例子。一个城市有n个人,他们分成不同的帮派。给出一些人之间的关系。如1号2号是朋友。1号3号也是朋友,那么他们就属于一个帮派。在分析完所有的朋友关系之后,问有多少个帮派每人属于哪个帮派的。这种题如果用并查集来实现不仅代码简单,而且复杂度只有O(log2n)

例题

今天是伊格纳修斯的生日。他邀请了很多朋友。现在是晚饭时间。伊格内修斯想知道他至少需要多少张桌子。您必须注意,并不是所有的朋友都彼此认识,并且所有的朋友都不想和陌生人呆在一起。

解决此问题的一条重要规则是,如果我告诉您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
    思路
  • 在写了在写了(咕咕咕)
    代码
在这里插入代码片

Treap树

Splay树

3.线段树

4.树状数组

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值