习题课1-2
数字盒子
- 维护一个集合S,初始为空
- 支持两种操作insert delete
- 要求输出操作是否成功,操作次数5X10的5次方,数据的范围10的18次方(百分之60是10的5次方)
- 蛮力法,开一个长度为10的5次方的数组,集合中元素不重复,使用一个bool数组记录每一个数是否出现过
- 标答,通过哈希函数把原数变小,对一个质数取余,取一个不规则的数(10的6次方加3)(质数),但是并不是真的变小,而是作为原数的代号(key),用代号来存储元素,
- 出现哈希冲突,把bool数组升级,使用链表、vector或list(变长数组)来存储
代码实现
-
h = Hash(x),求出x的哈希值 x%100003
-
vector<ll>::iterator ptr = table[h].end()
-
在vector(table[h])的迭代器中遍历,找到x,赋值给ptr,然后break
-
插入时,如果ptr == table[h].end(),说明没有找到这个元素,
-
table[h].push_back(x);
-
删除时,如果ptr !=table[h].end()时,说明找到这个元素,然后进行删除,
-
代码逻辑删除元素后没有进行元素的移动,通过移动下标实现快速删除
-
*ptr = table[h].back();//让ptr位置指向back()位置的元素 table[h].pop_back();//删除back()位置的原先元素
-
table[h].back()是末尾元素,把末尾元素的值覆盖掉了要删除的值,同时删除掉末尾元素的值,实现快速删除,不用再进行数组元素的移动,保证了元素内存的连续,但是改变了元素的顺序
-
非官方解法,用c++::set,插入用insert,时间复杂度是O(QlogQ),删除用erase,查找用find,
重编码
-
蛮力算法,不断合并操作建立哈夫曼树,
-
哈夫曼树的建立,初始节点中,选出最小的两个进行合并,放入原先的数组,再选出最小的两个进行合并,然后不断循环
-
叶子节点对答案的贡献等于节点权值乘上该节点到根节点的路径长度(深度)
-
上述贡献的总和等于直接把新权值累加起来得到的和(新形成的节点),这样做的好处是不用维护整个哈夫曼树的结构,第一次合并后,就可以丢掉最初的两个叶子节点
-
怎么去保证足够大的时间复杂度,如果使用数组是O(n2),使用更高效的数据结构,使用堆-优先队列优化复杂度,像堆本身就支持取出最小的元素,插入一个新元素,这样很轻易就把时间复杂度优化到了O(nlogn)
代码分析
- 首先将所有的待合并的元素加入pq(优先队列)中
- 循环条件,当优先队列的size大于1时,才能进行合并操作
- 开一个临时变量newEle,连续取两次最小的元素,累加得到newEle,
- 把本次合并对答案的贡献加入答案,也就是把newEle加入到sum,然后把sum加入到队列中
- 最后的sum就是答案
改编
-
荷马史诗,更加普遍的情况
-
不使用二进制编码,k进制编码(八进制、十进制),此时怎么求解?
成绩排序
-
每个学生有两个成绩(算法、数据结构),总分排序,总分相同按算法训练营成绩排序,不存在两门成绩完全相同的情况
-
求所有逆序对数目,b学生,a学生排序,但是a学生的总分只有180,b学生的总分是190,这就是一个逆序对
-
3、1、2、5、4
-
31 32 54是逆序对
-
介绍一个结论,在起泡排序中,每一次交换都会恰好消去一个逆序对
-
因为起泡排序中交换的一定是逆序的数对,也就是左边的元素一定比右边元素大(左大右小)(xy)(相邻的两个元素),
-
所有的逆序对可以分成三类
- 不涉及x,y的任意逆序对,交换xy,对这些逆序对没有影响
- 外面的元素和x,y产生了逆序对,有没有影响,外部元素和x,y的相对位置关系没有发送变化
- x,y这个逆序对,逆序对减一
代码分析
-
id是学号,sum是总分
-
起泡排序进行n轮,起泡排序从哪里开始呢?
-
起泡排序从末尾根部往左进行
-
什么条件交换?
-
if(sum[j]>sum[j-1] || sum[j] == sum[j-1] && A[j]>A[j-1] )
-
题目要求时降序,所以此处是大于号
-
总分相同和总分相同算法成绩高者,则进行交换
-
交换的次数就是逆序对数目
-
||的优先级低于&&
-
优化代码可以只记录id的循环,不用对sum进行排序
-
归并排序统计逆序对,树状数组统计逆序对
-
快速排序和归并排序这种O(nlogn),使用归并排序统计逆序对,或者使用树状数组优化暴力解法