数据结构知识

本文深入探讨了数据结构中的重要概念,包括布隆过滤器在黑名单问题中的应用,一致性哈希算法的原理与优化,以及并查集、KMP算法、Manacher算法等常见数据结构的详细解析。此外,还涵盖了树状数组、线段树、窗口内最大最小值结构、单调栈、树形动态规划、Morris遍历等高效算法,以及解决大数据和位运算问题的策略。
摘要由CSDN通过智能技术生成


bit数组

//bit数组指的是相比于一般数组每一位上都是1bit,如何实现?
//利用int数组拆分实现
int arr[10];//10x4x8=320bit
int i = 144;//第i位
//如何拿到第i位的状态?
int numindex = i/32;
int moveindex = i%32;
int state = ((arr[numindex]>>moveindex) &1);
//如何将第i位改为1?
arr[numindex] = arr[numindex]|(1<<moveindex);
//如何把第i位改成0?
arr[numindex] = arr[numindex]&(!(1<<moveindex));

1、布隆过滤器

什么时候使用布隆过滤器?类似黑名单问题(只有增,查),允许一定的失误率。
单样本的数据大小只要哈希函数可以接受即可。

//假设样本数为n,失误率为p,则所需要的m个比特位,需要k个哈希函数
//满足:m = -(n*lnp)/(ln2*ln2);k = ln2*m/n	(向上取整)
//因为实际的k向上取整,m尽量选大的(比如算出来是16g,但是面试官可以给你32g,你就可以选择32,使得真实失误率更低)	p真 = (1-e.^(-n*k真/m真)).^k真
/*
			确定好参数后,将每个数据依次输入到k个哈希函数,将每次得到的值都对m取余,将bit数组对应结果上的标志位置1(已经为1的就保持就行)。
			查询时,将数据依次依次输入到k个哈希函数中,在对m取余,查看bit数组上对应位是否为1,若有一位为零,则不属于,若全为零,则属于。

*/

2、一致性哈希算法:

问题背景:假设现在我有三台服务器来存储大量的数据。一般做法,计算每次数据键的哈希值,并将哈希值对3取模,模为几,就放到几号服务器。但这个就有几个问题,首先是键的选取,越多越好,(假设键选取国家名,一般而言,中美最多,就势必使得其中两台机器的负载加重),其次,如果要增加或删除一台服务器,则需要全数据的迁移,故代价太大。
改进思路:假设哈希值从0-2.^32次方,把他考虑为一个顺时针首位相接的圆。对我们每台服务器的键求一个哈希值。按顺时针,将数据存放到大于等于数据对应哈希值的那台服务器上(可以二分查找确定服务器)。问题1:当你的服务器数量非常少时,服务器对应的哈希值肯定不能均分,就势必会导致不平衡。问题2,要增加一台服务器时,可以将该服务器计算出对应的哈希,只需要将它以及它之前的第一个服务器哈希值之间的数据进行迁移,而不影响其他,是对上述问题的改进,但是假设前一次服务器均分了,那么加入或者删除新的服务器,势必会不平衡。
继续改进:引入虚拟节点,为每个服务器分配多个节点(服务器性能相同,就分配一样多,不相同,就好的多分配点)然后将每个节点计算出对应的哈希值,数据存放和前面一样。这样子就解决了平衡问题,并且增加或者删除的时候,也可以继续保持均衡。若增加服务器,只需要增加一组节点,将该节点对应哈希值插入环内,迁移节点到逆时针第一个节点之间的数据。

3、并查集

必须初始化,
题目:假设用户给你一些字符(‘a’,‘b’,‘c’,‘d’,‘e’)可能的操作:查询任意两个是否在一个集合;将任意两个添加到一个集合。
思路:为每一个集合设置一个父节点,如果是一个集合的,就最上层的父节点一定相同。查询时,就查两个字符的最上层父节点是否相同,合并时,就只需要将最上层的合并为同一个即可。一开始将每个字符初始化为一个节点,并且将每个节点的父节点设置为自己。执行合并操作时,首先查询是否在一个集合(即查询各个字符对应节点的最上层父节点),若不是,则将其中一个的最上层节点改为另一个。(最基础的)

struct Node {
   
	char val;
	Node(char c) :val(c) {
   };
	Node() {
   };
};
class UnionFindSet {
   
private:
	unordered_map<char, Node*>	elementmap;//将用户数据转换为节点
	unordered_map<Node*, Node*>	fathermap;//统计一个节点的父节点
	unordered_map<Node*, int>	sizemap;//统计作为父节点的节点是几个节点的父节点,故不为零的个数为集合数,对应大小为该集合下的元素数
public:
	UnionFindSet(string s) {
   
		for (auto c : s) {
   
			Node* node = new Node(c);
			elementmap.emplace(c, node);
			fathermap.emplace(node, node);
			sizemap.emplace(node, 1);
		}
	}
	~UnionFindSet(){
   
		for (char c : str) 
			delete elementmap[c];
	}
	bool IsSameSet(char c1, char c2) {
   
		Node* node1 = elementmap[c1];
		Node* node2 = elementmap[c2];
		if(elementmap.count(c1)>0&&elementmap.count(c2)>0)//首先保真为已经添加过得元素才行
			return FindHead(node1) == FindHead(node2);
		return false;
	}
	
	void Union(char c1, char c2) {
   
		Node* node1 = elementmap[c1];
		Node* node2 = elementmap[c2];
		Node* f1 = FindHead(node1);
		Node* f2 = FindHead(node2);
		if (f1 == f2)	return;
		if (sizemap[f1] < sizemap[f2]) {
   
			fathermap[f1] = f2;
			sizemap[f2] += sizemap[f1];
			sizemap[f1] = 0;
		}
		else {
   
			fathermap[f2] = f1;
			sizemap[f1] += sizemap[f2];
			sizemap[f2] = 0;
		}
	}
	
private:
	Node* FindHead(Node* node) {
   //函数的返回值会和传入参数一致。
		stack<Node*> st;
		while (node != fathermap[node]) {
   
			st.push(node);
			node = fathermap[node];
		}
		while (!st.empty()) {
   
			fathermap[st.top()] = node;
			st.pop();
		}
		return node;
	}
};

4、KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。
假设现在有字符换s1,s2,其中s1是一个长字符串,需要我们检查s1是否包含s2;首先计算s2中每个元素的最大前缀和后缀相等的元素个数。
最大前缀和最大后缀相等元素个数计算:如现在计算索引5,则索引5前的子串为abcda,第一次第一个a和最后的a相等,第二次前面的ab和倒数的da不相等,故个数为1。

索引: 0 1 2 3 4 5 6 7 8 9 10
s1: a b c d a b c d d a e
s2: a b c d a b c d e
next: -1 0 0 0 0 1 2 3 4

如上,next记录的是s2中每个元素的最大相等前后缀,一开始从索引0开始逐个比较,一直到索引8位置不相等。按暴力搜索的话,此时应该开始比较s1的1位置与s2的0位置比较.但KMP算法,此时去比较s1的8位置与s2的next(8)即4位置,若相等,则重复上面的步骤,直到完全匹配或者出现不相等的情况。若不相等,则去比较s1的8位置和s2的naxt(4)即0位置。此时s1的8位置和s2的0位置还是不相等,继续比较s1的8位置和s2的next(0)即-1,此时就知道在s1的0-8上没有能匹配s2的,直接重新开始比较s1的9位置和s2的0位置,重复上述过程。直到s1遍历完或者有完全匹配的情况即可。

next数组求解:next[0]=-1;next[1] = 0;
等于i位置,可以利用next[i-1]的信息

索引: 0 1 2 3 4 5 6 7 8
s2: a b c d a b c d e
next: -1 0 0 0 0 1 2 3 4

首先给出前两个索引的next,假设现在我们要求索引为7的时候,我们只到next[6]=2,则先比较s2[6],s2[next[6]] = s2[2],若相等,则next[7] = next[6]+1;若不相等则继续比较s2[6],s2[next[2]]=s2[0],若相等,则next[7]=next[2]+1;若不相等,则继续该过程,直到next[0] = -1或者相等,则next[7] = next[j]+1


class Solution{
   
public:
int KMP(string s1,string s2){
   //若s1有等于s2的子串,则返回该子串的首元素索引,否则返回-1
	if(s1.empty()||s2.empty()||s2.size()>s1.size())	return -1;
	auto next = getNextArray(s2);
	int x=0;
	int y =0;
	while(x<s1.size()&&y<s2.size()){
   
		if(s1[x]==s2[y]){
   
			x++;
			y++;
		}
		else if(y==0)	x++;
		else	y=next[y];
	}
	return y==s2.size()?x-y:-1;
}
vector<int> getNextArray(string s){
   
	if(s.size()==1)	return {
   -1};
	vector<int> next(s.size(),0);
	s[0]=-1;
	int i = 2;
	int j = 0;//表示每次i-1的next的值
	while(i<s.size()){
   
		
		if(s[i-1]==s[j])
			next[i++] = ++j;	
		else if(j >0)
			j = next[j];
		else next[i++] = 0;
	}
	return next;
}
};

5、Mancher算法

解决问题:字符串str中,最长回文子串的长度求解?如何做到o(n)?

 //伪代码
 manacher(string s){
   
//将s处理成间隔串str(1221 -> #1#2#2#1)
	int R=-1;//表示回文串的最右边界
	int C=-1;//表示最右边界对应的中心点
	vector<int> parr(str.size(),0);
	for
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值