董晓算法笔记

链表

单链表

#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;	//闭环
}
约瑟夫环

image-20220616131658236

循环链表

#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]的后继元素的下标,相当于链表中的指针

image-20220618103906964

#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;
}

进制转换

image-20220618111844845

#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;
}

括号匹配

image-20220619105802926

#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;
}

后缀表达式

image-20220619111936593

#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;
}

车厢调度

image-20220619114931916

image-20220619115328497

车厢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;
}

队列

image-20220624132032025

#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;
	}
}

循环队列

image-20220624133108830

#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数集

image-20220624140710928

思路:一个数组,定义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;
}

连通块问题

image-20220624144524208

广度优先搜索

#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;
}

概念

image-20220629215135888

image-20220629215212513

性质

image-20220701153149046

顺序存储

完全二叉树

image-20220701153621761

链表存储

一般二叉树

image-20220701154036566

模拟链表

一般二叉树

image-20220701154326183

先序遍历

中->左->右

image-20220701154744608

中序遍历

左->中->右

image-20220701154849260

后序遍历

左->右->中

image-20220701155036096

注意

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

创建二叉树

image-20220701160437480

#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;
} 

查找二叉树

image-20220701173852216

image-20220701174041265

代码

image-20220701175823838

汉诺塔

#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;
}

层序遍历

从上到下,从左到右

image-20220701191959364

image-20220701192259137

代码

image-20220701192405517

迷宫广搜最短路径

image-20220701192632390

image-20220701204055125

#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;
}

深搜广搜模板

image-20220703103217901

堆的插入

完全二叉树

image-20220703103704972

方法1:交换法

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

image-20220703104306857

方法2:下移法

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

image-20220703104742962

方法3:哨兵法

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

image-20220703110011585

完整代码

image-20220703110451448

#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:交换法

将根节删除,将尾元素移到根节点并删除,再利用下沉法将大的值移下去,将小的值移上来

image-20220703112603091

方法2:上移法

image-20220703113157396

堆排序

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

image-20220703113242528

总结

image-20220703113751263

image-20220703113807359

概念

image-20220712111535235

image-20220712111517882

image-20220712111426886

邻接矩阵

image-20220712105701003

#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;
}

image-20220712111156596image-20220712111050963

邻接表

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

image-20220712105508412

image-20220712112458090

#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;
}

image-20220712114223555

邻接点

image-20220712114708428

image-20220712114933551

#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;
}

image-20220712115840369

边集数组

image-20220712120012150

欧拉路与欧拉回路

深搜

image-20220713100926361

image-20220713102122436

广搜

image-20220713104111130

image-20220713104233922

哈密尔顿回路

image-20220713102702624

image-20220713103559834

image-20220713103504129

无权最短路

广搜

image-20220713104605206

image-20220713104622590

有权最短路

image-20220716090822490

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

Floyd(弗洛伊德算法)

image-20220716091926918

image-20220716091951694

#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(戴克斯特拉算法)

image-20220716094517032

image-20220716095233016

#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(福特算法)

image-20220716103328656

image-20220716103946157

#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)

image-20220717094410795

image-20220717094537156

#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;
}

最小环

断边法

image-20220717103156188

Floyd算法

image-20220717105635427

image-20220717105156180

并查集

两种优化方案:路径压缩 + 按秩合并,一般只选取其中一种即可,路径压缩较为简洁。

路径压缩

int Find(int x) {
	if(parent[x] != x) parent[x] = Find(parent[x]);
	return parent[x];
}

按秩合并

image-20220717110443221

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(克鲁斯卡尔)算法

image-20220806204246473

image-20220806204632083

#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(普里姆)算法

image-20220806205732803

image-20220806205833760

AOV网与拓扑排序

image-20220808202542340

序列不唯一

卡恩(Kahn)算法

image-20220808202808910

#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算法

image-20220808205838268

#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网与关键路径

image-20220808211318669

image-20220808211455048

image-20220808211520819

image-20220808211542342

image-20220808211603852

BFS

image-20220808213014284

#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

image-20220808213208250

#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

动态规划

最长上升子序列

image-20220809093837628

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

最长公共子序列

image-20220809112831351

#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;
}

最长公共子串(连续)

image-20220809115008706

#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;
}
posted @ 2024-03-13 18:16  Liu-RC  阅读(235)  评论(0)    收藏  举报