**第一周 acm刷题总结** 并查集,深度优先搜索

               **第一周   acm刷题总结**

(1) 多组数据的输入:

相较于scanf的多种写法,所有的多组输入我都倾向于用while(cin >>) 来解决, 但是这个周在刷题的过程中,我跟一个同学学习到了多组数据的写法,不是多个数据,是多组数据,很喜欢,学到了!
比如一组数据包含10个数或者10组数,输入若干组这种数据,就用while(cin >>),可能是输入一个数,也可能是输入一组数,如果是输入一组数就在右面多接几个 >> ,然后对这一组数进行处理,然后用内层循环循环9次,每一次再输入这种格式的一组数据,并且处理,所有这些过程都在while这个外层循环当中,这样就可以输入若干个10组数据并且处理了。
关于多组数据,我在做题的时候还是犯了常见的错误,还是感觉一定要重视起来,就是多组数据从第二组开始,曾经开的数组就应该清零了,很简单的细节但是很容易忽略,犯过不止一次这种错误。

(2) 并查集:

2021年3月12号我初步了解了一下并查集,非常喜欢,最喜欢的就是刚开始的时候把自己设为自己队伍中的队长,就是for (i = 0; i < n; i++) a[i] = i; 这一句是神来之笔,为后续所有操作提供了极大的方便,其中道理很巧妙,当我在把一些队伍合成一个大队伍的时候,再通过看什么确定谁是队长?因为只有知道谁是队长,才能继续进行合并操作, 在判断谁是队长这个问题上每个队员是平等的也是独立的,别的东西都是不确定而变化的,只有队员自己是不变的,所以只有与该队员自身存在固定联系的数据才可以作为判断他是否是队长的标志。而在这里我们没有别的这种数据,只有队员自身,于是就直接在一开始让队员的队长等于他自身,后续谁的队长仍然是自身,谁就是队长。这一操作的巧妙之处不仅因为他是必要的,更是因为他是最好的,我们也可以设置标记数组,但是设想一下,在合并的过程中,没有成为新任队长的那个队员是还需要解除标记吧?但是一开始就把队员自己设置成队长,之后无论谁当新队长,无须解标,他自动变化了,说得更透彻些,其实在一开始每一个队员在把自己设置成队长的时候,就已经用自己把自己标记了,和并的过程只是下岗队长解标的过程。
通用查找代码:
typename find(typename x)
{
if (set[x] == x) return x;
else return find(set[x]);
}

合成之前先查找队长甚至还需要比较树的高度,或者一同封装到合并函数中,我等新手暂时不要考虑那么多。
合并通用代码:
void merge(typename a, typename b)
{
set[a] = b;
}

(3) 关于结构体中对数据成员的操作细节:

一定要通过对象而不是在类内直接写出变量之间的关系,因为所有的变量还尚未初始化。这在重载运算符时需要注意。

(4) 梳理这个周里修正了的两个错误认知:

一个是关于实现一个数据结构的时候,曾经以为所有的malloc都要判断是否分配失败,很多人不判断我不知道为什么, 其实malloc是在每次生成新节点或者初始化一个结构类型的时候使用一次,如果不成功的话也是返回NULL,函数本来返回值类型就是对应的指针类型,这个后续还有判空操作,不影响的。
第二个是写一个结构的clear函数的时候想要最后把释放完了的指针赋空除了在g++编译器下可以在传指针参数的时候加引用没有别的办法能改变传入的指针的值了,指针还要加引用不仅在gcc下无法编译而且看着也别扭,这几天忽然悟了,既然在函数里没办法改变指针的值,那就把他返回出来赋给函数外面的指针就完了。学会了这种用法以后用到别处,不需要纠结这个细节了。

(5) 深度优先搜索:
回头看这个全排列代码,感觉并不需要解释太多,之前太罗嗦了。其实就是n歌位置,1到n一共n个数字,给他们分配位置,占用了的位置就标记了。函数里就一个for循环,那个循环就是为当前数字找空位
并且找到之后占住他然后给下一个数找。不过遍历方向那个从左到右的思想还是有用的。

这个周学习了一点深搜,按照我的理解是在每一步中按照固定的访问方向变化规则去访问下一步的内容,优先前进,其次改变方向,很需要注意的一点,对于这种访问方式的设计要做到不重不漏,也就是只要每次按照这个访问方式去访问,最后必然可以得到一个符合要求的结果。那么剩下的就是代码实现了,在深搜函数中,函数递归调用的过程就是前进的过程,函数内的循环过程就是改变访问方向的过程,因为改变方向的规则固定,所以可以用单向的循环来模拟,寻到一个可以前进的方向,标记掉当前位置然后前进,还没前进之前所有的标记都是无效的,因为他是随着循环更新的,只有当前进后,也就是调用下一层函数之后,标记情况才停止了更新,等到递归出口,就启动输出代码输出一个符合要求的结果,然后回溯的过程,末端函数返回,次末端函数的第一件事就是清除标记,继续循环,也就是继续遍历方向,以此类推。
举一个深搜实现1到n的全排列的例子,层数路线是从1走到n层,再加上一开始的起点,得到的是树的高度,方向的改变方式是当前第a个节点可以选择剩下n-a个方向,我们把改变方式固定,就设为尽小的数选,那么从左往右这个节点的子节点就是升序排列了,首次压栈过程的输出点也就在整棵树的左下角了,弹栈之后走循环的过程也就是改变方向的过程就是向右遍历当前节点的其他孩子的过程。等到右面没有其他孩子了,也就是所有方向都走不通了,在函数中就是表现为循环结束了,此时会再次发生弹栈,这就是回溯的过程,再去上一层函数继续走循环,也就是去他的父节点向右搜索孩子,以此类推实现深度优先搜索。如下图当n为4时,绿色是第一次压栈过程,红色是遍历方向和回溯的过程!
在这里插入图片描述

全排列代码:

#include <iostream>
using namespace std;
const int max_n = 102;
int a[max_n], v[max_n], n;
void dfs(int dp)
{
        if (dp > n)
        {
                for (int i = 1; i <= n; i++)
                {
                        cout << a[i];
                        i < n && cout << ',' << ' ';
                }
                cout << endl;
                return;
        }
        for (int i = 1; i <= n; i++)
                if (!v[i])
                {
                        a[dp] = i;
                        v[i] = 1;
                         dfs(dp + 1);
                         v[i] = 0;
                }
}

int main()
{
        cin >> n;
        dfs(1);

        return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值