董晓算法笔记
链表
单链表
#include<iostream>
using namespace std;
struct Node
{
int data;
Node* next;
};
Node* head; //头指针
void CreateLink() {
int x;
Node* tail, * p; //tail是尾指针
printf("head:%d\n&tail:%d &p:%d\n", &head, &tail, &p);
head = new Node;
printf("head:%d\n", head);
tail = head;
tail->next = NULL;
printf("请输入数值, -1结束\n输入:");
while (cin >> x && x != -1) {
if (x == 5) {
Node* q = new Node;
q->data = 1;
q->next = NULL;
printf("q:%d q->data:%d q->next:%d\n", q, q->data, q->next);
tail->next = q;
tail = q;
}
else {
p = new Node; //新建一个节点
p->data = x;
p->next = NULL;
printf("p:%d p->data:%d p->next:%d\n", p, p->data, p->next);
tail->next = p; //尾插法
tail = p;
}
}
}
//将x插入到第i个节点前
void InsertLink(int i, int x) {
Node* p, * t;
int j;
j = 1;
t = head;
while (t != NULL && j < i - 1) { //当j = i - 1,即t指向第i个节点
t = t->next;
j += 1;
}
if (t == NULL || i < 1) {
printf("越界了\n");
}
else {
p = new Node;
p->data = x;
p->next = t->next;
t->next = p;
}
}
//删掉第i个节点
void DeleteLink(int i) {
Node* p, * t;
int j = 0;
t = head;
while (t != NULL && j < i - 1) {
t = t->next;
j++;
}
if (t->next == NULL || i < 1) {
printf("越界了\n");
}
else {
p = t->next;
t->next = p->next;
delete[] p;
}
}
void Print() {
Node* p = head->next;
printf("链表输出:");
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
}
int main() {
CreateLink();
Print();
DeleteLink(2);
Print();
InsertLink(1, 10);
Print();
return 0;
}
循环链表
将单链表的尾部指向head
#include<iostream>
using namespace std;
struct node
{
int data;
node* next;
};
node* h, * t, * p;
void Create(int n) {
int i;
h = new node;
h->data = 1;
h->next = NULL;
t = h;
for (i = 2; i <= n; i++) {
p = new node;
p->data = i;
p->next = NULL;
t->next = p;
t = p;
}
t->next = h; //闭环
}
约瑟夫环

循环链表
#include<iostream>
using namespace std;
struct Node
{
int data;
Node* next;
};
Node* head, * tail, * p;
int main() {
int n, m;
cin >> n >> m;
head = new Node;
head->data = 1;
head->next = NULL;
tail = head;
for (int i = 2; i <= n; i++) {
p = new Node;
p->data = i;
p->next = NULL;
tail->next = p;
tail = p;
}
tail->next = head;
tail = head; //闭环
for (int i = 1; i <= n; i++) {
for (int j = 1; j < m - 1; j++) tail = tail->next; //只移动m-2次,指向m-1
printf("%d ", tail->next->data);
tail->next = tail->next->next; //跳过第m个
tail = tail->next;
}
return 0;
}
队列
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
queue<int> a;
int main()
{
int b,c,d,e=1,f=0;
cin>>b>>c;
for(int i=1;i<=b;i++)
{
a.push(i);//模拟队列
}
while(!a.empty())
{
if(e==c)//如果这个人正好被踢
{
cout<<a.front()<<" ";//先输出
a.pop();//再删除
e=1;//再从1开始报数
}
else if(e!=c)//如果不被剔除
{
e++;//报的数+1
a.push(a.front());//先把head压进队尾
a.pop();//再把head删除
}
}
return 0;//结束程序(完美)
}
双向链表
#include<iostream>
using namespace std;
struct Node {
int data;
Node* pre, * next;
};
Node* head, * tail;
void CreateLink() {
int x;
Node* p;
head = NULL;
tail = head;
printf("请输入数值,-1结束\n输入:");
while (cin >> x && x != -1) {
p = new Node;
p->data = x;
p->pre = tail;
p->next = NULL;
if (head == NULL) {
head = p;
printf("Node:%d data:%d pre:%d next:%d\n", sizeof(Node), sizeof(head->data),
sizeof(head->pre), sizeof(head->next));
printf("&head:%d head:%d\n&data:%d &pre:%d &next:%d\n", &head, head, &head->data, &head->pre, &head->next);
}
else {
tail->next = p;
}
tail = p;
}
}
int main() {
CreateLink();
return 0;
}
模拟链表
用于对图的存储
原理:创建两个数组——数据数组data与游标数组next,游标数组next[i]存储data[i]的后继元素的下标,相当于链表中的指针

#include<iostream>
using namespace std;
char data[101]; //数据
int next[101]; //游标
int len;
int main() {
int i;
len = 5;
char a[9] = { ' ', 'a', 'e', 'd', 'b', 'c' };
int b[9] = { 1, 4, 0, 2, 5, 3 };
printf("下标 data next\n");
for (i = 0; i <= len; i++) {
::data[i] = a[i];
::next[i] = b[i];
printf(" %d %c %d\n", i, a[i], b[i]);
}
printf("\n输出:");
i = ::next[0]; //第一个元素的下标
while (i) {
printf("%c ", ::data[i]);
i = ::next[i]; //依次往后,直到next[i] == 0
}
return 0;
}
栈
数组s[n]存放栈元素,top表示一个栈指针,其中s[0]与s[n]不存放元素,分别表示栈空与栈满
#include<iostream>
using namespace std;
#define n 100
int s[n]; //栈
int top = 0; //指针
void push(int x) //入栈
{
if (top == n) printf("栈已经满了");
else s[++top] = x;
}
int pop() //出栈
{
if (top == 0) printf("栈已经空了");
else return s[top--];
}
int main()
{
push(1);
push(2);
push(3);
push(4);
for (int i = 1; i <= top; i++) {
printf("%d ", s[i]);
}
pop();
pop();
printf("top:%d\n", top);
for (int i = 1; i <= top; i++) {
printf("%d ", s[i]);
}
return 0;
}
进制转换

#include<iostream>
using namespace std;
#define n 100
int s[n]; //栈
int top = 0; //指针
int main()
{
int a, d;
cout << "请输入一个十进制数:a = ";
cin >> a;
cout << "输入要转换的进制:d = ";
cin >> d;
do {
s[++top] = a % d;
a /= d;
} while (a != 0);
do {
cout << s[top--];
} while (top != 0);
return 0;
}
括号匹配

#include<iostream>
using namespace std;
char c[256] = "((dx++) + (jf++)) > 209 @";
char s[21]; //栈
int top = 0; //指针
bool judge(char c[]) {
int i = 0;
while (c[i] != '@') {
if (c[i] == '(') {
s[++top] = c[i]; //入栈
}
if (c[i] == ')') {
//if (top == 0) {
// return false; //不匹配,右括号多些
//}
//else {
// top--;
//}
if (top > 0) top--;
else return false;
}
i++;
}
if (top != 0) return false;
else return true;
}
int main()
{
if (judge(c)) printf("YES");
else printf("NO");
return 0;
}
后缀表达式

#include<iostream>
using namespace std;
char c[256] = "12 6 /9 +2 5 *-"; //后缀表达式
char s[101]; //栈
int top = 0; //指针
int main()
{
int i = 0, x;
while (i < strlen(c)) {
switch (c[i])
{
//--top会全局改变top的值,top + 1不会全局改变值,只是对top + 1位置值的引用
case '+': s[--top] += s[top + 1]; break;
case '-': s[--top] -= s[top + 1]; break;
case '*': s[--top] *= s[top + 1]; break;
case '/': s[--top] /= s[top + 1]; break;
default:
x = 0;
while (c[i] != ' ') {
x = x * 10 + c[i++] - '0'; //如果是一个多位数,要这样操作
}
s[++top] = x; //入栈
}
printf("s[%d] = %d\n", top, s[top]);
i++;
}
printf("result = %d\n", s[top]);
return 0;
}
车厢调度


车厢c就是一个栈,先进后出。根据给出的出栈顺序1,依次遍历A位置车厢,如果不符合条件,入栈,直到符合条件,将其先入栈,出栈。每次出栈的都是栈顶的元素,所以只要判断栈顶元素每次都符合s[top] == a[i],即可,如果不符合则直接输出"NO"
#include<iostream>
using namespace std;
const int N = 1010;
char s[N]; //栈
int top = 0; //指针
int a[N] = { 0, 3, 2, 5, 4 , 1 }; //出站顺序
int main()
{
int n = 5, i, cur; //cur表示A位置的车厢顺序
for (i = 1, cur = 1; i <= n; i++) {
while (cur <= a[i]) {
s[++top] = cur++; //入栈:s[1] = 1;...
printf("入栈:s[%d] = %d\n", top, s[top]);
}
if (s[top] == a[i]) {
printf("出栈:s[%d] = %d\n", top, s[top]);
top--;
}
else {
cout << "NO" << endl;
return 0;
}
}
cout << "YES" << endl;
return 0;
}
队列

#include<iostream>
using namespace std;
#define n 100
int Q[n];
int head = 0, tail = 0;
void push(int x) { //将x插入队列
if (tail == n) cout << "队满" << endl;
else Q[++tail] = x;
}
int pop() {
if (tail == head) cout << "队空" << endl;
else return Q[++head];
}
int main() {
push(1);
push(2);
push(3);
for (int i = head + 1; i <= tail; i++) {
cout << Q[i] << endl;
}
pop();
pop();
for (int i = head + 1; i <= tail; i++) {
cout << Q[i] << endl;
}
}
循环队列

#define n 100
int Q[n];
int head = 0, tail = 0;
void push(int x) {
tail = (tail + 1) % n;
//tail赶上了head指针,队满
if (tail == head) cout << "队满" << endl;
else Q[tail] = x;
}
int pop() {
if (head == tail) cout << "队空" << endl;
else {
head = (head + 1) % n;
return Q[head];
}
}
Blah数集

思路:一个数组,定义3个指针,pa、pb、tail,分别表示2X + 1, 3X + 1, 放入数组的位置。因为Blah数组是按照升序排列的,所以放入数组的元素总是Q[pa]与Q[pb]中较小的那一个,且保证Q[无重复值]
#include<iostream>
using namespace std;
const int N = 100000;
long long Q[N];
int pa = 1, pb = 1, tail = 2;
int a, n; //a是基数,n是第n个元素
int main() {
cin >> a >> n;
Q[1] = a; //基数x
int t1, t2, t;
while (tail <= n) {
t1 = Q[pa] * 2 + 1;
t2 = Q[pb] * 3 + 1;
t = min(t1, t2);
if (t != Q[tail - 1]) { //保证升序,重复的不要
Q[tail++] = t;
}
//后移指针
if (t1 < t2) pa++;
else pb++;
}
cout << "Q[" << n << "] = " << Q[n] << endl;
}
连通块问题

广度优先搜索
#include<iostream>
using namespace std;
const int N = 110;
int Q[N * N][2]; //队列
int head, tail;
bool visi[N][N]; //访问标记
//上右下左
int dx[4] = { -1, 0, 1, 0 };
int dy[4] = { 0, 1, 0, -1 };
int n = 3, m = 3, ans; //行、列、答案
int a[N][N] = { //地图
{0, 0, 0, 0},
{0, 1, 1, 1},
{0, 0, 1, 0},
{0, 1, 0, 1}
};
void BFS(int x, int y) {
head = 0, tail = 1;
visi[x][y] = true; //标记
Q[1][0] = x;
Q[1][1] = y;
while (head < tail) { //如果没找到可以连通的则结束本次搜索
++head;
x = Q[head][0];
y = Q[head][1];
for (int i = 0; i < 4; i++) { //4个方向
int x1 = x + dx[i];
int y1 = y + dy[i];
if (a[x1][y1] && !visi[x1][y1]) { //当前块能走且没被走过
visi[x1][y1] = true; //标记
tail++;
Q[tail][0] = x1;
Q[tail][1] = y1;
}
}
}
}
int main() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] && !visi[i][j]) { //遍历每一个块,果当前块为1,且没被走过,则进行一次BFS
++ans;
BFS(i, j);
}
}
}
cout<< "ans = " << ans << endl;
return 0;
}
树
概念


性质

顺序存储
完全二叉树

链表存储
一般二叉树

模拟链表
一般二叉树

先序遍历
中->左->右

中序遍历
左->中->右

后序遍历
左->右->中

注意
已知先序与中序,或中序与后序可以确定唯一的二叉树,但是已知先序与后续不能推出唯一二叉树
创建二叉树

#include<iostream>
using namespace std;
typedef struct Node *BiTree;
struct Node {
char Data;
BiTree left;
BiTree right;
};
BiTree Root; //根节点
char a[20] = "ABC...D.."; //先序遍历数组
int t = -1;
//根据先序遍历创建二叉树
void CreateBiTree(BiTree &BT) {
if(a[++t] != '.') {
BT = new Node;
BT->Data = a[t];
CreateBiTree(BT->left);
CreateBiTree(BT->right);
} else {
BT = NULL;
}
}
void InOrder(BiTree BT) {
if(BT) {
InOrder(BT->left);
cout<<BT->Data<<" ";
InOrder(BT->right);
}
}
void PostOrder(BiTree BT) {
if(BT) {
PostOrder(BT->left);
PostOrder(BT->right);
cout<<BT->Data<<" ";
}
}
int main() {
CreateBiTree(Root);
cout<<"中序遍历:"<<endl;
InOrder(Root);
cout<<endl;
cout<<"后序遍历:"<<endl;
PostOrder(Root);
return 0;
}
查找二叉树


代码

汉诺塔
#include <iostream>
#include <cstdio>
using namespace std;
int cnt;
void move(int id, char from, char to) // 打印移动方式:编号,从哪个盘子移动到哪个盘子
{
printf ("step %d: move %d from %c->%c\n", ++cnt, id, from, to);
}
void hanoi(int n, char x, char y, char z)
{
if (n == 0)
return;
hanoi(n - 1, x, z, y);
move(n, x, z);
hanoi(n - 1, y, x, z);
}
int main()
{
int n;
cnt = 0;
scanf ("%d", &n);
hanoi(n, 'A', 'B', 'C');
return 0;
}
迷宫深搜
#include<iostream>
using namespace std;
char Map1[9][9] = { //地图
{".#..."},
{".#.#."},
{"....."},
{".#.#."}
};
char Map[9][9];
int m = 4, n = 5;
int SouX = 3, SouY = 1; //起点
int DesX = 4, DesY = 5;
int PathX[81], PathY[81]; //记录走过的路径
bool Visited[9][9]; //标记走过得路径
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1}; //上右下左
void ChangeMap() { //给Map周围上一个由0组成的围墙
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
Map[i][j] = Map1[i - 1][j - 1];
}
}
}
void Print(int step) { //输出走过的路径
cout<<"Path: ";
for(int i = 1; i <= step; i++) {
cout<<PathX[i]<<","<<PathY[i]<<" ";
}
cout<<endl;
}
void DFS(int x, int y, int step) {
cout<<x<<", "<<y<<", "<<step<<endl;
PathX[step] = x;
PathY[step] = y;
Visited[x][y] = true; //标记为走过
if(x == DesX && y == DesY) Print(step);
for(int i = 0; i < 4; i++) {
int xx = x + dx[i];
int yy = y + dy[i];
if(Map[xx][yy] == '.' && !Visited[xx][yy]) { //没走过
DFS(xx, yy, step + 1);
Visited[xx][yy] = false; //回溯
}
}
}
int main() {
ChangeMap();
DFS(SouX, SouY, 1);
return 0;
}
层序遍历
从上到下,从左到右


代码

迷宫广搜最短路径


#include<iostream>
using namespace std;
char Map1[9][9] = { //地图
{".#..."},
{".#.#."},
{"....."},
{".#.#."}
};
char Map[9][9];
int m = 4, n = 5;
int SouX = 3, SouY = 1; //起点
int DesX = 4, DesY = 5;
int Pre[81];
int Path[81][2]; //记录走过的路径
int head, tail;
bool flag, Visited[9][9]; //标记走过得路径
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1}; //上右下左
void ChangeMap() { //给Map周围上一个由0组成的围墙
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
Map[i][j] = Map1[i - 1][j - 1];
}
}
}
void Print(int i) { //输出走过的路径
if(Pre[i] != 0) Print(Pre[i]); //递归
cout<<Path[i][0]<<", "<<Path[i][1]<<" ";
}
void BFS() {
head = 0, tail = 1;
Path[tail][0] = SouX;
Path[tail][1] = SouY;
Pre[tail] = head;
Visited[SouX][SouY] = true; //标记为走过
cout<<SouX<<", "<<SouY<<", head = "<<head<<", tail = "<<tail<<endl;
while(head != tail) {
head++;
for(int i = 0; i < 4; i++) {
int x = Path[head][0] + dx[i];
int y = Path[head][1] + dy[i];
if(Map[x][y] == '.' && !Visited[x][y]) { //没走过
tail++;
Path[tail][0] = x;
Path[tail][1] = y;
Pre[tail] = head;
Visited[x][y] = true;
cout<<x<<", "<<y<<", head = "<<head<<", tail = "<<tail<<endl;
if(x == DesX && y == DesY) {
flag = true;
Print(tail);
break;
}
}
}
if(flag) break;
}
}
int main() {
ChangeMap();
BFS();
return 0;
}
深搜广搜模板

堆的插入
完全二叉树

方法1:交换法
先插入堆尾,再记录父子节点信息,当子节点未到达堆顶时,一直比较父子节点信息,父小子大就直接退出,父大子小则交换

方法2:下移法
记录父子节点下标,将每一个父节点与x进行比较,比x大的则下移,直到遇到比x小的父节点或者到达根节点,最后将x插入合适的位置

方法3:哨兵法
在下移法的基础上改进,在head[0]处设置一个哨兵(极小值),重复下移的步骤

完整代码

#include<iostream>
using namespace std;
int a[7] = {3, 5, 1, 7, 6, 4, 2};
int head[100];
int len = 0;
void Insert(int x) {
int pa = 0, son = 0;
head[0] = -10000; //哨兵
son = ++len;
pa = son / 2;
while(x < head[pa]) {
head[son] = head[pa];
son = pa;
pa = son / 2;
}
head[son] = x;
}
int main() {
for(int i = 0; i < 7; i++) { //依次插入堆
Insert(a[i]);
}
for(int i = 1; i <= 7; i++) {
cout<<head[i]<<" ";
}
return 0;
}
堆的删除
方法1:交换法
将根节删除,将尾元素移到根节点并删除,再利用下沉法将大的值移下去,将小的值移上来

方法2:上移法

堆排序
完整代码,也是堆排序,因为每次返回的都是堆顶最小元素

总结


图
概念



邻接矩阵

#include<iostream>
using namespace std;
int G[101][101];
int main() {
int i, j, k, w;
int N, M; //顶点数和边数
const int oo = 0x7f; //一个很大的数 127??感觉也不大
printf("输入顶点数和边数\n");
cin>>N>>M;
//初始化边权
for(i = 1; i <= N; i++) {
for(j = 1; j <= N; j++) {
G[i][j] = oo;
}
}
printf("输入%d条边\n", M);
for(k = 1; k <= M; k++) {
cin>>i>>j>>w; //读入顶点和边权
G[i][j] = w;
G[j][i] = w;
}
printf("\n输出顶点和边权\n");
for(i = 1; i <= N; i++)
for(j = 1; j <= N; j++)
if(G[i][j] < oo) printf("%d %d %d\n", i, j, G[i][j]);
return 0;
}


邻接表
指针数组next[num]更好的表示应该叫做pre[num],其作用只是连接每一个节点的所有出边


#include<iostream>
using namespace std;
int num = 0; //编号
int head[21]; //
struct node {
int to; //当前边终点
int w; //当前边权值
int next; //存储的上一条边编号
} p[51];
void AddEdge(int v1, int v2, int w) {
num++;
p[num].next = head[v1];
p[num].to = v2;
p[num].w = w;
head[v1] = num;
printf("<%d %d %2d> %d nx[%d]=%d hd[%d]=%d\n",
v1, v2, w, num, num, p[num].next, v1, head[v1]);
}
int main() {
int N = 4, M = 6; //顶点数和边数
int a[11][11] = {
{1, 2, 10},
{1, 3, 7},
{3, 4, 9},
{2, 3, 8},
{2, 4, 12},
{1, 4, 11}
};
printf("邻接表存储\n");
printf("v1 v2 w num next head\n");
for(int i = 0; i < M; i++) AddEdge(a[i][0], a[i][1], a[i][2]);
printf("\n访问起点为1的所有边\n");
for(int i = head[1]; i != 0; i = p[i].next) {
printf("%d %d %d\n", 1, p[i].to, p[i].w);
}
return 0;
}

邻接点


#include<iostream>
using namespace std;
int num[31]; //邻接点个数
int A[31][31]; //邻接点
int W[51][51]; //边权
int main() {
int i, j, x, y;
int N = 4, M = 6; //顶点数和边数
int a[11][11] = {
{1, 2, 10},
{1, 4, 11},
{3, 4, 9},
{2, 3, 8},
{2, 4, 12},
{1, 3, 7}
};
printf("邻接点存储\n");
for(int i = 0; i < M; i++) {
x = a[i][0];
y = a[i][1];
A[x][++num[x]] = y;
W[x][y] = a[i][2];
printf("num[%d]=%d A[%d][%d]=%d\n",
x, num[x], x, num[x], y);
}
printf("\n输出所有边\n");
for(i = 1; i <= N; i++) {
for(j = 1; j <= num[i]; j++) {
printf("%d %d %d\n",
i, A[i][j], W[i][A[i][j]]);
}
}
return 0;
}

边集数组

欧拉路与欧拉回路
深搜


广搜


哈密尔顿回路



无权最短路
广搜


有权最短路

| 算法复杂度 | 能否处理负边权 | |
|---|---|---|
| Floyd | O(N3) | 能 |
| Dijkstra | O(N2) | 不能 |
| Ford | O(NM):N是点数,M是边数 | 能 |
| SPFA | O(NM):k是常数,M是边数 | 能 |
Floyd(弗洛伊德算法)


#include<iostream>
using namespace std;
int W[51][51]; //边权
int main() {
int i, j, k, x, y;
int N = 5, M = 6; //顶点数和边数
int a[9][3] = {
{1, 2, 30},
{1, 3, 10},
{2, 4, 20},
{2, 5, 25},
{3, 4, 10},
{4, 5, 10},
};
//初始化W
for(i = 1; i <= N; i++) {
for(j = 1; j <=N; j++) {
//初始化一个极大值
if(i != j) W[i][j] = 10000;
}
}
for(i = 1; i <= M; i++) {
x = a[i - 1][0];
y = a[i - 1][1];
W[x][y] = W[y][x] = a[i - 1][2];
}
//求最短路径
for(i = 1; i <= N; i++) //起点
for(j = 1; j <= N; j++) //终点
for(k = 1; k <= N; k++) //中间点
if(W[i][k] + W[k][j] < W[i][j])
W[i][j] = W[i][k] + W[k][j];
cout<<W[1][5]<<endl;
return 0;
}
Dijkstra(戴克斯特拉算法)


#include<iostream>
using namespace std;
int W[51][51]; //边权
int D[51]; //记录源点s到i的距离
int pre[51]; //前驱节点,用于记录走过的位置
bool B[51]; //蓝白点标记,false表示蓝点,须依次将蓝点转化为true即白点
//输出s到i的路径
void Print(int i) {
if(pre[i] != 0) Print(pre[i]);
cout<<i<<" ";
}
int main() {
//s表示起点
int i, j, k, x, y, minn, oo = 10000, s = 1;
int N = 5, M = 7; //顶点数和边数
int a[9][3] = {
{1, 2, 2},
{1, 3, 5},
{1, 4, 2},
{2, 3, 3},
{2, 5, 6},
{3, 4, 1},
{3, 5, 2}
};
//初始化
for(i = 1; i <= N; i++) {
for(j = 1; j <= N; j++) {
W[i][j] = oo;
}
}
for(i = 1; i <= M; i++) {
x = a[i - 1][0];
y = a[i - 1][1];
W[x][y] = W[y][x] = a[i - 1][2];
}
for(i = 1; i <= N; i++) {
D[i] = oo;
pre[i] = 0;
}
D[s] = 0;
//算法实现
for(i = 1; i <= N; i++) {
minn = oo, k = 0;
for(j = 1; j <= N; j++) {
if(!B[j] && D[j] < minn) {
minn = D[j];
k = j;
}
}
B[k] = true; //洗白蓝点
// 更新距离
for(j = 1; j <= N; j++) {
if(D[k] + W[k][j] < D[j]) {
D[j] = D[k] + W[k][j];
pre[j] = k;
}
}
}
//输出
cout<<D[5]<<endl;
Print(5);
return 0;
}
Ford(福特算法)


#include<iostream>
using namespace std;
int W[51]; //边权
int v1[51], v2[51]; //端点
int D[51]; //记录源点s到i的距离
int pre[51]; //前驱节点,用于记录走过的位置
//输出s到i的路径
void Print(int i) {
if(pre[i] != 0) Print(pre[i]);
cout<<i<<" ";
}
int main() {
//s表示起点
int i, j, u, v, x, y, minn, oo = 10000, s = 1;
int N = 4, M = 5; //顶点数和边数
int a[9][3] = {
{3, 4, 1},
{2, 4, 5},
{3, 2, 3},
{3, 1, 7},
{2, 1, 1}
};
for(i = 1; i <= M; i++) {
v1[i] = a[i - 1][0];
v2[i] = a[i - 1][1];
W[i] = a[i - 1][2];
}
for(i = 1; i <= N; i++) {
D[i] = oo;
}
D[s] = 0;
pre[s] = 0;
//算法实现
for(i = 1; i <= N - 1; i++) {
for(j = 1; j <= M; j++) {
u = v1[j], v = v2[j];
if(D[u] + W[j] < D[v]) {
D[v] = D[u] + W[j];
pre[v] = u;
}
if(D[v] + W[j] < D[u]) {
D[u] = D[v] + W[j];
pre[u] = v;
}
}
}
//输出
cout<<D[4]<<endl;
Print(4);
return 0;
}
SPFA算法
--SPFA 算法是Bellman-Ford算法的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素Bellman-Ford相同,为O(NM)


#include<iostream>
#include<queue>
using namespace std;
int A[51][51], num[51];
int W[51][51]; //边权
queue<int> q;
bool exist[51]; //记录点是否在队列中
int D[51]; //记录源点s到i的距离
int pre[51]; //前驱节点,用于记录走过的位置
//输出s到i的路径
void Print(int i) {
if(pre[i] != 0) Print(pre[i]);
cout<<i<<" ";
}
int main() {
//s表示起点
int i, j, u, v, x, y, minn, oo = 10000, s = 1;
int N = 4, M = 5; //顶点数和边数
int a[9][3] = {
{3, 4, 1},
{2, 4, 5},
{3, 2, 3},
{3, 1, 7},
{2, 1, 1}
};
//初始化
for(i = 1; i <= N; i++) {
for(j = 1; j <= N; j++) {
W[i][j] = oo;
}
}
for(i = 1; i <= M; i++) {
u = a[i - 1][0];
v = a[i - 1][1];
A[u][++num[u]] = v; //记录每一个点的邻接点
A[v][++num[v]] = u;
W[u][v] = W[v][u] = a[i - 1][2];
}
for(i = 1; i <= N; i++) {
D[i] = oo;
}
// memset(D, oo, sizeof(D));
D[s] = 0;
pre[s] = 0;
//算法实现
q.push(1);
exist[1] = true;
while(!q.empty()) {
u = q.front(); //将u作起点
q.pop();
exist[u] = false;
for(i = 1; i <= num[u]; i++) {
v = A[u][i]; //u的邻接点
if(D[u] + W[u][v] < D[v]) {
D[v] = D[u] + W[u][v];
pre[v] = u;
if(!exist[v]) {
q.push(v);
exist[v] = true;
}
}
}
}
//输出
cout<<D[4]<<endl;
Print(4);
return 0;
}
最小环
断边法

Floyd算法


并查集
两种优化方案:路径压缩 + 按秩合并,一般只选取其中一种即可,路径压缩较为简洁。
路径压缩
int Find(int x) {
if(parent[x] != x) parent[x] = Find(parent[x]);
return parent[x];
}
按秩合并

void Union(int x, int y) {
x = Find(x);
y = Find(y);
if(x == y) return ;
else {
if(Rank[x] < Rank[y]) parent[x] = y;
else {
parent[y] = x;
if(Rank[x] == Rank[y]) Rank[x]++;
}
}
}
最小生成树
用于解决N - 1条边连接N个点
Kruskal(克鲁斯卡尔)算法


#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 2e4 + 10;
int n, m;
int father[maxn];
struct node {
int u; //路的一端
int v; //路的另一端
int w; //路的拥挤度
} a[maxn];
bool cmp(node a, node b) { //从小到大排序
return a.w < b.w;
}
void init(int n) {
for (int i = 1; i <= n; i++) {
father[i] = i;
}
}
int findFather(int x) {
while (x != father[x]) {
x = father[x];
}
return x;
}
void unit(int a, int b) {
int fa = findFather(a);
int fb = findFather(b);
if (fa != fb) {
father[fa] = fb;
}
}
int kruskal(int n, int m) {
int ans = 0, cnt = 0;
sort(a + 1, a + m + 1, cmp); //按边的权值从小到大排序
for (int i = 1; i <= m; i++) { //遍历m条路
int u = a[i].u;
int v = a[i].v;
int w = a[i].w;
int fa = findFather(u);
int fb = findFather(v);
if (fa != fb) { //两条路没有连通
father[fa] = fb;
ans += w;
cnt++; //边数加1,最小生成树的最多边数应该为n - 1
if(cnt == n - 1) break;
}
}
if(cnt != n - 1) return -1;
else return ans;
}
int main() {
cin >> n >> m ;
init(n); //n个小区,m条路
for (int i = 1; i <= m; i++) {
cin >> a[i].u >> a[i].v >> a[i].w;
}
int ans = kruskal(n, m);
cout<<ans<<endl;
return 0;
}
Prim(普里姆)算法


AOV网与拓扑排序

序列不唯一
卡恩(Kahn)算法

#include<bits/stdc++.h>
using namespace std;
int mm[110][110]; //邻接点
int cd[110], rd[110]; //出度,入度
stack<int> s;
int topo[110], t, num;
int main() {
int i, j, k;
int n = 7, m = 9;
int a[10][3] = {{1, 2}, {2, 4}, {2, 7},
{3, 2}, {3, 4}, {3, 5},
{4, 6}, {4, 7}, {5, 6}};
//初始化
for (i = 1; i <= m; i++) {
j = a[i - 1][0];
k = a[i - 1][1];
cd[j]++;
mm[j][cd[j]] = k;
rd[k]++;
}
//入度为0的点入栈
for (int i = 1; i <= n; i++) {
if (rd[i] == 0) {
s.push(i);
}
}
do {
i = s.top();
s.pop(); //将第一个入度0的点出栈
num++;
topo[++t] = i;
//遍历i的出边
for (j = 1; j <= cd[i]; j++) {
k = mm[i][j]; //取出邻接点
rd[k]--; //出度点i消去,i的邻接点入度-1
if (rd[k] == 0) {
s.push(k);
}
}
} while(num != n);
//输出排序结果
for (i = 1; i <= t; i++) {
cout << topo[i] << " ";
}
return 0;
}
dfs算法

#include<bits/stdc++.h>
using namespace std;
int mm[110][110]; //邻接点
int cd[110]; //出度
bool vis[110];
int topo[110], t;
void dfs(int v) {
//从1开始,遍历所有出边
for (int i = 1; i <= cd[v]; i++) {
int k = mm[v][i];
if (!vis[k]) {
vis[k] = true;
dfs(k);
}
}
topo[++t] = v;
}
int main() {
int i, j, k;
int n = 7, m = 9;
int a[10][3] = {{1, 2}, {2, 4}, {2, 7},
{3, 2}, {3, 4}, {3, 5},
{4, 6}, {4, 7}, {5, 6}};
//初始化
for (i = 1; i <= m; i++) {
j = a[i - 1][0];
k = a[i - 1][1];
cd[j]++;
mm[j][cd[j]] = k;
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
vis[i] = true;
dfs(i);
}
}
//输出排序结果
for (i = t; i >= 1; i--) {
cout << topo[i] << " ";
}
return 0;
}
//3 5 1 2 4 7 6
AOE网与关键路径





BFS

#include<bits/stdc++.h>
using namespace std;
int mm[110][110], num[110]; //邻接点
queue<int> s;
int vis[110];
int main() {
int i, j, k, u, v;
int n = 9, m = 12; //顶点,边数
int a[15][3] = {{1, 2}, {2, 3}, {3, 1},
{1, 4}, {4, 5}, {5, 6},
{5, 7}, {7, 6}, {6, 4},
{5, 8}, {8, 9}, {9, 8}};
//初始化
for (i = 1; i <= m; i++) {
j = a[i - 1][0];
k = a[i - 1][1];
mm[j][++num[j]] = k;
}
s.push(1); //顶点1入队
vis[1] = 1;
cout << 1 <<" ";
while(!s.empty()) {
u = s.front();
s.pop();
for (j = 1; j <= num[u]; j++) {
v = mm[u][j];
if (vis[v] == 0) {
vis[v] = 1;
s.push(v);
cout << v << " ";
}
}
}
return 0;
}
//1 2 4 3 5 6 7 8 9
DFS

#include<bits/stdc++.h>
using namespace std;
int mm[110][110]; //邻接点
int vis[110];
void dfs(int u) {
int i, v;
for (i = 1; i <= mm[u][0]; i++) {
v = mm[u][i];
if (!vis[v]) {
vis[v] = 1;
dfs(v);
}
}
cout << u << " ";
}
int main() {
int i, a, b;
int n = 9, m = 12; //顶点,边数
int E[15][3] = {{1, 2}, {2, 3}, {3, 1},
{1, 4}, {4, 5}, {5, 6},
{5, 7}, {7, 6}, {6, 4},
{5, 8}, {8, 9}, {9, 8}};
//初始化
for (i = 0; i < m; i++) {
a = E[i][0];
b = E[i][1];
mm[a][++mm[a][0]] = b;
}
cout << "后序输出:";
for (i = 1; i <= n; i++) {
if (!vis[i]) {
vis[i] = 1;
dfs(i);
}
}
return 0;
}
//后序输出:3 2 6 7 9 8 5 4 1
动态规划
最长上升子序列

f[i] = max(f[i], f[j] + 1)
#include<bits/stdc++.h>
using namespace std;
int ans, n = 9;
int a[110] = {0, 5, 7, 1, 9, 4, 6, 2, 8, 3};
int dp[110];
int main() {
for (int i = 1; i <= n; i++) dp[i] = 1;
for (int i = 2; i <= n; i++) { //[2, n]
for (int j = 1; j < i; j++) { //[1, i)
if (a[i] > a[j]) { //后面的数比前面大
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]); //更新最大长度
}
cout << ans <<endl;
return 0;
}
//
二分查找版
时间复杂度:O(nlogn)
#include<bits/stdc++.h>
using namespace std;
int ans, len, n = 9;
int a[110] = {0, 5, 7, 1, 9, 4, 6, 2, 8, 3};
int b[110]; //记录一组有序子序列,不是最长上升子序列
//查找第一个大于等于x的位置
int find(int x) {
int l = 1, r = len, mid;
while(l <= r) {
mid = (l + r) / 2;
if (x > b[mid]) l = mid + 1;
else r = mid - 1;
}
return l;
}
int main() {
len = 1;
b[1] = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] > b[len]) { //大于b数组末尾的数则直接添加
b[++len] = a[i];
} else {
int j = find(a[i]);
b[j] = a[i];
}
}
for (int i = 1; i <= len; i++) {
cout << b[i] << " ";
}
putchar('\n');
cout << len << endl;
return 0;
}
最长不下降子序列
只改变一个符号,将>改为>=
#include<bits/stdc++.h>
using namespace std;
int ans, n = 9;
int a[110] = {0, 5, 7, 1, 9, 4, 6, 2, 8, 3};
int dp[110];
int main() {
for (int i = 1; i <= n; i++) dp[i] = 1;
for (int i = 2; i <= n; i++) { //[2, n]
for (int j = 1; j < i; j++) { //[1, i)
if (a[i] >= a[j]) { //后面的数不小于前面
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]); //更新最大长度
}
cout << ans <<endl;
return 0;
}
二分查找版
#include<bits/stdc++.h>
using namespace std;
int ans, len, n = 9;
int a[110] = {0, 5, 7, 1, 9, 4, 6, 2, 8, 3};
int b[110]; //记录一组有序子序列,不是最长上升子序列
//查找第一个大于x的位置,与最长上升子序列条件不同
int find(int x) {
int l = 1, r = len, mid;
while(l <= r) {
mid = (l + r) / 2;
if (x >= b[mid]) l = mid + 1;
else r = mid - 1;
}
return l;
}
int main() {
len = 1;
b[1] = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] >= b[len]) { //大于等于b数组末尾的数则直接添加
b[++len] = a[i];
} else {
int j = find(a[i]);
b[j] = a[i];
}
}
for (int i = 1; i <= len; i++) {
cout << b[i] << " ";
}
putchar('\n');
cout << len << endl;
return 0;
}
最长上升子序列与不下降子序列的输出
最长上升子序列
#include<bits/stdc++.h>
using namespace std;
int ans, n = 9;
int a[110] = {0, 5, 7, 1, 9, 4, 6, 2, 8, 3};
int dp[110], p[110]; //p数组记录前驱节点
//根据最长子序列位置,递归输出前驱节点
void _print(int x) {
if (p[x]) _print(p[x]);
cout << a[x] << " ";
}
int main() {
int i, j, mx, ans = 1;
for (i = 1; i <= n; i++) dp[i] = 1;
for (i = 2; i <= n; i++) {
for (j = 1; j < i; j++) {
if (a[i] > a[j] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
p[i] = j; //记录前驱
}
}
if (dp[i] > ans) {
ans = dp[i];
mx = i; //记录更新最长位置
}
}
_print(mx);
return 0;
}
//1 4 6 8
最长公共子序列

#include<bits/stdc++.h>
using namespace std;
char a[200] = "ADABBC";
char b[200] = "DBDCA";
int f[210][210], p[210][210]; //长度数组,前驱数组
int m, n;
//获取公共子序列长度
void LCS() {
int i, j;
m = strlen(a);
n = strlen(b);
for (i = 1; i <= m; i++) {
for (j = 1; j <= n; j++) {
if (a[i - 1] == b[j - 1]) {
f[i][j] = f[i - 1][j - 1] + 1;
p[i][j] = 1;
} else if(f[i][j - 1] > f[i - 1][j]) {
f[i][j] = f[i][j - 1];
p[i][j] = 2;
} else {
f[i][j] = f[i - 1][j];
p[i][j] = 3;
}
}
}
cout << f[m][n] <<endl;
}
//输出公共子序列
void getLCS() {
int i, j, k;
char s[200];
i = m, j = n;
k = f[m][n];
while(i > 0 && j > 0) {
if (p[i][j] == 1) {
s[k--] = a[i - 1];
i--, j--;
} else if (p[i][j] == 2) {
j--;
} else {
i--;
}
}
for (i = 1; i <= f[m][n]; i++) {
cout << s[i] << " ";
}
}
int main() {
LCS();
getLCS();
return 0;
}
最长公共子串(连续)

#include<bits/stdc++.h>
using namespace std;
char a[200] = "AACCAB";
char b[200] = "ABACCB";
int f[210][210]; //长度数组
int m, n, x, y;
int mmax = INT_MIN;
//获取公共子串长度
void LCS() {
m = strlen(a);
n = strlen(b);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (a[i - 1] == b[j - 1]) {
f[i][j] = f[i - 1][j - 1] + 1;
} else f[i][j] = 0;
if (f[i][j] > mmax) {
mmax = f[i][j];
x = i, y = j;
}
}
}
cout << mmax << endl;
}
//输出公共子串
void getLCS() {
char s[200];
int ind = mmax;
while (f[x][y] > 0) {
s[ind--] = a[x - 1];
x--, y--;
}
for (int i = 1; i <= mmax; i++) {
cout << s[i];
}
}
int main() {
LCS();
getLCS();
return 0;
}

浙公网安备 33010602011771号