按照方法(更新中)

------------------------------------------前缀和(一维O(n),二维O(m*n)):

//一维:
     vector<int> sums;      
     sums[0] = nums[0];      
     for (int i = 1; i < nums.size(); ++i) {
         sums[i] = sums[i-1] + nums[i];
     }
     //[i, j]区间和
     if (i == 0) ans = dp[j] - dp[0];
     else ans = dp[j] - dp[i - 1];
//二维 
/*
    i行j列左上角n行m列矩形的和表示为:
	sum[i][j] - sum[i][j - m] - sum [i - n][j] + sum[i - n][j - m];
*/ 
	sum[0][0] = a[0][0];
	for (int i = 1; i < 10; ++i) {
		sum[0][i] = sum[0][i - 1] + a[0][i];
		sum[i][0] = sum[i - 1][0] + a[i][0];
	}
	for (int i = 1; i < 10; ++i) {
		for (int j = 1; j < 10; ++j) {
			sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j]; 
		}
	}

题型:
给出一个数组,不修改元素,求某一段区间和
leetcode题目:
------------------------------------------二分(左开右开):O(logn)

本质:有唯一的分界线,在分界线两边性质不一样。
int left = 范围 - 1, right = 范围 + 1;
while (left + 1 < right) { //是left + 1!!!
	int mid = left + (right - left) / 2;//mid能取到的范围是 整个范围,并不会取到left和right的初始值!!!
	if (ifright(mid)) {
		right = mid; //是mid!!!
	}else {
		left = mid;
	}
}
return left/right 看具体题目;
注意:
1. left不一定一开始就是红色,right同理。所以, right初值等于可能的取值+1,left初值等于可能的取值-1,首先要确定好left跟right的初始值
2. 二分结束后考虑一下需要返回的left/right如果为初始值的话怎么处理
4. 对小数采取二分的时候,要考虑精度,一般大于两个精度就可以了leetcode786

题型:
1.满足某条件的最小~或者最大 ~
leetcode题目:

------------------------------------------并查集:初始化O(n),查找合并近似O(1)

//初始化
int father[N];
void init() {
	for (int i = 1; i <= n; ++i) {   
		father[i] = i;
	}
}

	//查找
	int findfather(int i) {
		if (father[i] == i) return i;
		return findfather(father[i]); 
	}
//同上,查找的时候路径压缩,查找的复杂度几乎为O(1),取代上面!!
int findfather(int m) {
	if (father[m] == m) return m;
	return father[m] = findfather(father[m]); //不是findfather(m)!!!
}
 
//并查集的合并,过程是先判断是否在一个集合,若不在一个集合的话,把一个集合根节点的父亲指向另一个集合的根节点
void unite(int u, int v) {
	int fau = findfather(u); 
	int fav = findfather(v);
	if(fau != fav) father[fau] = fav;
}
//集合的块数求法:最后枚举father[i]==i的个数
//判断是否同根:father[i]==i 的个数是否为1

题型
1.判断图是否存在环:看是否存在边的两个顶点的findfather()相等。
2.father[]保存的是集合中最大的值(做法是unin(小值,大值))。例题:蓝桥杯的修改数组。
3.0/1矩阵中1的块有多少个:二维坐标转换到一维然后合并集合
4.最小生成树,任意加一条边,就会成为一个图。
leetcode题目:

------------------------------------------单调栈(O(n))

//栈内元素满足单调性的栈结构,分为单调递增(x>=top)与单调递减(x<=top),作用是求每一个元素在序列中左边第一个<=/右边第一个<的元素(递增栈,反向搜索:右边第一个<=/左边第一个<的元素),左边第一个>=它/右边第一个>的元素(递减栈,反向搜索就左右相反了)。
//单调递增栈:假设当前元素为x,若栈顶元素≤x,则将 x入栈,否则不断弹出栈顶元素,直至栈顶元素≤x,再将x入栈。若top被弹出,则x为其右边第一个<top的数。当一x即将被放入栈时,其top是它在左边第一个<=x的数。单调递减栈同理。
以递减栈为例:
//单调递减栈 stk[top] 为下标 nums[stk[top]]为栈顶元素的值
vector<int> stk(n); //存储下标,n个元素!!!
vector<int> ans(n, -1); //存储下一个更大元素的值,不存在则为-1!!!
int top = -1; //栈顶,指向存在的元素
for (int i = 0; i < n; ++i) {
	while (top != -1 && nums[stk[top]] < nums[i]) { 
		//右边第一个>的元素,想得到左边>的话从右向左扫描!!!
		ans[stk[top]] = nums[i];//ans中存下标还是元素的值看题目!!
		top--; //跟上面这句顺序不能相反!!!
	}
	//左边第一个>=的元素,想得到右边>=的话从右向左扫描!!!
	if (top != -1) ans[i] = nums[stk[top]];    //加个判断!!!
	stk[++top] = i; 
}
return ans;
//不建议用stack,因为stack没有clear()函数,题目中需要使用多次单调栈时,在下一次使用之前需要先清空(top = -1即可)!!!

leetcode题目:
1.序列中下一个更大/更小的元素。

------------------------------------------单调队列(O(n))

作用是得到一个滑动窗口中的最大值/最小值(也可以用multiset来代替)
//leetcode239.滑动窗口最大值:给定一个nums数组,返回长度为k的滑动窗口中的最大值数组。
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        
        int n = nums.size();
        deque<int> dq; //只存下标!!!
        vector<int> ans;
        for (int i = 0; i < k; ++i) {
            while (!dq.empty() && nums[dq.back()] < nums[i]) dq.pop_back();
            dq.push_back(i);
        }
        ans.push_back(nums[dq.front()]);
        for (int i = 1; i <= n - k; ++i) {
            //加入元素
            while (!dq.empty() && nums[dq.back()] < nums[i + k - 1]) dq.pop_back();
            dq.push_back(i + k - 1);
            //得到最大值
            while (dq.front() < i) dq.pop_front();
            ans.push_back(nums[dq.front()]);
        }
        return ans;
    }
};

leetcode题目:
leetcode239.滑动窗口最大值

------------------------------------------字符串hash:比较两个时为O(1)

#include <bits/stdc++.h>
using namespace std;

using ull = unsigned long long;
ull get(vector<int>& v, vector<int>& pow,int l, int r) { //[1 - 1, r - 1]的hash值 
	return v[r] - (v[l - 1] * pow[r - l + 1]); 
}
int main() {
 
	string s;
	int n = s.size(), base = 131; //base为大于100的质数 
	vector<ull> pow(n + 1), hash(n + 1); //hash对一个很大的质数取余(赋值给unsigned出现越界时 自动取余,这里用ull)
	pow[0] = 1;
	for (int i = 0; i < n; ++i) {
		hash[i + 1] = hash[i] * base + (s[i] - 'a' + 1); //字符映射到1->26(为什么?) 数字的话就不用映射 
		pow[i + 1] = pow[i] * base;
	} 
	//function<ull(int, int)> get = [&](int l, int r) {return hash[r] - (hash[l - 1] * (r - l + 1));};
	//auto get = [&](int l, int r) {
	//	return hash[r] - (hash[l - 1] * (r - l + 1));
	//};
	return 0;
}
//主要用于优化两个字符串/vector比较问题:O(n)->O(1) 都映射为hash比较的话为O(n) 
//字符串匹配用字符串hash 也可以达到O(n)等于kmp算法 

易错点:pow[r - l + 1] 不要写成了 (r - l + 1)
leetcode题目:

leetcode 28.实现strStr() (中等)
leetcode718. 最长重复子数组(中等)

------------------------------------------快速幂
leetcode题目:
leetcode70.爬楼梯(简单)

------------------------------------------分块思想:O(根号n)
离线查询: 在查询过程中元素不生改变。
在线查询: 在查询过程中元素可能发生修改或者删除

//在线查询,找出n个元素中的第k大值
给定n个非负整数的元素(n <= 10^5 元素值 <= 10^5),在线查询,求n个元素中第k大值。
思路1:暴力做的话,添加删除一个元素就O(n),复杂度高!!!
正确思路:分块。块数是:sqrtn+1     块内元素的个数:sqrn
const int maxn = 100001;
const int sqrn = sqrt(maxn);   //maxn + 1表示100001=100000+1 ,因为包括元素0
//block[0]存储0...sqrn-1 的总个数
int block[sqrn + 1]; //block[x]表示x这个块内元素的个数!!!
int table[maxn]; //table[x]表示当前x存在的个数
memset(block, 0, sizeof(block));  //!!!
memset(table, 0, sizeof(table));
//插入元素x 	O(1)
table[x]++;
block[x / sqrn]++;
//删除元素  O(1)
table[x]--;
block[x / sqrn]--;
//查找元素 O(根号n)
int sum = 0;
int index = 0; //先是index索引下标!!!
while (sum + block[index] < k) {  //找到最大元素所在的块号
	sum += block[index++];
}
//然后是index索引下标!!!
index = index * sqrn; //第index块第一个元素是index * sqrn  
while (sum + table[index] < k) {
	sum += table[index++];
}
return index;

题型:
1.给定n个元素在线查询,求n个元素中第k大值。
leetcode题目:

------------------------------------------树状数组:都是log(n)

给出n个整数(小于等于N:10^5)查前k(<=N)个元素的和,在查的过程中随时会给某个值增加或者减少k。
思路1:用前缀和,但是改变a[i]时,sum[i] sum[i+1]....sum[n-1]都需要更新,改变一个值时,更新复杂度达到O(n),复杂度过高
正确思路:针对单点更新,树状数组能达到O(logn)的复杂度,使用树状数组来做。
预备知识:
1.注意:a的下标是从1开始的!!!
2. -x:x二进制所有位取反,然后末位+1...1000会变成 ~1000
   因此lowbit(x) = x & (-x)就是取x的二进制最右边的1和它右边所有0,也可理解为能整除x的最大2次幂,比如: 
   lowbit(6)=2 lowbit(8)=8
3.树状数组与sum数组
相同点:都是数组的区间和
不同点:sum[i]是从a[0]到a[i]的和,树状数组c[i]是i之前lowbit(i)个整数的和(包括i)
				
case1:描述区间和。单点更新(第x个元素增加或者减少某个值),区间查询(查询区间和)
//a[]和c[]下标都是从1开始存储的
#define lowbit(x)  ((x) & (-x)) //注意格式!!!
const int N = 100000;
int	a[N]; //a[]代表的是依次输入的每个值,可以没有这个数组,因为getsum()和update()都用不到,输入的顺序就是a[]的顺序!!!
int	c[N]; //表示lowbit(i)个元素的区间和

//区间查询 ->  getsum(x)返回前x个元素的和 
//i -= lowbit(i)实现将i二进制最后的1改为0(因此可以用i -= i & (i-1)代替),在树状数组上向左移动
//区间[x,y]之间的和:getsum(y)-getsum(x - 1)
int getsum(int x) {        //O(logn)
	int sum = 0;
	for (int i = x; i > 0; i -= lowbit(i)) {  //注意是>0而不是>=0!!!
		sum += c[i];
	}
	return sum;
}
//单点修改 -> update(x, v)实现将第x个元素改变v 
//i += lowbit(i) 在树状数组上向上移动。
void update(int x, int v) {        //O(logn)
	for (int i = x; i <= N; i += lowbit(i)) {
		c[i] += v;
	}
}
//推广到二维
//二维数组的区间查询:返回[1][1]到[x][y]的和
int getsum(int x, int y) {   //O(lognlogn)
	int sum = 0;
	for (int i = x; i > 0; i -= lowbit(i)) {
		for (int j = y; j > 0; j -= lowbit(j)) {
			sum += c[i][j];
		}
	}
	return sum;
}
//二位数组的单点修改
int update(int x, int y, int v) {   //O(lognlogn)
	for (int i = x; i <= N; i += lowbit(i)) {
		for (int j = y; j <= N; j += lowbit(j)) {
			c[i][j] += v;
		}
	}
}
前面:
a[i]表示:第i个元素的值,支持增加或者减少x
c[i]表示:i前面lowbit(i)个元素的和,支持查询sum[i]

case2:描述每个值变化的大小。单点查询(查询第x个元素变化的值),区间更新(前x个元素都增加或者减少某个值)
a[i]表示:第i个元素变化的值 也是从下标1开始
c[i]表示:lowbit(i)个元素每个元素变化的值,本质也是区间和。

//单点查询:也就是返回a[i],即第i个元素变化的值
int getsum(int x) {    //O(logn)  下标x的元素一直增加的值
	int sum = 0;
	for (int i = x; i <= N; i += lowbit(i)) {
		sum += c[i];
	}
	return sum;
}
//区间更新:前x个元素都加上v
//[x, y]都加上v:update(y, v); update(x - 1, -v)
int update(int x, int v) {      //O(logn)
	for (int i = x; i > 0; i -= lowbit(i)) {
		c[i] += v;
	}
}

题型:
1.区间和 或者 每个值变化情况
2.给出n个正整数!!!(小于等于N),对序列中的每个数都求出左边比他小的元素的个数
拓展题型:
(1)统计序列中左边比它大的个数:相当于求hash[a[i]+1]到hash[N] ,getsum(x)改成getsum(N)-getsum(a[i])就可以。
(2)统计序列中右边比它(大/小)的元素:从右向左遍历即可!!!
(3).如果a[i]不是小于等于10^5,而是更大,比如:10 ^9,数组开不了那么大,做法是离散化,比如 (520,999999999,18,666,88888)离散化为(2,5,1,3,4)。具体代码见:序列的离散化+树状数组

-----------------------------离散化代码:
主函数外面:
	struct Node {
		int val;
		int idx;
	}node[N];
	bool cmp(Node &node1, Node &node2) {
		return node1.val < node2.val;
	}
主函数里面:
	//step1:用结构体数组(元素值,输入的顺序)来存这些元素
	for (int i = 1; i <= n; ++i) {
		cin >> node[i].val;
		node[i].idx = i;
	} 
	//step2:对结构体数组按照元素值大小进行排序
	sort(node + 1, node + n + 1, cmp);
	//step3:依次按照a[输入的顺序]=结构体数组的下标相关的新值
	for (int i = 1; i <= n; ++i) {   //是小于等于而不是小于!!!
		if (i == 1 || node[i].val != node[i - 1].val) {
			a[node[i].idx] = i;  
		} 
		if (node[i].val == node[i - 1].val) {
			a[node[i].idx] = a[node[i - 1].idx];
		}	
	}

leetcode题目:

------------------------------------------数组

1.前面/后面第一个小于/x小于等于/大于/大于等于 ->单调栈
2.满足某条件的最小~或者最大 ~  ->二分
3.维护集合 ->并查集
4.一串英文字母,找出每个位置前一个相同元素的位置和后一个相同元素的位置。
做法:比如用last[0]记录'a'最近位置,不断更新他就好了    变量都是命名last!!!
for (int i = 0; i < 26; ++i) last[i] = -1;
for (int i = 0; i < n; ++i) {
	pre[i] = last[str[i] - 'a'];
	last[str[i] - 'a'] = i;
}
for (int i = 0; i < 26; ++i) last[i] = n;
for (int i = n - 1; i >= 0; --i) {
	nxt[i] = last[str[i] - 'a'];
	last[str[i] - 'a'] = i;
}
5.找出左边(右边)最大的(最小的)    !!!
left_max[0] = 0;
for (int i = 1; i < height.size(); i++) {
	left_max[i] = max(left_max[i - 1], height[i - 1]);
}
6.找出数组中的第K大值 ->离线:元素包括负数->排序O(nlogn)   否则->分块(根号n) 树状数组+二分(log^2n)
					->在线(元素>=0):分块思想(根号n)树状数组+二分(log^2n)
7.区间和:前缀和(只适用于离线查询)树状数组(优点在于:增加了单点修改,区间修改)
8.判断a/b == c/d的方法:都除以最大公因数->pair<int, int>相等即可   leetcode5868.可互换矩形的组数
    求最大公约数:
     __gcd(a, b) c++11以前就可以 ,头文件<algorithm>
     gcd(a, b) c++17以后支持,在力扣里可以使用
9.
有很多柱子:找两个柱子为两边来盛水使得盛的水最多->双指针 leetcode11
有很多柱子,柱子都相邻,在柱子上放水,问能放多少水,水的高度为两边最大值的较小值->leetcode42
柱状图中的最大矩形,分别找两边第一个小于他的index->单调栈 leetcode84

------------------------------------------kmp:O(m+n)

//kmp算法
暴力O(m*n)的复杂度,kmp O(m+n)的复杂度。
字符串s:从下标0开始!!!
next[i]表示字符串s[0...i]中的前缀s[0...k]等于后缀s[i-k...i]中最大的k,并且k不能等于i
递推式子为:
if (s[i] == s[j+1])  j++; 然后next[i] = j + 1; 
else j = next[j] 直到s[i] == s[j + 1]或者j == -1

//得到next数组的代码如下(next是关键字,表示指针不能用):
vector<int> next(n);
void get_next(string s) {
	//j代表前一个字符匹配的位置。
	int j = -1;
	next[0] = -1; 
	for (int i = 1; i < s.size(); ++i) { 
		//找j的位置。
		while (j != -1 && s[i] != s[j + 1]) {
			j = next[j];
		}
		if (s[i] == s[j + 1]) j++;
		next[i] = j; 
	}
}
//判断str2是否是str1的子串
int kmp(string str1, string str2) { //相比求next数组,j代表str2当前匹配的位置。
	get_next(str2);
	int j = -1;
1for (int i = 0; i < str1.size(); ++i) { // 0开始 !!!
		while (j != -1 && str1[i] != str2[j + 1]) {
				j = next[j];
		}
		if (str1[i] == str2[j + 1]) j++;
2if (j == str2.size() - 1) return i - str2.size() + 1;  //返回下标或者return true;表示匹配成功 !!!
	}
3return false; //匹配失败
}
//求模式串出现的次数:在匹配成功后,j回退到nxt[j],见上面代码
1int ans = 0;
2:修改为:
if (j == str2.size() - 1) {
	ans++;
	j = nxt[j];
}
3:修改为return ans; 

适用题型:
求最长前缀,字符串匹配,模式串在主串出现的次数
leetcode题目:
leetcode28.实现strStr()

------------------------------------------深度搜索DFS:O(指数)+剪枝

是一种枚举所有完整路径以遍历所有情况的搜索方法;每次走到死胡同就代表一条完整路径的形成。递归式就是岔路口,递归边界就是死胡同。
void dfs(参数) {
	//剪枝:
	if (满足题目中的条件) { //这里不能index == n,因为深度不一定能到这里
		if (参数>maxn) {
			更新maxn;
			更新最优方案
		}
		//这里如果能确定已经不需要再往下搜索了就return  ;
	}
	if (index > n || 不满足题目中的条件) return ; //index > n也可能index == n
	//搜索
}

void dfs(int m);
vector<int> a;
dfs(0);
--------------------------------------
1:排列:Anm ,当m==10时就成了全排列
if (m == 3) {
	//输出a[0]到a[2]就是A10 3
	return ;
}
for (int i = 0; i < 10; ++i) {
	if (!flag[i]) {   
		//先选再不选
		flag[i] = 1; 
		a.push_back(i);
		dfs(m + 1);
		flag[i] = 0; //回溯
	}
}
----------------------------
2:组合
if (a.size() == 3) {
	//输出a[0]到a[2]就是C10 3
	return ;
}
for (int i = m; i < 10; ++i) {
		a.push_back(i);
		dfs(i + 1);
		a.pop_back();
}
----------------------------
301背包()  ------>最后得到路径的话这样写
ans.push_back(a[m]);
dfs(m+1); 
ans.pop_back();
dfs(m+1);
//更简单的写法:
for (int i = 0; i < (1 << n); ++i) {
	int tmp = 0;
	for (int j = 0; (1 << j) <= i; ++j) {
		if (i & (1 << j)) {
			//第j位被选上。
		}
	}
}
//更一般的写法:
另外一种写法:
flag[m] = 1; 
dfs(m + 1);
flag[m] = 0;
dfs(m + 1); 
----------------------------
4:完全背包:直到某一时刻不能选时才决定不选 ------>最后得到路径的话这样写
ans.push_back(a[m]);
dfs(m); 
ans.pop_back();
dfs(m+1);

5.遍历(不回溯)
调用(处理根):
vis[x][y] = true; 
dfs(x, y);
void Solution::DFS(int x, int y) {
     if (下一个位置满足) {
     	 vis[xx][yy] = true; //下一个位置标记
         DFS(xx, yy); //下一层回来后不恢复
     }
}
6.遍历(回溯)
dfs(x, y, 根节点的val)
void Solution::DFS(int x, int y, int val) {
	if (下一个位置满足) {
    	val += 下一个位置的值
        DFS(xx, yy, val); //下一层回来后不标记
        val -= 下一个位置的值 //恢复现场
    }
}
7.记忆化搜索(某点x,y某个值时到结尾是否成立,所以只有两种状态:true false):
bool vis[101][101][200] = {0};  
bool dfs(int x, int y, int val) {
	//当再次来该点时,由于之前就不成立,所以直接return false
	if (hash[x][y][val] == true) return false; 
	hash[x][y][val] == true; //第一次来的时候标记一下
	return 下一个位置1 || 下一个位置2;
}

------------------------------------------广度搜索BFS

当碰到岔路口时,先依次访问该岔路口能直接到达的所有结点,再按照这些结点被访问的顺序依次访问他们能直接到达的结点。
//队列实现的模板
int bfs(int s) {
	queue<int> q;
	q.push(s);
	inq[s] = true;
	while (!q.empty()) {
		取出队头元素top
		//访问队头元素
		将队头元素出队
		top能到达的所有顶点i:
			如果没入队:
				入队
				inq[i] = true;
	}
}

------------------------------------------记录层数----------:
queue<> q;
q.push();
int layer = 0;
while (!q.empty()) {
    int len = q.size();
    for (int i = 0; i < len; ++i) {
        auto frt = q.front();
        q.pop();
    } 
    layer++;
}
return layer;

题型:
1.地图上从起点到出口的最小步数
2.矩阵中全是0/1,若矩阵中若干个1相邻则构成快,问地图上块的个数(算法笔记p276,也可以用dfs做)
------------------------------------------数据结构—树
0.定义
1.二叉树的存储结构和基本操作
(1)二叉树的二叉链表存储结构
(2)基本操作:二叉树的插入
(3)基本操作:二叉树的创建
(4)基本操作:二叉树的查找,修改
2.二叉树的遍历 时间复杂度都是O(n) n为树中节点的个数
(1)二叉树的先序遍历
(2)二叉树的中序遍历
(3)二叉树的后序遍历
(4)二叉树的层次遍历
(5)通过先序+中序遍历,重构原来的二叉树
(6)通过中序+后序遍历,重构二叉树
(7)通过中序+层次遍历,重构二叉树
3.树的存储结构
4.树的遍历
(1)树的先序遍历
(2)树的层次遍历
5.二叉查找树(BST)
void insert(node *&root, int v) //二叉查找树的插入 O(h)
node *create(int n) //二叉查找树的创建,调用上面代码 O(nh)
void search(node *root, int v) //二叉查找树的查找 O(h)
void delete(node *&root, int v) //二叉查找树的删除 O(h)
6.平衡二叉树(AVL)
void insert(node *&root, int v) //平衡二叉树的插入 O(logn)
node *create(int n) //平衡二叉树创建,调用上面代码 O(nlogn)
void search(node *root, int v) //二叉查找树的查找 O(logn)
7.堆
void downAdjust(int low, high); //对heap[low]进行向下调整 O(logn)
void create(int n); //调用上面。建立具有n个元素的堆 O(n)
void deleteTop(); //删除堆顶元素 O(logn)
void upAdjust(int low, int high); //将heap[high]进行向上调整 O(logn)
void insert(int x); //向堆中插入元素 O(logn)
void heapSort(); //堆排序 O(nlogn):建堆->O(n) 调整堆->O(nlogn),取大值O(nlogn)
8.哈夫曼树

0.定义
常见的边界数据:(1)树为空树 (2)树中只有一个结点
满足树的条件:(1)连通 (2)边数=顶点数-1
结点的深度:根结点为1      高度:叶子结点为1
二叉树的递归定义:(1)要么是一颗空树  (2)要么由根结点,左子树,右子树构成且左子树和右子树都是二叉树

1.二叉树的存储结构和基本操作
(1)二叉树的二叉链表存储结构
struct node {
	int data; //可能是权值
	node *lchild;
	node *rchild;
};
//新建结点
node *newNode(int v) {
	node *temp = new node;
	temp->data = v;
	temp->lchild = temp->rchild = NULL;    //->不能写成 . !!!
	return temp;
}2)基本操作:二叉树的插入
void insert(node *&root, int v) {    //注意加 &!!!
	if (root == NULL) { 
		root = newNode(v);          //不是new node!!!
		return ;
	} 
	if (往左子树走) {
		insert(root->lchild, v);
	}else{
		insert(root->rchild, v);
	}
}3)基本操作:二叉树的创建
node *create(int n) {
	node *root = NULL; //初始化为NULL,不断调用insert()函数即可!!!
	while (n--) {
		cin >> x;
		insert(root, x);
	}
	return root;
}4)基本操作:二叉树的查找,修改
//将二叉树中所有data值为x的结点data修改为newdata
//在使用之前先确定root不为空!!!
node *search(node *root, int x, int newdata) {
	if (root == NULL)  return ;  //递归边界
	if (root->data == x) {
		root->data = newdata;     //在这里return的话就相当于剪枝了,就遍历不到树中下一层的结点了!!!
	}
	search(root->lchild, x, newdata);
	search(root->rchild, x, newdata);
}
2.二叉树的遍历  时间复杂度都是O(n) n为树中节点的个数
//无论是哪种遍历,左子树一定先于右子树1)二叉树的先序遍历
void preorder(node *root) {
	if (root == NULL) return ;
	cout << root->data << endl;
	preorder(root->lchild);
	preorder(root->rchild);
}2)二叉树的中序遍历
void ineorder(node *root) {
	if (root == NULL) return ;
	inorder(root->lchild);
	cout << root->data << endl;
	inorder(root->rchild);
}3)二叉树的后序遍历
void postorder(node *root) {
	if (root == NULL) return ;
	postorder(root->lchild);
	postorder(root->rchild);
	cout << root->data << endl;
}4)二叉树的层次遍历
void layerorde(node *root) {
	if (root == NULL) return ; //层次遍历先判断一下二叉树是否为空!!!
	queue<node *> q;
	q.push(root);
	while (!q.empty()) {
		node *front = q.front();
		cout << front->data << endl;
		q.pop();
		if (front->lchild != NULL) q.push(front->lchild);
		if (front->rchild != NULL) q.push(front->rchild);
	}
}
//计算出每个结点的层次
struct node {
	int data;
	int layer; //增加成员layer 
	node *lchild;
	node *rchild;
};

void layerorde(node *root) {
	if (root == NULL) return ;
	root->layer = 0/1;   //根结点层次为0/1!!!
	queue<node *>q;
	q.push(root);
	while (!q.empty()) {
		node *front = q.front();
		cout << front->data << endl;
		q.pop();
		if (front->lchild != NULL) {
			front->lchild->layer = front->layer + 1;  //+1
			q.push(front->lchild);
		}
		if (front->rchild != NULL) {
			front->rchild->layer = front->layer + 1;
			q.push(front->rchild);
		}
	}
}5)通过先序+中序遍历,重构二叉树
//将父节点返回,同时递归左孩子跟右孩子
node *create(int preL, int preR, int inL, int inR) {
	//先序序列下标范围:[preL, preR];     中序序列:[inL, inR];
	//中序:[inL, k - 1]   k  [k + 1, inR]    numLeft = k - inL
	//先序: preL [preL + 1, preL + numLeft, preL + numLeft + 1, preR];
	if (preL > preR) return NULL;   //递归终止条件:区间不存在!!!
	node *root = new node;
	root->data = pre[preL]; //先序序列第一个就是根节点
	int k; //找中序序列中根节点的位置
	for (k = inL; k <= inR; ++k) {
		if (in[k] == pre[preL]) {
			break;
		}
	}
	int numLeft = k - inL; //左子树中结点的个数
	root->lchild = create(preL + 1, preL + numLeft, inL, k - 1);
	root->rchild = create(preL + numLeft + 1, preR, k + 1, inR);
	return root;
}6)通过中序+后序遍历,重构二叉树
node *create(int postL, int postR, int inL, int inR) { 
	//后序序列下标范围:[postL, postR];     中序序列:[inL, inR];
	//后序:[postL, postL + numLeft - 1]    [postL + numLeft, postR - 1]   postR
	//中序:[inL, k - 1]   k  [k + 1, inR]    numLeft = k - inL
	代码同上;
}7)通过中序+层次遍历,重构二叉树

3.树的存储结构
//一般关于树的题目,都是依次给出层次的序号:0 1 2 ....或者 1 2 3...在这种情况下,就不需要newNode函数了,直接定义成全局变量Node。
case1:包括数据域:
struct node {
	int data;
	vector<int> child;
}Node[maxn]; //maxn为结点上限个数
int n;
cin >> n; //结点的个数
for (int index = 0; index < n; ++index) {
	cin >> v;
	Node[index].data = v;
	cin >> num;
	while (num--) {
		cin >> children;
		Node[index].child.push_back(children);
	}
}
case2:不包括数据域
	vector<int> child[maxn];
4.树的遍历
(1)树的先序遍历
void preorder(int root) {   //int类型,表示下标!!!
	//无递归边界
	cout << Node[root].data << endl;
	for (int i = 0; i < Node[root].child.size(); ++i) {
		preorder(Node[root].child[i]);
	}
}2)树的层次遍历
case 1:不需要记录层号
void layerorder(int root) {
	queue<int> q;
	q.push(root);
	while (!q.empty()) {
		int front = q.front();
		cout << Node[front].data << endl;
		q.pop(); 
		for (int i = 0; i < Node[front].child.size(); ++i) {
			q.push(Node[front].child[i]);
		}
	}
}
case 2:需要记录层号
struct node {
	int data;
	int layer; //添加变量layer
	vector<int> child;
}
void layerorder(int root) {
	Node[root].layer = 0/1;   //根节点层次为0/1!!!
	queue<int> q;
	q.push(root);
	while (!q.empty()) {
		int front = q.front();
		cout << Node[front].data << endl;
		q.pop();
		for (int i = 0; i < Node[front].child.size(); ++i) {
			int child = Node[front].child[i];
			Node[child].layer = Node[front].layer + 1;
			q.push(child);
		}
	}
}
5.二叉查找树(BST)
void insert(node *&root, int v) //二叉查找树的插入 O(h)
node *create(int n) //二叉查找树的创建,调用上面代码 O(nh) 
void search(node *root, int v) //二叉查找树的查找 O(h)
void delete(node *&root, int v) //二叉查找树的删除  O(h)1)二叉查找树的递归定义
	<1>要么是一棵空树;要么由根节点,左右子树构成,左右子树都是二叉查找树,左子树所有节点数据域小于等于根节点的数据域,右子树所有节点的数据域大于根节点的数据域。
	<2>中序遍历:升序。
(2)基本操作:二叉查找树的插入
void insert(node *&root, int v) {    //时间复杂度:O(h)
	if (root == NULL) {
		root = newNode(v);
		return ;
	}
	if (root->data == v) {  //如果已经存在,则插入失败!!!
		cout << "already exist" << endl;
		return ;
	}
	else if (v < root->data) insert(root->lchild, v);
	else insert(root->rchild, v);
}3)基本操作:二叉查找树的创建,跟普通二叉树的创建代码一样
node *create(int n) {
	node *root = NULL;
	int x;
	while (n--) {
		cin >> x;
		insert(root, x);
	}
	return root;
}4)基本操作:二叉查找树的查找
//二叉查找树中无重复元素;
void search(node *root, int v) {     //时间复杂度:O(h)
	if (root == NULL) {
		cout << "no found" << endl;
		return ;
	}
	if (root->data == v) {
		cout << root->data << endl;
		return ;
	}
	if (root->data > v) {
		search(root->lchild, v);
	}else {
		search(root->rchild, v);
	}
}5)基本操作:二叉查找树的删除
做法一:前驱覆盖该节点,然后删掉前驱(比节点权值小的最大节点)
寻找root节点的前驱:左子树根节点不断沿着rchild往下直到rchild为NULL的节点
做法二:后继覆盖该节点,然后删掉后继(比节点权值大的最小节点)
寻找root节点的后继:右子树根节点不断沿着lchild往下直到lchild为NULL的节点
注意:总是优先删除前驱/后继会导致树的左右子树极度不平衡,最终退化成一条链,解决方法是:每次交替删除前驱或者后继!!!
void delete(node *&root, int v) {   //O(h)
	if (root == NULL) { //找不到该节点!!!
			return ;
	}
	if (root->data == v) {
		if (root->lchild != NULL) { //左子树不为空
			node *max = root->lchild;  //root->lchild而不是child!!!
			while (max->rchild != NULL) { //寻找root节点的前驱
				max = max->rchild;
			}
			root->data = max->data;
			delete(max, max->data);  //书上是delete(root->lchild, max->data),我这样也对吧??
		}else if (root->rchild != NULL) { //右子树不为空
			同理
		}else {  //左右子树都为空
			root = NULL; //root设置为NULL,父节点就引用不到它了!!!
		}
	}else if (v < root->data) {
		delete(root->lchild, v);
	}
	else {
		delete(root->rchild, v);
	}
}
6.平衡二叉树(AVL)
void insert(node *&root, int v) //平衡二叉树的插入 O(logn)
node *create(int n) //平衡二叉树创建,调用上面代码 O(nlogn) 
void search(node *root, int v) //二叉查找树的查找 O(logn)1)平衡二叉树的定义
二叉排序树按照从小到大插入,则生成一条链表,查找复杂度退化到O(n)。
平衡二叉树:二叉排序树+平衡,左子树和右子树高度只差绝对值不超过1,高度始终保持O(logn)
平衡因子:左右子树的高度之差
struct node {
	int data, height; //每个结点都需要保存高度,所以结构体增加高度变量
	node *lchild, *rchild;
}
node *newNode(int v) {
	前面的 
	Node.height = 1; //新增加的结点在叶子节点的位置,初始高度为1
}2)平衡二叉树获取结点所在子树的当前高度
int getHeight(node *root) { //直接返回结构体变量height即可!!!
	if (root == NULL) return 0;
	return root->height;
}3)平衡二叉树计算结点的平衡因子
int getBalanceFactor(node *root) {
	return getHeight(root->lchild) - getHeight(root->rchild);
}4)平衡二叉树更新结点当前子树的高度: height
//当前子树的高度为左右子树高度的最大值+1!!!
void updateHeight(node *root) {
	root->height = max(getHeight(root->lchild), getHeight(root->rchild)) + 1;
}5)基本操作:平衡二叉树的插入
LL 判断条件:getBalance(root) == 2 && getBalance(root->lchild) == 1
	调整:root右旋
LR 判断条件:getBalance(root) == 2 && getBalance(root->lchild) == -1
	调整:root->lchild左旋 root右旋
RL 判断条件:getBalance(root) == -2 && getBalance(root->lchild) == 1
	调整:root->rchild右旋 root左旋
RR 判断条件:getBalance(root) == -2 && getBalance(root->lchild) == -1
	调整:root左旋
void L(node *&root) {
//左旋
	node *temp = root->rchild;
	root->rchild = temp->lchild; //step1:temp的左孩子成为root的右孩子
	temp->lchild = root; //step2:root成为temp的左孩子
	updateHeight(root); //更新树高!!!
	updateHeight(temp);
	root = temp;
}
void R(node *&root) {
//右旋
	node *temp = root->lchild;
	root->lchild = temp->rchild;
	temp->rchild = root;
	updateHeight(root);
	updateHeight(temp);
	root = temp;
}
void insert(node *&root, int v) {
	//在二叉排序树的基础上+平衡调整
	//递归不断返回由下向上1:更新树高 2:调整
	if (root == NULL) {
		root = newNode(v);
		return ;
	}
	if (root->data > v) { //左子树走
		insert(root->lchild, v);
		updateHeight(root);   //记得先更新树高!!!
		if (getBalanceFactor(root) == 2) {
			if (getBalanceFactor(root->lchild) == 1) {  //LL
				R(root);
			}
			if (getBalanceFactor(root->lchild) == -1){  //LR
				L(root->lchild);
				R(root);
			}
		}
	}
	if (root->data < v) { //右子树走
		insert(root->rchild, v);
		updateHeight(root);
		if (getBalanceFactor(root) == -2) {
			if (getBalanceFactor(root->rchild) == 1) { //RL
				R(root->rchild);
				L(root);
			}
			if (getBalanceFactor(root->rchild) == -1) { //RR
				L(root);
			}
		}
	}
}6)基本操作:平衡二叉树的创建
//跟BST的代码一样7)基本操作:平衡二叉树的查找
//与BST查找代码一样8)无删除操作
7.堆
完全二叉树。
大顶堆:结点的值不小于其左右孩子的值,根是树中最大的值。  ->从小到大排序
小顶堆:结点的值不大于其左右孩子的值,根是树中最小的值。  ->从大到小排序
优先队列默认使用大顶堆
void downAdjust(int low, high); //对heap[low]进行向下调整 O(logn)
void create(int n); //调用上面。建立具有n个元素的堆 O(nlogn)
void deleteTop(); //删除堆顶元素 O(logn)
void upAdjust(int low, int high); //将heap[high]进行向上调整 O(logn)
void insert(int x); //向堆中插入元素 O(logn)
void heapSort(); //堆排序 O(nlogn):建堆->O(n) 调整堆->O(nlogn),取大值O(nlogn)1)建堆
const int maxn = 105;
int heap[maxn];
int n;
void downAdjust(int low, int high) {    //O(logn)
	int i = low, j = 2 * i;
	while (j <= high) { //如果存在孩子节点
		if (j + 1 <= high && heap[j + 1] > heap[j]) { //如果存在右孩子
			j = j + 1;
		}
		if (heap[j] > heap[i]) { 
			swap(heap[i], heap[j]);
			i = j;  //更新i和j
			j = 2 * i;
		}else {
			break; //孩子节点均小于该节点,则调整结束
		}
	}
}
void create() {   //O(nlogn)
	for (int i = 1; i <= n; ++i){ //heap数组从下标1开始存,则左孩子为2*i,右孩子为2*i+1
		cin >> heap[i]; 
	}
	for (int i = n / 2; i >= 1; --i) { //从最后一个非叶子节点开始调整!!!
		downAdjust(i, n);
	}
}2)删除堆顶元素
void deleteTop() { //O(logn)
	heap[1] = heap[n--]; //最后一个节点取代根节点的位置
	downAdjust(1, n); //调整根节点的位置
}3)添加元素
void upAdjust(int low, int high) {
	int i = high, j = i / 2; //j为该节点的父节点
	while (j >= low) { //存在父节点
		if(heap[j] < heap[i]) {
			swap(heap[i], heap[j]);
			i = j;
			j = i / 2;
		} else break;
	}
}
void insert(int x) {   //O(logn)
	heap[++n] = x; //新增加的元素放到堆中最后一个位置的后面
	upAdjust(1, n); //对该元素向上调整
}4)堆排序
//思路:将堆顶元素和最后的元素交换,对堆顶进行调整(调整后堆顶成为第二大)。倒数第二元素和堆顶元素交换,再调整堆顶。。。重复下去
void heapSort() { //取最大时间复杂度:O(nlogn)
	create(); //建堆 O(nlogn)
	for(int i = n; i > 1; --i) {  //O(nlogn)
		swap(heap[1], heap[i]); //最后一个元素取代堆顶
		downAdjust(1, i - 1);   //对堆顶元素进行向下调整 ,是i-1而不是n!!!
	}
}
8.哈夫曼树
(1)哈夫曼树的定义
过程:视为n棵只有1个节点的树->合并权值最小的两棵树,直到只有一个结点为止。
特点:带权路径长度最短;不存在度为1的结点,权值越大的节点离根越近;树的带权路径长度=所有非叶子节点的权值之和
解决:
1:字符串的最短编码长度
2:n个果堆,每次合并两个果堆,同时会消耗这两个果堆的质量之和,问合并为一堆以后消耗的最小质量(即建二叉树,非叶子结点权值和最小)
(2)带权路径长度(优先队列来实现)
long long ans = 0, tmp;
queue<int, vector<int>, greater<int> > q; //优先队列(小顶堆)
for (int i = 0; i < n; ++i) {
	cin >> tmp;
	q.push(tmp);
}
while (q.size() > 1) {
	long long tmp1 = q.top();
	q.pop();
	long long tmp2 = q.top();
	q.pop();
	ans += tmp1 + tmp2;
	q.push(tmp1 + tmp2);
}
cout << ans << endl;

leetcode题目:

------------------------------------------数据结构—图
1.图的存储
-------------邻接矩阵:适用于顶点数目n小于1000 且 除了边权没有其他更多的属性
------------邻接表:适用于顶点数目大于1000 或者 除了边权以外还有其他的属性
2.图的遍历
(1)DFS(邻接表,邻接矩阵)
(2)BFS(邻接表,邻接矩阵)
3.dijkstra求无向图/有向图中的最短路径(邻接表,邻接矩阵)
出题方式1:最短路径只有一条,要求输出最短路径
出题方式2:增加第二标尺边权(花费),当最短路径有多条时,要求费用和最小
出题方式3:增加第二标尺(点权),当最短路径有多条时,要求点权和最大
出题方式4:直接问有多少条最短路径
出题方式5:最短路径有很多条并且不是简单的边权和和点权和
4.最小生成树
(1)prim算法(邻接矩阵)
(2)kruskal(结构体数组)
5.拓扑排序

1.图的存储
-------------邻接矩阵:适用于顶点数目n小于1000 且 除了边权没有其他更多的属性
无边权:有边存1,无边存INF
有边权:有边存边权,无边存INF
无向图加边:G[u][v]和G[v][u]都要增加
有向图加边:只需要增加G[u][v]
const int MAXV = 1010; //最大顶点数
const int INF = 0x3fffffff;
int n, m; //n为顶点数量,m为边的数量
int G[MAXV][MAXV];
fill(G[0], G[0] + MAXV * MAXV, INF);
for (int i = 0; i < m; ++m) {
	cin >> u >> v >> G[u][v];
	G[v][u] = G[u][v]; //无向图加,有向图不加
}
------------邻接表:适用于顶点数目大于1000 或者 除了边权以外还有其他的属性
无边权:邻接表只存顶点号
vector<int> Adj[MAXV];
有向图增加边:Adj[u].push_back(v);
无向图增加边:Adj[u].push_back(v); Adj[v].push_back(u);

有边权:邻接表存顶点号和边权
struct node {
	int v; //顶点号
	int w; //边权或者用dis!!!
	node(int _v, int _w) : v(_v), _w(w) {}
	node() {}
};
vector<node> Adj[MAXV];
增加边:Adj[u].push_back(node(v, 3));

2.图的遍历
无向图中的连通分量和有向图中的强连通分量均称为连通块。
遍历的思路:将经过的顶点设置为已访问,在下次碰到这个顶点时就不再处理,直到所有的顶点都被访问。
------------------DFS深度优先搜索遍历的伪代码----------------------
bool vis[MAXV] = {false}; //标志数组,访问过为true
void DFSTrave() {
	图中所有的顶点:
		如果没有被访问过:
			DFS(该顶点);
}
void DFS(int u) {
	将该顶点置为访问过
	//访问该顶点
	该顶点能够到达的所有顶点:
		如果没有被访问过:
			DFS(该顶点);
}
==========邻接矩阵版:
void DFSTrave() {
	for (int i = 0; i < n; ++i) {
		if (vis[i] == false) DFS(i, 1);
	}
}
void DFS(int u, int depth) {  //depth表示深度,初始深度为1
	vis[u] = true;
	//访问顶点u
	for (int v = 0; v < n; ++v) {
		if (vis[v] == false && G[u][v] != INF) {    //别少了G[u][v] != INF !!!
				DFS(v, depth + 1);
		}
	}
}
==========邻接表版(无权值):
void DFSTrave();同上
void DFS(int u, int depth) {
	vis[u] = true;
	for (int i = 0; i < Adj[u].size(); ++i) {
		int v = Adj[u][i];   //u的邻接顶点是v!!!
		if (vis[v] == false) DFS(v, depth + 1);
	}
}
------------------BFS广度优先搜索遍历的伪代码----------------------
int inq[MAXV] ={false}; //true表示已入队
void BFSTrave() {
	图中的所有顶点:
		如果没有入队:
			BFS(该顶点)
}
void BFS(int u) {
	定义一个队列
	将u入队
	将顶点u设为已入队
	while(队列不空){
		获得队头顶点top
		//访问该顶点
		将top顶点出队
		top顶点能够到达的所有顶点:
			如果没有入队:
				入队
				该顶点设为已入队
	}
	
}
==========邻接矩阵版:
void BFSTrave() {
	for (int i = 0; i < n; ++i) {
		if (inq[i] == false) BFS(i);
	}
}
void BFS(int u) {
	queue<int> q;
	q.push(u);
	inq[u] = true;
	while (!q.empty()) {
		int top = q.front();
		//访问top
		q.pop();
		for (int v = 0; v < n; ++v) {
			if (inq[v] == false && G[u][v] != INF) {
				q.push(v);
				inq[v] = true;
			}
		}
	}
}
==========邻接表版(无权值):
void BFSTrave();同上
void BFS(int u) {
	...
	while (!q.empty()) {
		...
		for (int i = 0; i < Adj[top].size(); ++i) {
			int v = Adj[top][i];
			if (inq[v] == false) {
				q.push(v);
				inq[v] = true;
			}
		}
	}
}

3.dijkstra求无向图/有向图中的最短路径 (n的2次方的复杂度,使用priority_queue堆优化的方法可以使邻接表的算法降低到nlogn的复杂度)
作用:单源最短路问题。给出起点s,能够获得到其他所有顶点的最短路径。需要注意的是:访问的顺序是按照到起点s距离最短来的,并不是按路径来的。边权为负值的话就不能用dijkstra算法了。
算法思想:S集合存放已经被访问的顶点。然后执行下列n次:每次从集合V-S中选择距离起点s最短的顶点加入集合S,然后以该顶点为中介点,优化起点s和所有该顶点能够到达的顶点的最短距离。
dijkstra的伪代码实现:
S集合的实现:vis[]数组,false表示未加入
最短距离:d[i]存储顶点i到起点s的最短距离(d[s]初始值为0,其他均为INF)
void dijkstra(int s) {//起点
	初始化d[]
	n次循环:
		找出距离s最短的顶点u
		找不到 return ; //该连通块访问完了
		将u设置为已访问
		for(u能够到达的所有顶点v)
			如果顶点v未被访问并且经过u到达v的距离更短
				更新d[v]
}

==========邻接矩阵版:
void dijkstra(int s) {
	fill(d, d + MAXV, INF);
	d[s] = 0;
	for (int i = 0; i < n; ++i) {
		int u = -1, min = INF;
		for (int v = 0; v < n; ++v) {
			if (vis[v] == false && d[v] < min){  //别少了 vis[v] == false!!!
				u = v; min = d[v];
			}
		}
		if (u == -1) return ;  
		vis[u] = true; //将u设置为已访问!!!
		for (int v = 0; v < n; ++v) {
		if (vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) { //别少了G[u][v] != INF !!!
			d[v] = d[u] + G[u][v];
		}
		
	}
}

==========邻接表版:
struct node {
	int v;
	int dis; //边权
};
vector<node> Adj[MAXV];
void dijkstra(int s) {
	...
		for (int i = 0; i < Adj[u].size(); ++i) {
			int v = Adj[u][i].v;   //v获得顶点
			//是Adj[u][i].dis, 而不是Adj[u][v].dis !!!
			if (vis[v] == false && d[u] + Adj[u][i].dis < d[v]) {
				d[v] = d[u] + Adj[u][i].dis; //距离的写法是Adj[u][i].dis
			}
		}
	}
}
出题方式1:最短路径只有一条,要求输出最短路径,做法是:定义一个int pre[MAXV]存结点的前驱,在dijkstra()算法开头pre[s]= s,然后在d[v] = d[v] + Adj[u][i].dis; 后面加上pre[v] = u;
得到的类似就是:pre[4]=3 pre[3]=2  pre[2]=2
递归访问pre数组:
//vector<int> path;
void DFS(int i) { //v为终点顶点编号
	if (i == pre[i]) {
		//path.push_back(i);取代下面!!!
		cout << i << endl; 
		return ;
	}
	DFS(pre[i]);  //在cout的前面输出下一层。在输出当前层,顺序很重要!!!!
	//path.push_back(i);取代下面!!!
	cout << i << endl; 
}
出题方式2:增加第二标尺边权(花费),当最短路径有多条时,要求费用和最小 (更多边属性的话放到结构体里!!!)
做法:增加一个数组c[i]表示从顶点i到s最小的花费,初始化:c[s] = 0 ,其他初始化INF
struct node {
	int v;
	int dis; //边权
	int cost; //第二尺度:花费
};
if (vis[v] == false) {
	if (d[u] + Adj[u][i].dis < d[v]) {
		d[v] = d[u] + Adj[u][i].dis;
		c[v] = c[u] + Adj[u][i].cost;
	}
	if (d[u] + Adj[u][i].dis == d[v] && c[u] + Adj[u][i].cost < c[v])
		c[v] = c[u] + Adj[u][i].cost;
}
出题方式3:增加第二标尺(点权),当最短路径有多条时,要求点权和最大(点权的话用一个int数组!!!)
做法:weight[]表示每个顶点的点权
w[i]从起点到s最大的点权和初始化:w[s] = weight[s] ,其他初始化0!!!
int weight[MAXV];
if (vis[v] == false) {
	if (d[u] + Adj[u][i].dis < d[v]) {
		d[v] = d[u] + Adj[u][i].dis;
		w[v] = w[u] + weight[v];
	}
	if (d[u] + Adj[u][i].dis == d[v] && w[u] + weight[v] > w[v])  //是> !!!
		w[v] = w[u] + weight[v];
}

出题方式4:直接问有多少条最短路径
nums[i] 表示顶点i的最短路径数目。初始化:nums[s]=1,其余均为0 
if (vis[v] == false){
	if (d[u] + Adj[u][i].dis < d[v]) {
		d[v] = d[v] + Adj[u][i].dis; 
		nums[v] = nums[u];
	}
	if (d[u] + Adj[u][i].dis == d[v]) nums[v] += nums[u];
}
出题方式5:最短路径有很多条并且不是简单的边权和和点权和
做法:dijkstra + DFS
vector<int> pre[MAXV];来存储前一个顶点。
pre[s].push_back(s);
if (vis[v] == false) {
	if (d[u] + Adj[u][i].dis < d[v]) {
		d[v] = d[u] + Adj[u][i].dis;
		pre[v].clear(); //先清空
		pre[v].push_back(u);
	}
	if (d[u] + Adj[u][i].dis == d[v]) {
		pre[v].push_back(u);
	}
}
int val; //最优值,命名!!!
vector<int> tmp;
vector<int> ans; //最优路径
tmp.push_back(t);
void DFS(int t){ 
	if (t == pre[t][0]) {
		int tmp_val;
		从tmp.size()-1往前访问tmp计算tmp_val
			如果tmp_val优于val:
				val = tmp_val;
				ans = tmp;
		return ;
	}
	for (int i = 0; i < pre[t].size(); ++i) {  //循环中只有一条语句
		tmp.push_back(pre[t][i]); 
		DFS(pre[t][i]);
		tmp.pop_back();
	}
}

4.最小生成树
给定一个无向图,在该图中找一棵树,树中包含了图中所有的顶点,并且满足边权和最小。最小生成树不唯一,但是权值唯一,最后需要得到这个权值。
------------------prim算法的伪代码,n的2次方的复杂度,边多的情况下使用---------------------
思想:与dijkstra思想不一样的是,dijkstra找的是距离起点s距离最近的顶点,而prim算法找的是距离集合S最近的顶点。dijkstra中d[]代表距离起点s最近的距离,prim中的d[]代表距离集合S最近的距离。
伪代码为:
int prim(){ 
	//默认使用顶点0号作为起点
	int ans = 0; //最小生成树的边权
	fill(d, d + MAXV, INF);
	d[0] = 0; //起点距离设置为0
	重复n次:
		找出d[]中未被访问并且距离最短的顶点u
		如果不存在 return -1;  //图不连通
		ans += d[u]; //将边权加到ans上
		vis[u] = true;
		对于顶点u能够到达所有边v:
			如果未被访问并且u->v的距离大于d[v]
				更新d[v]
	return ans;	
}
==========邻接矩阵版:
int prim() {
	int ans = 0; //加上!!!
	fill(d, d + MAXV, INF);
	d[0] = 0;
	for (int i = 0; i < n; ++i) {
		int u = -1, min = INF;
		for (int v = 0; v < n; ++v) {
			if (vis[v] == false && d[v] < min) {
				u = v;
				min = d[v];
			}
		}
		if (u == -1) return -1;
		vis[u] = true;  
		ans += d[u];  //加上!!!
		for (int v = 0; v < n; ++v) {
			//注意是G[u][v] < d[v]!!!
			if (vis[v] == false && G[u][v] != INF && G[u][v] < d[v]) 
				d[v] = G[u][v];
		}
	}
	return ans;	
}
==========邻接表版省略
------------------kruskal算法的伪代码,边少的情况下使用----------------------
思想:每次挑选出未加入最小的边,判断该边两个顶点是否在同一个集合,如果不在则合并(并查集),否则不做处理,直到加入的边数=n-1退出循环。
伪代码如下:
struct edge {
	int u;
	int v; 
	int w; //边权
};
bool cmp(edge&e1, edge &e2) {
	return e1.w < e2.w;
}
edge e[MAXE];
int  kruskal() {
	对所有的边排序
	循环m次:
		选出最短的边tmp
			如果该边的两个顶点不在同一个集合中:
				biansu++;
				ans += tmp.w;
				集合合并;
				如果边数等于n-1 return ans;
	return -1;  //不连通
}
==========结构体数组版:
int father[n];
void init(); //并查集初始化
int findfather(int x); //并查集查找
int kruskal() {
	int ans = 0, bianshu = 0;
	init(); //并查集初始化!!!
	sort(e, e + m, cmp);
	for (int i = 0; i < m; ++i) {
		edge tmp = e[i];
		int fau = findfather(tmp.u); !!!
		int fav = findfather(tmp.v);
		if (fau != fav) {
			father[fau] = fav; //并查集合并
			bianshu++;
			ans += tmp.w;
			if (bianshu == n - 1) return ans;	
		}	
	}
	return -1; 
}
5.拓扑排序
(1)拓扑排序:任意两个顶点u v,如果存在边u->v,那么序列中u一定在v前面。
(2)针对的对象:有向无环图
(3)应用:   判断是否是有向无环图!!!
算法思想:
创建一个队列,将入队为0的结点入队,同时将能直接到达的结点入度-1
	循环:如果队列不空
		出队
		nums++;
		更新到达的节点的入度
	判断nums是否等于结点数,如果相等,则结束,否则返回false
vector<int> G[MAXV];
int  indegree[MAXV];//入度表
void tuopuSort() {
	queue<int> q;
	/*
	如果要求有多个入度为0的节点时,选择编号最小的节点,将上面改为:
	priority_queue<int, vector<int>, greater<int> > q;
	*/
	int nums = 0;
	for (int i = 0; i <  n; ++i) {
		if (indegree[i] == 0) {
			q.push(i);
		}
	}
	while (!q.empty()) {
		int front = q.front();
		cout << front << endl;
		q.pop();
		nums++;
		for (int i = 0; i < G[front].size(); ++i) {  //不是front.size()!!!
			int u = [front][i];
			indegree[u]--;
			if (indegree[u] == 0) {
				q.push(u);
			}
		}
	}
	if (nums == n) {
		rerurn true; //是有向无环图
	}else {
		return false; //不是有向无环图
	}
}
6.关键路径

图搜索的题型
1.连通块中点权和,边权和,顶点个数,点权最大的结点。
2.有向图中,每条边没有权值,求最短路径。可以把每条边权看作为1,直接用BFS就行,可以不用Dijkstra。
leetcode题目:

------------------------------------------动态规划DP

//特点:一个问题可以被分解成若干个子问题,且这些子问题会重复出现,通过最优子问题的结构来达到原问题的最优解。必须有重复子问题,才能用DP。
int dp[10000];
int f(int n) {
	if (dp[n] != -1) return dp[n]; //已经计算过
	dp[n] = f(n - 1) + f(n - 2); //状态转移方程!!!
	return dp[n]; 
}
int main() {
	memset (dp, 0xff, sizeof(dp)); //-1表示还未计算过。
	dp[1] = dp[2] = 1;
}

/*
做题关键:找出状态转移方程,然后用递归套模板即可
易错点:注意最后输出的是dp数组中的最大值还是dp中的某个元素!!!
f()中:
1. f别写成dp[]!!
2. 当前层dp[]一定要有值可赋!!!
*/

题型
1.数塔(二维,逐个递推): 给一个数塔,问从最顶到最底最长的路径和是多少
dp[i][j]表示: i,j的位置到底部的最长路径长度
转移方程: dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j]
边界: dp[n][j] = a[n][j]
调用:f(1, 1); ----->看是1:一维/二维,然后根据是否是2:逐个递推/分组来判断
ans : dp[1][1] ------->ans是什么,具体题目具体分析:某个dp值?dp中的最大值?dp所有值的和?

2.最大连续子序列和(一维递推): 给一个数字序列,找出一个连续子序列使之和最大(找最优子序列的问题可以朝DP想)
dp[i]表示: 以i结尾的序列的最大值
转移方程: dp[i]=max(dp[i-1]+a[i],a[i])
边界: dp[0] = a[0];
ans 是dp中的最大值
连续:dp[]表示以i结尾的~

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n);
        dp[0] = nums[0];
        int ans = dp[0];
        for (int i = 1; i < n; ++i) {
            dp[i] = max(dp[i - 1] + nums[i], nums[i]);
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

3.最长递增子序列(一维递推): 给一个数字序列,找出最长一个递增的子序列(可以不连续) 的长度
dp[i]表示: dp[i]表示以i元素结尾的最长长度
转移方程: dp[i]=max(dp[j]+1,dp[i]) 0<=j<i 为小于nums[i]的左边元素所有下标
边界: dp[i]均初始化为1
ans: dp[中的最大值]

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n, 1);
        int ans = 1; 
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i]) {
                    dp[i] = max(dp[j] + 1, dp[i]);
                }
            }
            ans = max(dp[i], ans);
        }
        return ans;
    }
};

4.最长公共子序列长度: 给出两个字符串str1和str2,问最长公共子序列的长度 (不连续)
dp[i][j]表示 字符串1的i和字符串2的j结尾的公共子序列最长长度
转移方程:!!!
str1[i]==str2[j]: dp[i][j]=dp[i-1][j-1]+1
str1[i]!=str2[j]: dp[i][j]=max(dp[i-1][j],dp[i][j-1])
可以发现使用的是左上角,上方,左方的数字。
边界: dp[0][0…text2.size()] dp[0…text1.size()][0] 均为0,下标可以理解为空串!!!
ans是 dp[str1.size()][str2.size()];

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size()+1, vector<int>(text2.size()+1));
        int len1 = text1.size();
        int len2 = text2.size();
        for (int i = 0; i < len1; ++i) {
            for (int j = 0; j < len2; ++j) {
                if (text1[i] == text2[j]) {
                    dp[i + 1][j + 1] = dp[i][j] + 1;  //实际上是在dp[1...text1.size()][1...text2.size()]存储!!!
                }else {
                    dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
                } 
            }
        }
        return dp[len1][len2];    
    }
};

5.最长回文子串的长度: 给出一个字符串,找出最长的回文子串
dp[i][j]表示 i到j之间是否是回文串,是为1,不是就为0
转移方程:
str1[i]==str1[j]: dp[i][j]=dp[i+1][j-1]
str1[i]!=str1[j]: dp[i][j]=0
边界:
可以发现:边界是长度为1和长度为2的子串,所以按照长度 左端点 来递推
dp[i][i] = 1;
if(str[i] == str[i+1]) dp[i][i+1]= 1;
else dp[i][i+1]=0;
ans的值: dp中为1的值中j-1+1的最大值。

class Solution {
public:
    string longestPalindrome(string s) {
    
        int n = s.size();
        string ans = string(1, s[0]);
        vector<vector<bool>> dp(n, vector<bool>(n));
        for (int i = 0; i < n; ++i) {
            dp[i][i] = true;
            if (i < n - 1) {
                if (s[i] == s[i + 1]) {
                    dp[i][i + 1] = true;
                    if (2 > ans.size()) ans = s.substr(i, 2);
                }else dp[i][i + 1] = false;
            }
        }
        for (int len = 3; len <= n; ++len) {
            for (int i = 0; i <= n - len; ++i) {
                if (s[i] == s[i + len - 1]) {
                    if ((dp[i][i + len - 1] = dp[i + 1][i + len - 2]) && len > ans.size()) {
                        ans = s.substr(i, len);
                    }
                }
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值