第二篇——数据结构

数据结构篇

单链表
#include<iostream>
using namespace std;
const int N=100010;
int idx,head,n[N],ne[N];
int a;
void add_head(int x){
    n[idx]=x;
    ne[idx]=head;
    head=idx++;
}
void add(int k,int x){
    n[idx]=x;
    ne[idx]=ne[k];
    ne[k]=idx++;
}
void remove(int k){
    ne[k]=ne[ne[k]];
}

int main(){

    head=-1;idx=0;
    cin>>a;
    while(a--){
        string op;
        int k,x;
        cin>>op;
        if(op=="D")
        {

            cin>>k;
            if(!k)head=ne[head];
            remove(k-1);
        }
        else if(op=="H")
        {
            cin>>x;
            add_head(x);
        }
        else if(op=="I"){          
            cin>>k>>x;
            add(k-1,x);
        }
    }
    for(int i=head;i!=-1;i=ne[i])
      cout<<n[i]<<" ";
    return 0;

}

双链表
#include<iostream>

using namespace std;

const int N = 1e5 + 10;

int m;
int e[N], l[N], r[N];
int idx;


//! 初始化
void init()
{
    l[1] = 0, r[0] = 1;//* 初始化 第一个点的右边是 1   第二个点的左边是 0
    idx = 2;//! idx 此时已经用掉两个点了
}

//* 在第 K 个点右边插入一个 X 
void add(int k, int x)
{
    e[idx] = x;
    l[idx] = k;
    r[idx] = r[k]; //todo 这边的 k 不加 1 , 输入的时候 k+1 就好
    l[r[k]] = idx;
    r[k] = idx;
    idx++;
}//! 当然在 K 的左边插入一个数 可以再写一个 , 也可以直接调用我们这个函数,在 k 的左边插入一个 数 等价于在 l[k] 的右边插入一个数 add(l[k],x)

//*删除第 k个 点
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

int main(void)
{
    ios::sync_with_stdio(false);
    cin >> m;

    init();

    while(m--)
    {
        string op;
        cin >> op;
        int k, x;
        if(op=="R")
        {
            cin >> x;
            add(l[1], x); //!   0和 1 只是代表 头和尾  所以   最右边插入 只要在  指向 1的 那个点的右边插入就可以了
        }
        else if(op=="L")//! 同理  最左边插入就是 在指向 0的数的左边插入就可以了   也就是可以直接在 0的 有右边插入
        {
            cin >> x;
            add(0, x);
        }
        else if(op=="D")
        {
            cin >> k;
            remove(k + 1);
        }
        else if(op=="IL")
        {
            cin >> k >> x;
            add(l[k + 1], x);
        }
        else
        {
            cin >> k >> x;
            add(k + 1, x);
        }    
    }
    for(int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';

    return 0;
}



模拟栈
#include <bits/stdc++.h>
using namespace std;
int main()
{
    string a;
    int s,n,tt=0,b[100005];
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    cin>>a;
    if(a=="push")
    {cin>>s;
    b[++ tt]=s;}
    if(a=="pop")
    tt--;
    if(a=="empty")
    if(tt==0)
    cout<<"YES"<<endl;
else cout<<"NO"<<endl;
    if(a=="query")
    cout<<b[tt]<<endl;} 
}
队列
模拟队列
#include<iostream>
using namespace std;

const int N = 100010;
int q[N],qh,qt = -1;//qh指向队首,qt指向队尾

int main(){
	ios:: sync_with_stdio(false);
	cin.tie(0);
	
	int m;
	cin >> m;
	
	while(m--){
		string op;
		int x;
		cin >> op;
		
		if(op == "push"){
			cin >> x;
			q[++ qt] = x; //入队 
		}
		else if(op == "pop"){
			qh++;//出队 
		}else if(op == "query"){
			cout << q[qh] << endl; //输出队首元素 
		}else if(op == "empty"){
			if(qh <= qt) cout << "NO" << endl;//判断队空
			else cout <<"YES" << endl; 
		}
		
	}
	return 0;
}
 
使用STL实现
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll N = 1e5+1e4;
queue<ll>a;//定义一个队列
ll n, sr;
string s;
bool flag;
signed main(){
	cin >> n;
	for(ll i = 1;i<=n;i++){
		cin >>s;
		if(s[0] =='p' && s[1] == 'u')
		{
			cin >> sr;
			a.push(sr);//向队尾插入一个数x
				
		}
		if(s[0]=='q'){
			cout << a.front()<<endl;//询问队头元素
			 
		}
		if(s[0]=='p' && s[1] == 'o'){
			a.pop();//从队头弹出一个数 
		}
		if(s[0] == 'e'){
			flag = false;
			flag = a.empty();//判断队列是否为空
			if(flag == true){
				cout <<"YES" <<endl;
				
			} else{
				cout <<"NO" << endl;
			}
		}
	}
	return 0;
} 
单调栈(用数组模拟)
#include<iostream>
using namespace std;
const int N = 100010;
int stk[N] ,tt;
int main(){
	int n;
	cin >> n;
	while(n--){
		int x ;
		cin >> x;
		while(tt && stk[tt] >= x) tt--;
		//如果栈顶元素大于当前元素,则出栈
		if(!tt) cout << "-1" <<" ";
		else  cout << stk[tt]<<" ";
		//栈顶元素就是左侧第一个比他小的元素
		stk[ ++ tt] = x; 
	}
	return 0;
}
单调队列
滑动窗口
//单调队列
#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x)
{
	x= 0; T f =1,ch = getchar();
	while(!isdigit(ch)) {
		if(ch=='-') f=-1;
		ch = getchar();
	}
	while(isdigit(ch)){
		x = x*10+ ch -'0';
		ch = getchar();
	}
	x *= f;
}   
int head,tail,q[1000001],p[1000001],k,n,a[1000001];
inline void maxn(){
	head  =1;tail = 0;
	for(int i =1;i <= n ;i++){
		while(head <= tail && q[tail] <= a[i])
		tail--;//从队尾出队
		q[++tail] = a[i];//入队
		p[tail ] =i ;//记录在原序列的位置
		while(p[head] <= i-k)//长度不超过k
		head ++;
		if(i>=k) cout << q[head]; 
	}
	putchar('\n');	
}
inline void minn(){
	head = 1;tail = 0;
	for(int i = 1;i<=n;i++){
		while(head <= tail && q[tail]>=a[i] )
		tail--;//只要队列里有元素,并尾元素比待处理值大,即表示尾元素
				//已经不可能成为最小值,所以出队 ,直到队尾元素小于待处理值,满足单调
		q[++tail] = a[i];
		p[tail] = i;
		while(p[head] <= i-k)
		head++;
		if(i>=k) printf("%d",q[head]);//满足题意 
	}
	putchar('\n');
}

int main(){
	read(n);read(k);
	for(int i = 1;i <=n ;i++)
		read(a[i]);
		
		minn();
		maxn();
	
	return 0;
}



KMP

1.KMP是什么,做什么用的

KMP是什么,做什么用的
KMP全称为Knuth Morris Pratt算法,三个单词分别是三个作者的名字。KMP是一种高效的字符串匹配算法,用来在主字符串中查找模式字符串的位置(比如在“hello,world”主串中查找“world”模式串的位置)。

2.KMP算法的高效体现在哪

高效性是通过和其他字符串搜索算法对比得到的,在这里拿BF(Brute Force)算法做一下对比。BF算法是一种最朴素的暴力搜索算法。它的思想是在主串的[0, n-m]区间内依次截取长度为m的子串,看子串是否和模式串一样(n是主串的长度,m是子串的长度)。

3.如何求KMP算法的next数组

4.KMP的代码

#include <iostream>

using namespace std;

const int N = 10010, M = 100010;

int n, m;
int ne[N];
char s[M], p[N];

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;

    for (int i = 2, j = 0; i <= n; i ++ )
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j ++ ;
        ne[i] = j;
    }

    for (int i = 1, j = 0; i <= m; i ++ )
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j ++ ;
        if (j == n)
        {
            printf("%d ", i - n);
            j = ne[j];
        }
    }

    return 0;
}


5.KMP的时间复杂度是多少

//1.kmp两个循环类似,分析一个即可
for i := 0; i < n; i++ { //4. 两个循环的时间复杂度是O(2n),所以KMP的时间复杂度是O(n)
        for j > 0 && s[i] != pattern[j] { 
      j = next[j-1] + 1 //3. 这里j会减值,由于next[j-1]肯定小于j,所以j最多减n次
        }

        if s[i] == pattern[j] {
            if j == m-1 { 
                return i - m + 1 
            }
            j++ //2. 在循环中,每次循环j最多+1,所以j最多加n次
        }
    }


Trie
解析

在这一章节中,我们经常看到y总在实现各种数据结构中经常用到idx这个变量,这个变量到底有什么用呢?
为什么链表,Trie树和堆会用到idx来维护这个数据结构,而栈和队列就不用idx来维护,而是用hh和tt来维护呢?

解答
可以看出不管是链表,Trie树还是堆,他们的基本单元都是一个个结点连接构成的,可以成为“链”式结构。这个结点包含两个基本的属性:本身的值和指向下一个结点的指针。按道理,应该按照结构体的方式来实现这些数据结构的,但是做算法题一般用数组模拟,主要是因为比较快。

那就有个问题,原来这两个属性都是以结构体的方式联系在一起的,现在如果用数组模拟,如何才能把这两个属性联系起来呢,如何区分各个结点呢?

这就需要用到idx这个东东啦!

从y总给出的代码可以看出,idx的操作总是idx++,这就保证了不同的idx值对应不同的结点。因此可以利用idx把结构体内两个属性联系在一起了。因此,idx可以理解为结点。

链表:

链表中会使用到这几个数组来模拟:

h, e[N], ne[N], idx;
h表示头结点指针,一开始初始化指向-1,每次插入x的操作idx++。利用idx联系结构体本身的值和next指针,因此e[idx]可以作为结点的值,ne[idx]可以作为next指针。同理可以理解双链表。

//单链表
void add_to_head (int x)
{
    e[idx] =  x;
    ne[idx] = h;
    h = idx ++ ;
}
//双链表
void insert(int a,  int x)
{
    e[idx] = x;

l[idx] = a;
r[idx] = r[a];

l[r[a]] = idx;
r[a] = idx ++;

}
Trie树

Trie树中有个二维数组son[N][26],表示当前结点的儿子,如果没有的话,可以等于++idx。Trie树本质上是一颗多叉树,对于字母而言最多有26个子结点。所以这个数组包含了两条信息。比如:son[1][0]=2表示1结点的一个值为a的子结点为结点2。如果son[1][0] = 0,则意味着没有值为a子结点。这里的son[N][26]相当于链表中的ne[N]。

void insert(char str[])
{
    int p = 0; //从根结点开始遍历
    for (int i = 0; str[i]; i ++ )
    {
        int u =str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx; //没有该子结点就创建一个
        p = son[p][u]; //走到p的子结点
    }

cnt[p] ++;

}

堆中的每次插入都是在堆尾,但是堆中经常有up和down操作。所以结点与结点的关系并不是用一个ne[idx][2]可以很好地维护的。但是好在堆是个完全二叉树。子父节点的关系可以通过下标来联系(左儿子2n,右儿子2n+1)。就数组模拟来说知道数组的下标就知道结点在堆中的位置。所以核心就在于即使有down和up操作也能维护堆数组的下标(k)和结点(idx)的映射关系。比如说:h[k] = x, h数组存的是结点的值,按理来说应该h[idx]来存,但是结点位置总是在变的,因此维护k和idx的映射关系就好啦,比如说用ph数组来表示ph[idx] = k, 那么结点值为h[ph[idx]], 儿子为ph[idx] * 2和ph[idx] * 2 + 1, 这样值和儿子结点不就可以通过idx联系在一起了吗?

if (op == "I")
{
    scanf("%d", &x);
    size ++ ;
    idx ++ ;
    ph[idx] = size, hp[size] = idx;//每次插入都是在堆尾插入
    h[size] = x;//h[k], k是堆数组的下标,h存储的是结点的值,也就是链表中的e[idx]
    up(size);
}

由于idx只有在插入的时候才会更新为idx ++,自然idx也表示第idx插入的元素

Trie字符串统计
#include <iostream>
using namespace std;

const int N = 1e5 + 10;
int son[N][26]; // 其中存放的是:子节点对应的idx。其中son数组的第一维是:父节点对应的idx,第第二维计数是:其直接子节点('a' - '0')的值为二维下标。
int cnt [N];    // 以“abc”字符串为例,最后一个字符---‘c’对应的idx作为cnt数组的下标。数组的值是该idx对应的个数。
int idx;        // 将该字符串分配的一个树结构中,以下标来记录每一个字符的位置。方便之后的插入和查找。
char str[N];

void insert(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i++)
    {
        int u = str[i] - '0';
        if (!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }
    // 此时的p就是str中最后一个字符对应的trie树的位置idx。
    cnt[p]++;
}

int query(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i++)
    {
        int u = str[i] - '0';
        if (!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}

int main()
{
    int n;
    scanf("%d", &n);
    char op[2];
    while (n--) 
    {
        scanf("%s%s", op, str);
        if (op[0] == 'I') insert(str);
        else printf("%d\n", query(str));
    }

    return 0;
}

最大异或对
#include<iostream>
#include<algorithm>
using namespace std;
int const N=100010,M=31*N;

int n;
int a[N];
int son[M][2],idx;
//M代表一个数字串二进制可以到多长

void insert(int x)
{
    int p=0;  //根节点
    for(int i=30;i>=0;i--)
    {
        int u=x>>i&1;   /取X的第i位的二进制数是什么  x>>k&1(前面的模板)
        if(!son[p][u]) son[p][u]=++idx; ///如果插入中发现没有该子节点,开出这条路
        p=son[p][u]; //指针指向下一层
    }
}
int search(int x)
{
    int p=0;int res=0;
    for(int i=30;i>=0;i--)
    {                               ///从最大位开始找
        int u=x>>i&1;
        if(son[p][!u]) 如果当前层有对应的不相同的数
        {   ///p指针就指到不同数的地址

          p=son[p][!u];
          res=res*2+1;
             ///*2相当左移一位  然后如果找到对应位上不同的数res+1 例如    001
        }                                                       ///       010 
        else                                                      --->011                                                                           //刚开始找0的时候是一样的所以+0    到了0和1的时候原来0右移一位,判断当前位是同还是异,同+0,异+1
        {
            p=son[p][u];
            res=res*2+0;
        }
    }
    return res;
}
int main(void)
{
    cin.tie(0);
    cin>>n;
    idx=0;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        insert(a[i]);
    }
    int res=0;
    for(int i=0;i<n;i++)
    {   
        res=max(res,search(a[i]));  ///search(a[i])查找的是a[i]值的最大与或值
    }
    cout<<res<<endl;
}

并查集
合并集合

开始时每个集合都是一个独立的集合,并且都是等于自己本身下标的数
例如:
p[5]=5,p[3]=3;
如果是M操作的话那么就将集合进行合并,合并的操作是:
p[3]=p[5]=5;
所以3的祖宗节点便成为了5
此时以5为祖宗节点的集合为{5,3}
如果要将p[9]=9插入到p[3]当中,应该找到3的祖宗节点,
然后再把p[9]=9插入其中,所以p[9]=find(3);(find()函数用于查找祖宗节点)
也可以是p[find(9)]=find(3),因为9的节点本身就是9
此时以5为祖宗节点的集合为{5,3,9};
如果碰到多个数的集合插入另一个集合当中其原理是相同的
例如:
上述中以5为祖宗节点的是p[5],p[3],p[9];(即p[5]=5,p[3]=5,p[9]=5)
再构造一个以6为祖宗节点的集合为{6,4,7,10}
如果要将以6为祖宗节点的集合插入到以5为祖宗节点的集合,则该操作可以是
p[6]=find(3)(或者find(9),find(5))
此时p[6]=5
当然如果是以6为祖宗节点集合中的4,7,10则可以这样
p[find(4)]=find(3)
或者p[find(7)]=find(3)均可以
此时以6为祖宗节点的集合的祖宗节点都成为了5

#include<iostream>
using namespace std;

const int N=100010;
int p[N];//定义多个集合

int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    /*
    经上述可以发现,每个集合中只有祖宗节点的p[x]值等于他自己,即:
    p[x]=x;
    */
    return p[x];
    //找到了便返回祖宗节点的值
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) p[i]=i;
    while(m--)
    {
        char op[2];
        int a,b;
        scanf("%s%d%d",op,&a,&b);
        if(*op=='M') p[find(a)]=find(b);//集合合并操作
        else
        if(find(a)==find(b))
        //如果祖宗节点一样,就输出yes
        printf("Yes\n");
        else
        printf("No\n");
    }
    return 0;
}


连通块中点的数量

为什么这个题题解这么少啊
是不是大家都太强不屑于做板子题啊
//来自算法基础课

维护连通块size的并查集
一、初始化
void init() {
for (int i=1; i<=n; i++) {
fa[i] = i;
size[i] = 1;
}
}
二、找祖源
int find(int x) {
if(fa[x]==x) return x;
else return fa[x] = find(fa[x]);
}
三、合并连通块
void merge(int a,int b) {
int x = find(a);
int y = find(b);
fa[x] = y;
size[y] += size[x];
}
四、询问是否连通
bool ask(int a,int b) {
return find(a)==find(b);
}
特别注意:
size只有祖节点的有意义
要特别注意所有处理size的地方,都要“归根结底”

完整CODE

#include<bits/stdc++.h>
#define read(x) scanf("%d",&x)
using namespace std;
const int N = 1e5+5;
int n,m,a,b,fa[N], size[N];
string act;

void init() {
    for (int i=1; i<=n; i++) {
        fa[i] = i;
        size[i] = 1;
    }
}

int find(int x) {
    if(fa[x]==x) return x;
    else return fa[x] = find(fa[x]);
}

void merge(int a,int b) {
    int x = find(a);
    int y = find(b);
    fa[x] = y;
    size[y] += size[x];
}

bool ask(int a,int b) {
    return find(a)==find(b);
}

int main() {
    read(n),read(m);
    init();
    while(m--) {
        cin>>act;
        if(act=="C") {
            read(a),read(b);
            if(!ask(a,b)) merge(a,b);
        } else if(act=="Q1") {
            read(a),read(b);
            ask(a,b) ? printf("Yes\n") : printf("No\n");
        } else {
            read(a);
            printf("%d\n",size[find(a)]);
        }
    }   
    return 0;
}
食物链

另一个题解写了拆点并查集的做法,我这里再写一下带权并查集的做法

本题的关系有三层 -> a -> b -> c -> ,但不同的是本题的关系是有向的,也就是说a和b如果是敌对关系,那么b和a就不是敌对关系。

关系传递的本质实际上是向量的运算。
还是设 d[x] 表示 x 与 fa[x] 的关系,0 代表是同类,1 代表是x吃fa[x], 根据关系图自然2就代表x被fa[x]吃。

下面假设a的祖先是x,b的祖先是y,为简化书写,设他们的向量关系为

a⃗ =(a,x)b⃗ =(b,y)a→=(a,x)b→=(b,y)
给出的关系设为rel=ab→rel=ab→
以下的向量关系均用以上符号代替,实际运算时自行带入二元组运算即可

若x=yx=y
想要知道 ab→ab→ ,则需要 a⃗ −b⃗ a→−b→ 然后对3取模并移动到正整数
此时得到的关系0代表ab是同类,1代表a吃b,2代表a被b吃。直接与rel进行比较即可。

如果x和y不等,那么这个给出的关系肯定是合法的
合并的时候同样fa[x] = y,x⃗ =b⃗ +ab→−a⃗ x→=b→+ab→−a→

然后就可以愉快的搞了

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 233;
int fa[maxn], d[maxn];
int ff(int x)
{
    if(fa[x] == x) return x;
    int r = ff(fa[x]);
    d[x] += d[fa[x]];
    return fa[x] = r;
}
int main()
{
    int n,k; cin >> n >> k;
    for(int i = 0; i <= n; i++) fa[i] = i;
    int ans = 0;
    for(int i = 1; i <= k; i++)
    {
        int t, a, b;
        scanf("%d%d%d", &t, &a, &b);
        if(a > n || b > n) {ans ++; continue;}
        else if(t == 2 && a == b) {ans++; continue;}
        else
        {
            int rel;
            if(t == 2) rel = 1;
            else rel = 0;
            int x = ff(a), y = ff(b);
            if(x == y) 
            {
                if((((d[a] - d[b]) % 3) + 3) % 3 != rel)
                ans++;
            }
            else
            {
                fa[x] = y;
                d[x] = d[b] - (d[a] - rel);
            }
        }
    }
    cout<< ans;
}
堆排序
#include<iostream> 
#include<algorithm>

using namespace std;

const int N = 100010;

int h[N], mySize;

int n, m;

void down(int u)
{
    int t = u;
    if (2 * u <= mySize && h[t] > h[2 * u])
        t = 2 * u;
    if (2 * u + 1 <= mySize && h[t] > h[2 * u + 1])
        t = 2 * u + 1;
    if (u != t)
    {
        swap(h[u], h[t]);
        down(t);
    }
}

int main()
{
    cin >> n >> m;
    mySize = n;
    for (int i = 1; i <= n; i++)
        scanf("%d", &h[i]);
    for (int i = n / 2; i; i--)
        down(i);

    while (m--)
    {
        cout << h[1] << " ";
        h[1] = h[mySize--];
        down(1);
    }

    return 0;
}

模拟堆
#include<iostream>
#include<algorithm>
using namespace std;

const int N=1e5+10;
int h[N];   //堆
int ph[N];  //存放第k个插入点的下标
int hp[N];  //存放堆中点的插入次序
int cur_size;   //size 记录的是堆当前的数据多少

//这个交换过程其实有那么些绕 但关键是理解 如果hp[u]=k 则ph[k]=u 的映射关系
//之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
//从而我们需要对应到原先第K个堆中元素
//如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换 
//h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
void heap_swap(int u,int v)
{   
    swap(h[u],h[v]); 
     swap(hp[u],hp[v]);     
     swap(ph[hp[u]],ph[hp[v]]);            

}

void down(int u)
{
    int t=u;
    if(u*2<=cur_size&&h[t]>h[u*2]) t=u*2;
    if(u*2+1<=cur_size&&h[t]>h[u*2+1])  t=u*2+1;
    if(u!=t)
    {
        heap_swap(u,t);
        down(t);
    }
}
void up(int u)
{
    if(u/2>0&&h[u]<h[u/2]) 
    {
        heap_swap(u,u/2);
        up(u>>1);
    }
}

int main()
{
    int n;
    cin>>n;
    int m=0;      //m用来记录插入的数的个数
                //注意m的意义与cur_size是不同的 cur_size是记录堆中当前数据的多少
                //对应上文 m即是hp中应该存的值
    while(n--)
    {
        string op;
        int k,x;
        cin>>op;
        if(op=="I")
        {
            cin>>x;
            m++;
            h[++cur_size]=x;
            ph[m]=cur_size;
            hp[cur_size]=m;
            //down(size);
            up(cur_size);
        }
        else if(op=="PM")    cout<<h[1]<<endl;
        else if(op=="DM")
        {
            heap_swap(1,cur_size);
            cur_size--;
            down(1);
        }
        else if(op=="D")
        {
            cin>>k;
            int u=ph[k];                //这里一定要用u=ph[k]保存第k个插入点的下标
            heap_swap(u,cur_size);          //因为在此处heap_swap操作后ph[k]的值已经发生 
            cur_size--;                    //如果在up,down操作中仍然使用ph[k]作为参数就会发生错误
            up(u);
           down(u);
        }
        else if(op=="C")
        {
            cin>>k>>x;
            h[ph[k]]=x;                 //此处由于未涉及heap_swap操作且下面的up、down操作只会发生一个所以
            down(ph[k]);                //所以可直接传入ph[k]作为参数
            up(ph[k]);
        }

    }
    return 0;
}

哈希表
模拟散列表
拉链法代码
/*
 * Project: 11_哈希表
 * File Created:Sunday, January 17th 2021, 2:11:23 pm
 * Author: Bug-Free
 * Problem:AcWing 840. 模拟散列表 拉链法
 */
#include <cstring>
#include <iostream>

using namespace std;

const int N = 1e5 + 3;  // 取大于1e5的第一个质数,取质数冲突的概率最小 可以百度

//* 开一个槽 h
int h[N], e[N], ne[N], idx;  //邻接表

void insert(int x) {
    // c++中如果是负数 那他取模也是负的 所以 加N 再 %N 就一定是一个正数
    int k = (x % N + N) % N;
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx++;
}

bool find(int x) {
    //用上面同样的 Hash函数 讲x映射到 从 0-1e5 之间的数
    int k = (x % N + N) % N;
    for (int i = h[k]; i != -1; i = ne[i]) {
        if (e[i] == x) {
            return true;
        }
    }
    return false;
}

int n;

int main() {
    cin >> n;

    memset(h, -1, sizeof h);  //将槽先清空 空指针一般用 -1 来表示

    while (n--) {
        string op;
        int x;
        cin >> op >> x;
        if (op == "I") {
            insert(x);
        } else {
            if (find(x)) {
                puts("Yes");
            } else {
                puts("No");
            }
        }
    }
    return 0;
}

开放寻址法代码
/*
 * Project: 11_哈希表
 * File Created:Sunday, January 17th 2021, 4:39:01 pm
 * Author: Bug-Free
 * Problem:AcWing 840. 模拟散列表  开放寻址法
 */
#include <cstring>
#include <iostream>

using namespace std;

//开放寻址法一般开 数据范围的 2~3倍, 这样大概率就没有冲突了
const int N = 2e5 + 3;        //大于数据范围的第一个质数
const int null = 0x3f3f3f3f;  //规定空指针为 null 0x3f3f3f3f

int h[N];

int find(int x) {
    int t = (x % N + N) % N;
    while (h[t] != null && h[t] != x) {
        t++;
        if (t == N) {
            t = 0;
        }
    }
    return t;  //如果这个位置是空的, 则返回的是他应该存储的位置
}

int n;

int main() {
    cin >> n;

    memset(h, 0x3f, sizeof h);  //规定空指针为 0x3f3f3f3f

    while (n--) {
        string op;
        int x;
        cin >> op >> x;
        if (op == "I") {
            h[find(x)] = x;
        } else {
            if (h[find(x)] == null) {
                puts("No");
            } else {
                puts("Yes");
            }
        }
    }
    return 0;
}


字符串哈希
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5+5,P = 131;//131 13331
ULL h[N],p[N];

// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或  13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
ULL query(int l,int r){
    return h[r] - h[l-1]*p[r-l+1];
}
int main(){
    int n,m;
    cin>>n>>m;
    string x;
    cin>>x;

    //字符串从1开始编号,h[1]为前一个字符的哈希值
    p[0] = 1;
    h[0] = 0;
    for(int i=0;i<n;i++){
        p[i+1] = p[i]*P;            
        h[i+1] = h[i]*P +x[i];      //前缀和求整个字符串的哈希值
    }

    while(m--){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(query(l1,r1) == query(l2,r2)) printf("Yes\n");
        else printf("No\n");

    }
    return 0;
}

二叉树的先序、中序、后序遍历、求二叉树的高度、叶子节点的个数
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>


 int count = 0;

typedef struct Node
{
	char data;
	struct Node * LChild;
	struct Node * RChild;
}BiTNode,*BiTree;

//创建一个二叉数链表的二叉树
void CreateBiTree(BiTree * root)
{
	char ch;
	ch = getchar();
	if(ch == '#')
	*root = NULL;	
	else 
	{
		*root = (BiTree)malloc(sizeof(BiTNode));
		(*root) -> data = ch;
		CreateBiTree(&((*root) -> LChild));
		CreateBiTree(&((*root) -> RChild));  
	}
}
 //先序遍历二叉树
void xianxu(BiTree root )
{
	if(root != NULL)
	{
		printf("%c",root -> data);
		xianxu(root -> LChild);
		xianxu (root -> RChild);
		
	}  	
}
 
 //中序遍历二叉树
 void zhongxu(BiTree root)
 {
 	if(root != NULL)
 	{
 		zhongxu(root -> LChild);
 		printf("%c",root -> data);
 		zhongxu(root -> RChild); 
 	}
 } 
  
//后序遍历二叉树
void houxu(BiTree root )
{
	if(root != NULL)
	{
		houxu( root -> LChild);
		houxu( root -> RChild);
		printf("%c",root -> data);
	}	
} 
  
  //用后序遍历输出叶子的片数
  void leaf(BiTree root )
  {
  	if(root != NULL)
	  {
  		leaf(root -> LChild);
		leaf(root -> RChild);
		if(root -> LChild == NULL && root -> RChild == NULL)
		count++;
			
  	  }
  } 
  
 //求二叉树的高度
 int gaodu(BiTree root) {
 	int hr ;
 	int hl ;
 	int max;
 	if(root != NULL)
	 {
 		hl = gaodu(root -> LChild ) ;
 		hr = gaodu(root -> RChild);
 		max = hl > hr ? hl : hr;
 		return (max + 1); 
	 }
	 return 0;
 } 
  
  int main(){
  	BiTree T;
  	CreateBiTree(&T);
  	leaf(T);
  	printf("先序遍历:" ) ;xianxu(T);
  	printf("\n");
  	printf("中序遍历:"); zhongxu(T);
	printf("后序遍历:"); houxu(T);
	printf("\n");
	printf("该二叉树的片数为:%d ",count) ;
	printf("\n");
	printf("该二叉树的高度为: %d",gaodu(T));
	return 0; 
  }
并查集
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值