Day23:算法之分支定界

一、分支定界思想

分支定界思想:一般用来解决寻路问题(最短路径)   广度寻路  A星寻路
    ①常用广度优先或者以最小耗费(最大收益)优先的方式搜索问题的解空间树。
        
    ②在分支定界算法思想中,

        每一个活节点(坐标节点)只有一次机会成为拓展节点
              一旦成为拓展节点,就一次性生成其所有孩子节点。
            在这些孩子节点中,导致不可行解或者非最优解的孩子节点被舍弃(剪枝)
            其余孩子节点被加入活节点表中(存放,记录,保存)
         此后,从活节点表中获取下一节点成为当前拓展节点,并重复上述节点拓展过程。
    这个过程一直到找到需要的解(找到终点)或者活节点表(buff)为空。

    ③分支定界法常用的两个数据结构:
            1. 队列式分支定界法
                按照队列原则选取下一个节点成为拓展节点。(广度寻路)
            2. 优先队列式分支定界法
                按照优先队列中规定的优先级选取优先级最高的节点成为当前拓展节点(A星寻路)

二、案例一:爬虫

算法流程:

        给一个网址 
            解析网址:
            连上网页的服务器
            发送请求到网页服务器,获取网页源码
            源码中就有很多的图片链接和网址链接
            把网址链接存到队列中
            循环从队列中一个个取出 取出后

/*
	爬虫原理:
		要求用户输入一个网址
		获取到这个网址代表的网页的源代码
		分析源代码:
			得到    网址
			得到    网络图片地址
		网络图片地址  --> 下载图片到本地
		循环连新的网址

		去重

		http协议:客户端连接网页服务器 
			客户端发送信息给网页服务器,服务器收到后 反馈 传输完数据
			断开连接

		基于tcp协议

		Get  Post  

		服务器的ip地址和服务器的端口号
		www.baidu.com
		http://www.netbian.com/s/meinvmm/
		http://					//前缀  说明了  协议类型	
		www.netbian.com			//域名
		/s/meinvmm/				//路径
*/
#include <regex> //正则表达式头文件
#include <fstream> //文件操作头文件
#include <iostream>
#include <string>
#include <queue>//队列容器
#include <map>//解决网址重复问题
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")//加载动态库   把源代码弄过来
#include <windows.h>

using namespace std;
//网址       数据
map<string, int> g_map;//解决网址重复问题
//存放主机名
char g_zhuji[256] = { 0 };
//存放主机后的路径
char g_path[256] = { 0 };
//服务器socket
SOCKET g_socket;
//存放图片地址的容器
vector<string> photoAddr;
//存放网页地址的容器
vector<string> htmlAddr;

//爬jpg图片
void snapJpg(const char* addr);
//解析网址
void jiexiAddr(char* buff);
//连接服务器
void connectAddr();
//得到源代码
void getHtml();
//下载图片
void saveImage(string str);
//从网页源代码中获取图片链接
void getImage(string& allHtml);
//从网页源代码中获取网址链接
void getAddr(string& allHtml);
int main(){
	string begAddr;
	cout << "请输入起始网址:";
	cin >> begAddr;

	//创建一个文件夹
	CreateDirectory(".\\images", NULL);

	snapJpg(begAddr.c_str());



	while (1);
	return 0;
}
//爬jpg图片
void snapJpg(const char* addr){
	queue<string> q;//存网址
	q.push(addr);//把起始网址放进去

	while (!q.empty()){
		//从q中拿出一个 
		string currentAddr = q.front();
		//删掉
		q.pop();
		//解决网址重复问题
		g_map[currentAddr]++;
		//解析网址 拿到域名
		char buff[256] = { 0 };
		strcpy(buff, currentAddr.c_str());
		//解析网址
		jiexiAddr(buff);
		//连接服务器
		connectAddr();
		//得到源代码
		getHtml();
		//从源代码中解析出网址和图片地址 存入对应容器中
		//把网址存放到队列q中
		vector<string>::iterator it;
		for (it = htmlAddr.begin(); it != htmlAddr.end(); it++){
			if (0 == g_map[*it]){//没有连接过
				q.push(*it);//放到队列中
			}
		}
		htmlAddr.clear();//清空容器
		//下载图片
		for (it = photoAddr.begin(); it != photoAddr.end(); it++){
			saveImage(*it);
		}
		photoAddr.clear();//清空容器
	}
}


//解析网址
void jiexiAddr(char* buff){
	char temp[256] = { 0 };
	strcpy(temp, buff);

	//略过前面七个 http://
	char* p = strstr(buff,"http://");//buff中找子串 "http://" 找到返回子串首地址
	if (NULL == p) return;
	else
		p += 7;//往后挪七个

	sscanf(p, "%[^/]%s", g_zhuji, g_path);
	printf("主机:%s\n", g_zhuji);
	printf("路径:%s\n", g_path);
}
//连接服务器
void connectAddr(){
	//1 获取协议版本号
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2){
		printf("请求版本号失败!\n");
		return;
	}
	printf("请求版本号成功!\n");
	//2 创建socket
	g_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (SOCKET_ERROR == g_socket){
		printf("创建socket失败!\n");
		return;
	}
	printf("创建socket成功!\n");
	//3 创建协议地址族
	SOCKADDR_IN addr = { 0 };
	addr.sin_family = AF_INET; //必须和socket函数第一个参数一致
	//4 绑定
	int r = bind(g_socket, (sockaddr*)&addr, sizeof addr);
	if (-1 == r){
		printf("绑定失败!\n");
		return;
	}
	printf("绑定成功!\n");
	//5 拿到主机ip地址
	struct hostent* p = gethostbyname(g_zhuji);//192.168.0.44    ipv4
	if (NULL == p){
		printf("获取主机地址失败!\n");
		return;
	}
	printf("获取主机地址成功!\n");

	memcpy(&addr.sin_addr, p->h_addr, 4);	//把主机地址放入协议地址族中
	addr.sin_port = htons(80);				//设置主机端口号   浏览器端口号一般为80

	//6 连接服务器
	r = connect(g_socket, (sockaddr*)&addr, sizeof addr);
	if (-1 == r){
		printf("连接服务器失败!\n");
		return;
	}
	printf("连接服务器成功!\n");
	//7 通信:发送获取源代码请求

	//请求信息
	string reqInfo = "GET " + (string)g_path + " HTTP/1.1\r\nHost:" +
		(string)g_zhuji + "\r\nConnection:Close\r\n\r\n";
	//发送请求信息到服务器
	r = send(g_socket, reqInfo.c_str(), reqInfo.size(), NULL);
	if (r > 0){
		printf("发送请求信息成功!\n");
	}
	else{
		printf("发送请求信息失败,失败原因:%d\n", WSAGetLastError());
	}

}
//得到源代码
void getHtml(){
	string allHtml;

	char buff[1024];
	int r;
	while (1){
		r = recv(g_socket, buff, 1023, NULL);
		if (r > 0){
			buff[r] = 0;
			allHtml += buff;
		}
		else{
			break;
		}
	}

	//cout << allHtml << endl;

	//从网页源代码中获取图片链接
	getImage(allHtml);
	//从网页源代码中获取网址链接
	getAddr(allHtml);
}
//下载图片
void saveImage(string str){
	//1 解析图片地址
	char buff[256];
	memset(buff, 0, 256);
	strcpy(buff, str.c_str());

	jiexiAddr(buff);
	//2 连接服务器
	//3 发送下载图片请求
	connectAddr();
	//4 本地创建图片文件
	//4.1 得到图片文件名
	string photoName;
	photoName.resize(str.size());

	char ch;
	int j = 0;
	for (int i = 0; i < str.length(); i++){
		ch = str[i];
		// '\0'  '\n' '\t' '\r' '\\' '\"'
		if (ch != '\\' && ch != '/' && ch != ':' && ch != '*' && ch != '?' &&
			ch != '"' && ch != '<' && ch != '>' && ch != '|'){
			photoName[j++] = ch;
		}
	}

	photoName = "./images/" + photoName.substr(0, j);

	//4.2 创建图片文件
	fstream file;
	file.open(photoName, ios::out | ios::binary);//二进制写

	//5 接收发送过来的图片信息并写入图片文件中
	int r;
	char tempBuff[1024] = { 0 };

	//排除掉文件头的"\r\n\r\n"
	r = recv(g_socket, tempBuff, 1023, NULL);
	char* p = strstr(tempBuff, "\r\n\r\n");
	file.write(p + strlen("\r\n\r\n"), r - (p - tempBuff) - strlen("\r\n\r\n"));

	while (1){
		r = recv(g_socket, tempBuff, 1023, NULL);
		if (r > 0){
			file.write(tempBuff, r);
		}
		else{
			break;
		}

	}
	//6 保存关闭
	file.close();

}

//从网页源代码中获取图片链接
void getImage(string& allHtml){
	smatch mat;//用作匹配的对象
	regex pattren("src=\"(.*?\\.jpg)\"");
	string::const_iterator start = allHtml.begin();//起始位置
	string::const_iterator end = allHtml.end();//结束位置

	while (regex_search(start, end, mat, pattren)){
		string msg(mat[1].first, mat[1].second);
		photoAddr.push_back(msg);
		cout << "找到图片:" << msg << endl;
		start = mat[0].second;//改变起始位置
	}
}
//从网页源代码中获取网址链接
void getAddr(string& allHtml){
	smatch mat;//用作匹配的对象
	regex pattren("href=\"(http://[^\\s,\"]+)\"");
	string::const_iterator start = allHtml.begin();//起始位置
	string::const_iterator end = allHtml.end();//结束位置

	while (regex_search(start, end, mat, pattren)){
		string msg(mat[1].first, mat[1].second);
		htmlAddr.push_back(msg);
		cout << "找到网址:" << msg << endl;
		start = mat[0].second;//改变起始位置
	}
}

 三、案例二:图的A*寻路算法

#include <iostream>
#include <vector>
#include <limits>//numeric_limits
#include <queue>
using namespace std;

//节点类
class Node {
public://属性  一般应该是 private 或者 protected
	int		index;		//序号
	int		weight;		//权重
public://功能
	//构造
	Node(int index = 0, int weight = 0) :index(index), weight(weight) {}
	//拷贝构造
	Node(const Node& object) :index(object.index), weight(object.weight) {}
	//重载小于运算符   为了比较  剪枝
	friend bool operator<(const Node& one, const Node& two) {
		return (one.weight > two.weight);
	}
};



//路径类   节点类是边  路径类其实是很多节点相加
class Path {
public://属性  一般应该是 private 或者 protected
	int		index;		//序号
	int		weight;		//权重
public://功能
	Path() :index(0), weight(numeric_limits<int>::max()) {}
};

class shortTestPath {
public://属性
	vector<vector<int>>		graph;		//图
	int						nodeCount;	//节点统计
	const int				edge;		//起始
	const int				end;		//结束
	vector<int>				pathIndex;	//存储最短路径的容器
	int						shortPath;	//最短路径值
public://功能
	//构造函数
	shortTestPath(const vector<vector<int>>& object, int end) :edge(-1), end(end),
		nodeCount(object.size()), graph(object) {}
	//打印
	void printPath() {
		cout << "最短路径值:" << shortPath << endl;
		cout << "路径:";
		//反向打印
		copy(pathIndex.rbegin(), pathIndex.rend(),
			ostream_iterator<int>(cout, " "));
		cout << endl;
	}
	void getShortTestPath() {
		vector<Path> myPath(nodeCount);//初始化路径容器大小
		priority_queue<Node, vector<Node>> minHeap;//准备一个优先队列(小顶堆)
		minHeap.push(Node(0, 0));//入口入堆

		while (1) {
			Node top = minHeap.top();
			minHeap.pop();

			//如果是终点,结束循环
			if (top.index == end) break;

			for (int i = 0; i < nodeCount; i++) {//剪枝过程
				if (graph[top.index][i] != edge &&
					top.weight + graph[top.index][i] < myPath[i].weight) {
					minHeap.push(Node(i, top.weight + graph[top.index][i]));
					myPath[i].index = top.index;
					myPath[i].weight = top.weight + graph[top.index][i];
				}
			}

			//最终堆空了,还没找到终点
			if (minHeap.empty()) break;
		}//end of while (1)

		//求路径和
		shortPath = myPath[end].weight;

		//求路径
		int index = end;
		pathIndex.push_back(index);
		while (1) {
			index = myPath[index].index;
			pathIndex.push_back(index);
			if (0 == index) break;
		}
	}
};

int main() {
	//准备图结构
	const int size = 11;//顶点个数
	vector<vector<int>> graph(size);
	for (int i = 0; i < size; i++) {
		graph[i].resize(size);//重置大小  开空间
	}

	//赋值
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < size; j++) {
			graph[i][j] = -1;
		}
	}

	graph[0][1] = 2;
	graph[0][2] = 3;
	graph[0][3] = 4;

	graph[1][2] = 3;
	graph[1][4] = 7;
	graph[1][5] = 2;

	graph[2][6] = 2;
	graph[2][5] = 9;

	graph[3][6] = 2;

	graph[4][7] = 3;
	graph[4][8] = 3;

	graph[5][6] = 1;
	graph[5][8] = 3;

	graph[6][9] = 1;
	graph[6][8] = 5;

	graph[7][10] = 3;
	
	graph[8][10] = 2;

	graph[9][8] = 2;
	graph[9][10] = 2;
	//创建shortTestPath对象
	shortTestPath sPath(graph, 10);

	sPath.getShortTestPath();

	sPath.printPath();

	while (1);
	return 0;
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值