-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 223 KB
/
Copy pathcontent.json
File metadata and controls
1 lines (1 loc) · 223 KB
1
{"meta":{"title":"DjangoZ's blog","subtitle":"Just for fun","description":"Just for fun","author":"DjangoZ","url":"https://www.djangoz.com"},"pages":[{"title":"","date":"2019-02-27T05:25:03.736Z","updated":"2018-12-06T06:59:48.000Z","comments":false,"path":"categories/index.html","permalink":"https://www.djangoz.com/categories/index.html","excerpt":"","text":""},{"title":"友情链接","date":"2018-06-11T04:25:33.000Z","updated":"2018-12-06T06:59:48.000Z","comments":true,"path":"links/index.html","permalink":"https://www.djangoz.com/links/index.html","excerpt":"","text":"需要互相加友情链接的可在下方留言 Techbelife ⌘鸡哥⌘的博客 我的博客园地址"},{"title":"","date":"2019-02-27T05:25:03.736Z","updated":"2018-12-06T06:59:48.000Z","comments":false,"path":"tags/index.html","permalink":"https://www.djangoz.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"RocketMQ通过docker快速搭建及调试","slug":"docker-install-rocketmq","date":"2020-04-03T07:30:34.000Z","updated":"2020-04-22T08:33:19.100Z","comments":true,"path":"2020/04/03/docker-install-rocketmq/","link":"","permalink":"https://www.djangoz.com/2020/04/03/docker-install-rocketmq/","excerpt":"","text":"最近本人在搞毕设,准备做一个oj,其中判题沙盒模块准备用消息队列来实现判题的并行化。下面是用docker来搭建和调试RocketMQ的过程 安装 Namesrv拉取官方镜像1docker pull rocketmqinc/rocketmq:4.4.0 启动容器1docker run -d -p 9876:9876 -v {RmHome}/data/namesrv/logs:/root/logs -v {RmHome}/data/namesrv/store:/root/store --name rmqnamesrv -e \"MAX_POSSIBLE_HEAP=100000000\" rocketmqinc/rocketmq:4.4.0 sh mqnamesrv 注意{RmHome} 要替换成你的宿主机想保存 MQ 的日志与数据的地方,通过 docker 的 -v 参数使用 volume 功能,把你本地的目录映射到容器内的目录上。否则所有数据都默认保存在容器运行时的内存中,重启之后就又回到最初的起点。 安装 broker 服务器与上一个是同一个docker镜像,无需拉取 创建broker.conf文件 在 {RmHome}/conf 目录下创建 broker.conf 文件 在 broker.conf 中写入如下内容12345678brokerClusterName = DefaultClusterbrokerName = broker-abrokerId = 0deleteWhen = 04fileReservedTime = 48brokerRole = ASYNC_MASTERflushDiskType = ASYNC_FLUSHbrokerIP1 = {本机局域网 IP} 启动容器1docker run -d -p 10911:10911 -p 10909:10909 -v {RmHome}/data/broker/logs:/root/logs -v {RmHome}/rocketmq/data/broker/store:/root/store -v {RmHome}/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf --name rmqbroker --link rmqnamesrv:namesrv -e \"NAMESRV_ADDR=namesrv:9876\" -e \"MAX_POSSIBLE_HEAP=200000000\" rocketmqinc/rocketmq:4.4.0 sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf 其中 brokerIP1 需要自己修改,不能是127.0.0.1 因为运行在docker容器里面的,localhost与宿主机器隔离,需要使用连接的局域网ip,否则会有各种异常产生 安装rocketmq控制台拉取镜像1docker run -e \"JAVA_OPTS=-Drocketmq.namesrv.addr={本机局域网 IP}:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false\" -p 8080:8080 -t pangliang/rocketmq-console-ng Drocketmq.namesrv.addr的ip就是上一步配置文件设置的brokerIP1 此时运行若无异常,console日志打出如下,则运行成功1234567[2020-04-03 06:23:30.049] INFO closeChannel: close the connection to remote address[192.168.31.156:10911] result: true[2020-04-03 06:23:59.976] INFO closeChannel: close the connection to remote address[192.168.31.156:10911] result: true[2020-04-03 06:24:00.045] INFO closeChannel: close the connection to remote address[192.168.31.156:9876] result: true[2020-04-03 06:24:30.031] INFO closeChannel: close the connection to remote address[192.168.31.156:9876] result: true[2020-04-03 06:24:30.035] INFO closeChannel: close the connection to remote address[192.168.31.156:10911] result: true[2020-04-03 06:24:59.961] INFO closeChannel: close the connection to remote address[192.168.31.156:9876] result: true[2020-04-03 06:25:00.027] INFO closeChannel: close the connection to remote address[192.168.31.156:10911] result: true 涉及到的容器如下 12345▶ docker container lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESfea34a9874b0 pangliang/rocketmq-console-ng \"sh -c 'java $JAVA_O…\" 13 minutes ago Up 13 minutes 0.0.0.0:8080->8080/tcp ecstatic_ritchie50d543af5485 rocketmqinc/rocketmq:4.4.0 \"sh mqbroker -c /opt…\" 17 hours ago Up 6 seconds 0.0.0.0:10909->10909/tcp, 9876/tcp, 0.0.0.0:10911->10911/tcp rmqbrokerf4c9e15adb96 rocketmqinc/rocketmq:4.4.0 \"sh mqnamesrv\" 17 hours ago Up 17 hours 10909/tcp, 0.0.0.0:9876->9876/tcp, 10911/tcp rmqnamesrv 安装c语言依赖库第一次跑go程序之前,还要先安装相关依赖包,不然可能会出现 1234rocketmq-client-go/core/cfuns.go:21:10: fatal error: 'rocketmq/CMessageExt.h' file not found#include \"rocketmq/CMessageExt.h\"^~~~~~~~~~~~~~~~~~~~~~~~1 error generated. 下面是解决步骤 123456789101112131. git clone https://github.com/apache/rocketmq-client-cpp2. mkdir -p /usr/local/include/rocketmq/3. cp rocketmq-client-cpp/include/* /usr/local/include/rocketmq4. sh build.sh5. after build sucess, cp bin/librocketmq.dylib /usr/local/lib6. install the go SDK.7. go build your code. go语言实现producer参考(官方demo)[https://github.com/apache/rocketmq-client-go/blob/master/examples/producer.go] 先在控制台新建一个topic 代码中修改 NameServer 和 Topic即可 参考代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748package mainimport ( \"fmt\" \"github.com/apache/rocketmq-client-go/core\")func Main0() { pConfig := &rocketmq.ProducerConfig{ ClientConfig: rocketmq.ClientConfig{ GroupID: \"broker-a\", NameServer: \"http://192.168.31.156:9876\", }, ProducerModel: rocketmq.CommonProducer, } sendMessage(pConfig)}func sendMessage(config *rocketmq.ProducerConfig) { producer, err := rocketmq.NewProducer(config) if err != nil { fmt.Println(\"create common producer failed, error:\", err) return } err = producer.Start() if err != nil { fmt.Println(\"start common producer error\", err) return } defer producer.Shutdown() fmt.Printf(\"Common producer: %s started... \\n\", producer) for i := 0; i < 10; i++ { msg := fmt.Sprintf(\"%s-%d\", \"Hello,Common MQ Message-\", i) result, err := producer.SendMessageSync(&rocketmq.Message{Topic: \"test_1\", Body: msg}) if err != nil { fmt.Println(\"Error:\", err) } fmt.Printf(\"send message: %s result: %s\\n\", msg, result) } fmt.Println(\"shutdown common producer.\")}func main() { Main0()} 运行生产者端代码,完成消息发送1234Common producer: ProducerConfig=[GroupId: broker-a, NameServer: http://192.168.31.156:9876, ProducerModel: CommonProducer, ] started... send message: Hello,Common MQ Message--0 result: [status: SendOK, messageId: C0A81F9C77926C5F71990DBE59BD0000, offset: 1]send message: Hello,Common MQ Message--1 result: [status: SendOK, messageId: C0A81F9C77926C5F71990DBE59F60001, offset: 1]send message: Hello,Common MQ Message--2 result: [status: SendOK, messageId: C0A81F9C77926C5F71990DBE5A000002, offset: 2] go语言实现consumer参考官方demo 修改对应topic和NameServer即可运行 运行消费者代码,完成消息消费1234567891011121314INFO[0000] subscribe topic[test_1] with expression[*] successfully. consumer: [PushConsumerConfig=[GroupId: broker-a, NameServer: http://192.168.31.156:9876, MessageModel: Clustering, ConsumerModel: Orderly, ], subcribed topics: [test_1, ]] started...A message received, MessageID:C0A81F9C77926C5F71990DBE59BD0000, Body:Hello,Common MQ Message--0 A message received, MessageID:C0A81F9C77926C5F71990DBE5A000002, Body:Hello,Common MQ Message--2 A message received, MessageID:C0A81F9C77926C5F71990DBE59F60001, Body:Hello,Common MQ Message--1 Consumer Later, MessageID:C0A81F9C77926C5F71990DBE59F60001 A message received, MessageID:C0A81F9C77926C5F71990DBE5A150003, Body:Hello,Common MQ Message--3 A message received, MessageID:C0A81F9C77926C5F71990DBE5A400004, Body:Hello,Common MQ Message--4 A message received, MessageID:C0A81F9C77926C5F71990DBE5A710007, Body:Hello,Common MQ Message--7 A message received, MessageID:C0A81F9C77926C5F71990DBE5A640006, Body:Hello,Common MQ Message--6 A message received, MessageID:C0A81F9C77926C5F71990DBE5A510005, Body:Hello,Common MQ Message--5 A message received, MessageID:C0A81F9C77926C5F71990DBE5A800008, Body:Hello,Common MQ Message--8 Consumer Later, MessageID:C0A81F9C77926C5F71990DBE5A800008 A message received, MessageID:C0A81F9C77926C5F71990DBE5A8D0009, Body:Hello,Common MQ Message--9 参考链接【Rocketmq】通过 docker 快速搭建 rocketmq 环境rocketmq 部署启动指南-Docker 版apache/rocketmq-client-gogo get rocketmq-client-go error #3","categories":[{"name":"docker","slug":"docker","permalink":"https://www.djangoz.com/categories/docker/"}],"tags":[{"name":"运维","slug":"运维","permalink":"https://www.djangoz.com/tags/运维/"},{"name":"MQ","slug":"MQ","permalink":"https://www.djangoz.com/tags/MQ/"}]},{"title":"mac高效使用合集(持续更新)","slug":"mac","date":"2019-03-21T06:01:38.000Z","updated":"2019-03-21T06:53:56.658Z","comments":true,"path":"2019/03/21/mac/","link":"","permalink":"https://www.djangoz.com/2019/03/21/mac/","excerpt":"","text":"item2+oh-my-zshitem2官网 item2: 同tab多窗口 垂直排列 command + d 水平排列 command + shift + d 窗口间切换 command + [ (]) 关闭当前窗口 command + w oh-my-zsh1sh -c \"$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)\" zshrc ZSH_THEME=”avit” plugins=(git zsh-syntax-highlighting zsh-autosuggestions) zsh-syntax-highlighting安装 1git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting zsh-autosuggestions安装 1git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 1source ~/.zshrc 当item2的主题是Solarized Dark时,会覆盖zsh-autosuggestions的提示字体,造成显示不了的情况1234▶ vim ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh: ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=2'}# 把fg的颜色从8改为其他颜色,只支持8种颜色,可参考zsh-autosuggestions文档 鼠标控制mac外接键盘无法用到触控板,外接鼠标的话,方向逻辑相反,Mos很好解决了这个问题,而且可以控制鼠标的滚轮爽如触控板. 鼠标箭头多屏幕切换在多屏幕工作时,经常要用手来操作鼠标箭头到另外一个屏幕,现用软件可实现快捷键切换到其他屏幕 先安装Hammerspoonopen config 123456789101112-- Set hyper to cmdlocal hyper = {'cmd'}-- Move Mouse to center of next Monitorhs.hotkey.bind(hyper, '`', function() local screen = hs.mouse.getCurrentScreen() local nextScreen = screen:next() local rect = nextScreen:fullFrame() local center = hs.geometry.rectMidPoint(rect) hs.mouse.setAbsolutePosition(center)end) 此处将cmd定义为hyper,然后定义hyper+`为切换鼠标箭头到其他屏幕,也可自定义为其他快捷键 窗口布局spectacle 移动窗口到到另外一个屏幕 ⌃⌥⌘→ 其他具体操作可参考文档 mac神器Alfred常用workflows CodeVar Dash Encode/Decode Google Translate Kill Process TerminalFinder Timestamp Youdao Translate","categories":[],"tags":[{"name":"技巧","slug":"技巧","permalink":"https://www.djangoz.com/tags/技巧/"}]},{"title":"总结遇到的python2中的坑","slug":"python2-pit","date":"2019-03-07T12:56:10.000Z","updated":"2019-03-08T08:57:49.857Z","comments":true,"path":"2019/03/07/python2-pit/","link":"","permalink":"https://www.djangoz.com/2019/03/07/python2-pit/","excerpt":"","text":"很久没有更新博客了,由于来到字节跳动实习,最近一直都很忙没时间来写博客, 有些东西也不便透露出来 公司内部一般都是用的python和golang, 而python库又是用的pyhton2.7来写的,编码的途中经常遇到很多很多坑,而且很多还是相同的问题导致了不同的结果…. 编码错误用python2最恶心的一句话莫过于UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128) python2不是默认utf-8编码…每个文件必须说明utf-8编码,不然py文件中遇到中文的话会直接出错 1# -*- coding: utf-8 -*- pycharm用户可以直接设置每次新增文件可以添加title.设置->Editor->File and Code Templates -> Files -> Python Script然后进行添加 在处理字符串时需要对中文字符进行unicode编码1s = u'你好世界' 输出含有中文的unicode字符的时候还要进行utf-8编码1print s.encode('utf-8') 对于如果要把unicode字符转化为str时,需要把环境编码设置为utf-8123import sysreload(sys).setdefaultencoding('utf8')print str(s) 去除字符串中所有空格1''.join(s.split()) 储存dict的list到csv文件有很多和dict需要存到csv里,以dict的keys作为csv的头123456789import csvheaders = ['name','age']person_list = [{'name':'djangoz','age':18},{'name':'alice','age':18}]with open('file.csv','w') as csvfile: csvwriter = csv.DictWriter(csvfile, fieldnames=headers) csvwriter.writeheader() for person in person_list: csvwriter.writerow(person) 然后file.csv文件就是这样了1234▶ cat file.csvname,agedjangoz,18alice,18","categories":[{"name":"python","slug":"python","permalink":"https://www.djangoz.com/categories/python/"}],"tags":[]},{"title":"本地mysql数据库搬迁到docker","slug":"dockermysql","date":"2018-12-26T06:54:26.000Z","updated":"2019-01-04T14:02:26.000Z","comments":true,"path":"2018/12/26/dockermysql/","link":"","permalink":"https://www.djangoz.com/2018/12/26/dockermysql/","excerpt":"","text":"最近慢慢熟悉了docker,打算将一些开发环境搬迁到docker容器里面便于管理和备份。 docker安装部分就不介绍了,可看官方文档 1.下载mysql镜像1docker pull mysql:8.0 下载的\b版本为8.0的mysql 2. \b创建容器1docker run -p 3306:3306 --restart=always -name mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:8.0 创建容器,将容器的3306端口映射到本地的3306端口,设置为docker启动时容器自启动,\b命名为mysql,设置数据库密码为root 3.导出本地数据库将本地mysql的数据库导出为sql文件1mysqldump -u root -p you_database > you_database.sql 4.将导出的sql文件复制到docker容器里1docker cp you_database.sql mysql:/opt/ \b此时sql文件\b就到mysql容器里的/opt/\b路径里面了。然后进入容器里面操作1docker exec -it mysql bash 此时就进入到容器里面了,先在容器里面的mysql创建相应的数据库\u001e12345678910111213141516▶ docker exec -it mymysql bashroot@19820b3cb9b8:/# mysql -u root -pEnter password:Welcome to the MySQL monitor. Commands end with ; or \\g.Your MySQL connection id is 13Server version: 8.0.13 MySQL Community Server - GPLCopyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\\h' for help. Type '\\c' to clear the current input statement.mysql> create database YOU_DATABASE; 创建完相应的数据库后,就可以导入之前复制到容器里面的sql文件了。1root@19820b3cb9b8:/# mysql -u root -p YOUDATABASE < /opt/you_database.sql 此时就大功告成了,然后就可以卸载本地的mysql,达成\b\b删库跑路成就了。12▶ brew remove mysqlUninstalling /usr/local/Cellar/mysql/8.0.12... (257 files, 233.2MB)","categories":[{"name":"docker","slug":"docker","permalink":"https://www.djangoz.com/categories/docker/"}],"tags":[{"name":"运维","slug":"运维","permalink":"https://www.djangoz.com/tags/运维/"}]},{"title":"Servlet/JSP简单生成图片验证码并后台验证功能","slug":"verifycode","date":"2018-11-18T07:45:15.000Z","updated":"2018-12-06T03:26:52.000Z","comments":true,"path":"2018/11/18/verifycode/","link":"","permalink":"https://www.djangoz.com/2018/11/18/verifycode/","excerpt":"","text":"最近在学用JAVA Servlet/JSP来开发web应用,于是练手来做一个通过Servlet后台来生成图片验证码,并实现验证功能。 实现思路先用单例模式写一个生成验证码图片的GenerateCode类,该类的createImage()方法返回BufferImage类,然后用Servlet来读取图片,GenerateCode对象在生成图片的同时也会生成一个验证码的字符串,可供后台验证的Servlet来验证前台post上去的验证码是否相同. 实现代码Servlet类 VerifyCode.java(显示验证码图片)12345678910111213141516171819202122232425262728293031323334353637383940package servlet;import Utils.GenerateCode;import com.sun.image.codec.jpeg.JPEGCodec;import com.sun.image.codec.jpeg.JPEGImageEncoder;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.OutputStream;@WebServlet(“/VerifyCode.do”)public class VerifyCode extends HttpServlet { private void processRequest(HttpServletRequest req, HttpServletResponse resp) { resp.setContentType(“image/jpg”); GenerateCode generateCode = GenerateCode.getInstance(); try { OutputStream outputStream = resp.getOutputStream(); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream); encoder.encode(generateCode.createImage()); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { processRequest(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { processRequest(req, resp); }} Verify.java(验证post上来的验证码)123456789101112131415161718192021222324package servlet;import Utils.GenerateCode;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet(“/Verify.do”)public class Verify extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(“text/html;charset=UTF-8”); String reallycode = GenerateCode.getInstance().getCode(); String postcode = req.getParameter(“verifycode”); if(reallycode.equalsIgnoreCase(postcode)){ req.getRequestDispatcher(“success.jsp”).forward(req,resp); }else resp.sendRedirect(“index.jsp”); }} Utils GenerateCode.java(生成验证码图片和字符串)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970package Utils;import java.awt.*;import java.awt.image.BufferedImage;import java.util.Random;public class GenerateCode{ private static final int PW = 70; private static final int PH = 35; private static Random random = new Random(); private static String[] fontnames = {“Chalkboard”,“Cochin”,“Skia”,“Apple Symbols”}; private String codes=“012345678901234567890123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ”; private static Color bgColor = Color.BLACK; private String code; private volatile static GenerateCode generateCode; public static GenerateCode getInstance(){ if(generateCode ==null){ synchronized (GenerateCode.class){ if(generateCode ==null){ generateCode = new GenerateCode(); } } } return generateCode; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } private Color randomColor(){ return new Color(random.nextInt(150),random.nextInt(150),random.nextInt(150)); } private Font randomFont(){ int index = random.nextInt(fontnames.length); String fontname = fontnames[index]; int style = random.nextInt(4); int size = random.nextInt(5) + 25; return new Font(fontname,style,size); } private String randomCode(){ int len = codes.length(); String code = “”; for(int i=0;i<4;i++){ int a = random.nextInt(len); code += codes.charAt(a); } return code; } public BufferedImage createImage(){ BufferedImage image = new BufferedImage(PW,PH,BufferedImage.TYPE_INT_BGR); Graphics g = image.getGraphics(); g.setColor(bgColor); g.fillRect(0,0,PW,PH); String num = randomCode(); this.setCode(num); g.setColor(randomColor()); g.setFont(randomFont()); g.drawString(num,0,25); return image; }} jsp index.jsp123456789101112131415161718192021222324252627<%@ page import=“java.awt.image.BufferedImage” %><%@ page import=“servlet.VerifyCode” %><%– Created by IntelliJ IDEA. User: onlyless Date: 11/17/18 Time: 11:34 To change this template use File | Settings | File Templates.–%><%@ page contentType=“text/html;charset=UTF-8” language=“java” %><html><head> <title>Title</title></head><body><form action=“/Verify.do” method=“post”> <table> <tr> <td> <img src=“/VerifyCode.do”> <input type=“text” name=“verifycode” maxlength=“4”> <input type=“submit” name=“submit”> </td> </tr> </table></form></body></html> success.jsp12345678910111213141516<%– Created by IntelliJ IDEA. User: onlyless Date: 11/16/18 Time: 16:46 To change this template use File | Settings | File Templates.–%><%@ page contentType=“text/html;charset=UTF-8” language=“java” %><html><head> <title>success</title></head><body><h1>success</h1></body></html> 不足思路很简单,最终也实现出来了,但是我发现了一个BUG,在多个窗口同时请求验证时,由于后台很简单的读取GenerateCode的验证码,因此这多个窗口post上来的验证码就算全部都是正确的,但是只能验证最后生成验证码图片的那个post,要解决这个问题,我想应该要用到Cookie的知识,现在还在学习,待以后再解决这个问题。 后台查看验证码不一致,后台正确的验证码是最后生成的验证码,导致之前的生成的验证码提交上去不匹配 后续第二天学到了Cookie和Session的运用,发现用Session很简单就能解决上面的那个不足。 VerifyCode.java123456…JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);encoder.encode(generateCode.createImage());req.getSession().setAttribute(“verify”,generateCode.getCode()); //添加的代码outputStream.close();… Verify.java1234567 … resp.setContentType(“text/html;charset=UTF-8”);// String reallycode = GenerateCode.getInstance().getCode(); String reallycode = (String) req.getSession().getAttribute(“verify”); //修改的代码 String postcode = req.getParameter(“verifycode”); System.out.println(reallycode + “—“ + postcode); …. 两个浏览器同时登陆都成功登陆,后台查看验证码一致 不过我发现这个问题在同一个浏览器上还是存在,原因很简单,用一个浏览器不同窗口的Session都是一样的,这样就导致新生成的验证码覆盖掉以前Session绑定的验证码,导致问题产生,这个问题我测试了一下学校的教务系统和12306的登录验证码,都存在这样的问题。不过这个一般谁也不会同时开多个窗口登录,登录失败一个,换以前的窗口登录,这样导致一直匹配到的不是产生最新的验证码。","categories":[{"name":"java","slug":"java","permalink":"https://www.djangoz.com/categories/java/"}],"tags":[{"name":"应用","slug":"应用","permalink":"https://www.djangoz.com/tags/应用/"},{"name":"web","slug":"web","permalink":"https://www.djangoz.com/tags/web/"},{"name":"Servlet/JSP","slug":"Servlet-JSP","permalink":"https://www.djangoz.com/tags/Servlet-JSP/"}]},{"title":"职业的乐趣","slug":"funnywork","date":"2018-11-02T11:15:27.000Z","updated":"2018-12-06T03:28:02.000Z","comments":true,"path":"2018/11/02/funnywork/","link":"","permalink":"https://www.djangoz.com/2018/11/02/funnywork/","excerpt":"","text":"今天在人月神话里看到的,觉得说得挺好的,就摘抄下来  首先,这种快乐是一种创建事物的纯粹快乐。如同小孩在玩泥巴时感到快乐一样,成年人喜欢创建事物,特别是自己进行设计。我想这种快乐是上帝创造世界的折射,一种呈现在每片独特的、崭新的树叶和雪花上的喜悦。 其次,这种快乐来自于开发对他人有用的东西。内心深处,我们期望我们的劳动成果能够被他人使用,并能对他们有所帮助。从这一角度而言,这同小孩用粘土为“爸爸的办公室”捏制铅笔盒没有任何本质的区别。 第三,快乐来自于整个过程体现出的一股强大的魅力—将相互啮合的零部件组装在一起,看到它们以精妙的方式运行着,并收到了预期的效果。比起弹球游戏机或自动电唱机所具有的迷人魅力,程序化的计算机毫不逊色。 第四,这种快乐是持续学习的快乐,它来自于这项工作的非重复特性。人们所面临的问题总有这样那样的不同,因而解决问题的人可以从中学习新的事物,有时是实践上的,有时是理论上的,或者兼而有之。 最后,这种快乐还来自于在易于驾驭的介质上工作。程序员,就像诗人一样,几乎仅仅在单纯的思考中工作。程序员凭空地运用自己的想象,来建造自己的“城堡”。很少有创造介质如此灵活,如此易于精炼和重建,如此容易实现概念上的设想(不过我们将会看到,容易驾驭的特性也有它自己的问题) 然而程序毕竟同诗歌不同,它是实实在在的东西;它可以移动和运行,能独立产生可见的输出;它能打印结果,绘制图形,发出声音,移动支架。神话和传说中的魔术在我们的时代已变成现实。在键盘上键入正确的咒语,屏幕会活动、变幻,显示出前所未有的也不可能存在的事物。","categories":[],"tags":[{"name":"闲言碎语","slug":"闲言碎语","permalink":"https://www.djangoz.com/tags/闲言碎语/"},{"name":"读书笔记","slug":"读书笔记","permalink":"https://www.djangoz.com/tags/读书笔记/"}]},{"title":"UEFI+GPT修复win10EFI开机启动","slug":"win10uefi","date":"2018-10-23T02:39:32.000Z","updated":"2018-12-06T03:28:26.000Z","comments":true,"path":"2018/10/23/win10uefi/","link":"","permalink":"https://www.djangoz.com/2018/10/23/win10uefi/","excerpt":"","text":"最近入手了三星sm951-SSD,把黑苹果和win10一起迁移到这个盘上来,结果只记起备份CLOVER,没有备份win10的启动项了,然后迁移过后EFI引导只有CLOVER了,由于已经把win10的整个分区移过来了,不想重装系统,想试试修复一下win10d EFI文件,理论上应该可行 EFI文件都在磁盘的ESP分区,先挂载ESP分区,然后用ms-dos自带的修复工具即可修复 准备工作:一个有原装winPE的u盘 开机进入u盘后,在含有现在安装和修复计算机界面那里,按shift+F10,进入命令行界面。 运行以下命令行12345678diskpart #进入diskpartlist disk # 查看磁盘select disk n # n->代表ESP分区所在的磁盘(0,1,2,3…n)list partition #查看选择的磁盘含有的分区select partition n # n->选择ESP分区assign letter=b #将ESP分区挂载为B盘exit #退出diskpartbcdboot C:\\windows /s B: /f uefi /l zh-cn #C:\\windows代表windows所在的分区在C盘,不同的情况可能会改变,找到系统所在的盘符即可","categories":[],"tags":[{"name":"折腾","slug":"折腾","permalink":"https://www.djangoz.com/tags/折腾/"},{"name":"系统","slug":"系统","permalink":"https://www.djangoz.com/tags/系统/"}]},{"title":"hdu1560DNA sequence(IDA*搜索)","slug":"hdu1560","date":"2018-10-20T02:38:29.000Z","updated":"2018-12-06T03:17:22.000Z","comments":true,"path":"2018/10/20/hdu1560/","link":"","permalink":"https://www.djangoz.com/2018/10/20/hdu1560/","excerpt":"","text":"题目链接 题意给定N个DNA字符串,找到最短的字符串使N个字符串都是它的子序列(不连续) 解题思路又是典型的IDA* 迭代加深搜索思路,用一个数组保存每次搜索到对于N个字符串的下标,当每个下标都到了字符串的结尾的时候即说明解出了答案。为防止超时,当此时深度+至少还要加深的深度>限制值时就返回上一层状态。 AC代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<functional>#include<map>#include<cmath>#include<queue>using namespace std;typedef long long ll;char dc[] = {‘A’,‘T’,‘G’,‘C’};string s[10];int n = 0;bool idastar(int step,int dex,int top){ bool yes = 1; int min_len = 0; for(int i=0;i<n;i++){ int len = s[i].size(); if(dex[i] != len){ yes = 0; min_len = max(min_len,len - dex[i]); } } if(yes)return true; if(min_len + step > top) return false; for(int i=0;i<4;i++){ char c = dc[i]; int cnt = 0; int pos[10]; for(int j=0;j<n;j++){ int len = s[j].size(); if(dex[j]!= len){ if (c == s[j][dex[j]]){ cnt++; pos[j] = dex[j]+1; }else{ pos[j] = dex[j]; } }else{ pos[j] = dex[j]; continue; } } if(cnt==0)continue; if(step <= top){ bool ok = idastar(step + 1,pos, top); if (ok) return true; } } return false;}int main(int argc, char const argv[]){ ios::sync_with_stdio(false); cin.tie(0); int T = 0; cin >> T; while(T–){ cin >> n; size_t top = 0; for(int i=0;i<n;i++){ cin >> s[i]; top = max(s[i].length(),top); } int dex[100]; while(top){ memset(dex,0,sizeof(dex)); if(idastar(0,dex,top))break; top++; } cout << top << endl; } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"},{"name":"搜索","slug":"搜索","permalink":"https://www.djangoz.com/tags/搜索/"}]},{"title":"hdu2102A计划(三维bfs)","slug":"hdu2102","date":"2018-10-18T08:19:04.000Z","updated":"2018-12-06T03:16:46.000Z","comments":true,"path":"2018/10/18/hdu2102/","link":"","permalink":"https://www.djangoz.com/2018/10/18/hdu2102/","excerpt":"","text":"题目链接 题意在一个两层迷宫内,骑士从起点是否能在给定步数之内找到公主,’#’代表传送门,会传送到另一个平面该位置去,’*’代表墙,’P’代表公主 解题思路水题,用bfs模拟即可,就是有个坑 传送门被传到的位置不能是墙(由题意)和传送门(会造成无限传送) AC代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<functional>#include<map>#include<cmath>#include<string>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 15;int N,M,S;struct Node { int x,y,z; int step;};char G[2][maxn][maxn];;bool vis[2][maxn][maxn];bool ok = 0;int dx[] = {1,0,-1,0};int dy[] = {0,1,0,-1};void bfs(Node beg){ queue<Node> Q; Q.push(beg); while(!Q.empty()){ Node t = Q.front(); Q.pop(); if(t.step > S)continue; if(G[t.z][t.x][t.y]==‘P’){ ok = 1; return ; } for(int i=0;i<4;i++){ int nx = t.x + dx[i]; int ny = t.y + dy[i]; int nz = t.z; if(nx<0||ny<0||ny>=M||nx>=N)continue; if(G[nz][nx][ny]==‘‘)continue; if(vis[nz][nx][ny])continue; vis[nz][nx][ny] = 1; if(G[nz][nx][ny]==‘#’&&G[nz^1][nx][ny]!=‘‘&&G[nz^1][nx][ny]!=‘#’){ Q.push((Node){nx,ny,nz^1,t.step+1}); }else if(G[nz][nx][ny]!=‘#’) Q.push((Node){nx,ny,nz,t.step+1}); } }}int main(int argc, char const *argv[]){ int T = 0; cin >> T; while(T–){ ok = 0; memset(vis,0,sizeof(vis)); cin >> N >> M >> S; Node beg; for (int k = 0; k < 2; k++) { for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { cin >> G[k][i][j]; } } } beg = (Node){0,0,0,0}; bfs(beg); if(ok){ cout << “YES” << endl; }else{ cout << “NO” << endl; } } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"},{"name":"搜索","slug":"搜索","permalink":"https://www.djangoz.com/tags/搜索/"}]},{"title":"poj2248Addition Chains(IDA*搜索)","slug":"poj2248","date":"2018-10-16T04:34:21.000Z","updated":"2018-12-06T03:26:20.000Z","comments":true,"path":"2018/10/16/poj2248/","link":"","permalink":"https://www.djangoz.com/2018/10/16/poj2248/","excerpt":"","text":"题目链接 题意f[0] = 1,f[1] = 2f[k] = f[i] + f[j] (0<=i,j<==k-1)求当n = f[k] 时,输出前k项 (k最小时) 解题思路典型的IDA*算法,K从1开始枚举,当k可行时,结束搜索.很容易发现f[k]是递增的,所以当搜索深度为step时,f[step] = f[1…step-1] + f[step-1],这样就很快地会搜索出解. AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<functional>#include<map>#include<cmath>#include<string>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 1e6+5;int sta[maxn];bool idastar(int n,int step,int top){ for(int i=0;i<step;i++){ int k = sta[i]+ sta[step-1]; if(k==n){ for(int i=0;i<step;i++){ cout << sta[i]; cout << “ “; } cout << k << endl; return true; } if(step < top){ sta[step] = k; bool ok = idastar(n,step+1,top); if(ok)return true; } } return false;}int main(int argc, char const *argv[]){ int n = 0; while(cin >> n,n){ int top = 0; if(n==1){ cout << “1” << endl; continue; }else if(n==2){ cout << “1 2” << endl; continue; } sta[0] = 1; sta[1] = 2; while(++top){ if(idastar(n,2,top))break; } } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"},{"name":"搜索","slug":"搜索","permalink":"https://www.djangoz.com/tags/搜索/"}]},{"title":"hdu3567Eight II(八数码,IDA*)","slug":"hdu3567","date":"2018-10-14T07:52:03.000Z","updated":"2018-12-06T03:17:48.000Z","comments":true,"path":"2018/10/14/hdu3567/","link":"","permalink":"https://www.djangoz.com/2018/10/14/hdu3567/","excerpt":"","text":"题目链接 题意八数码问题,不过目标状态不定,保证有解 解题思路和上一题类似,先算一下目标状态在跑IDA*即可,剪枝条件与上一题相同参考 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<functional>#include<map>#include<cmath>#include<string>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 1e6+5;int goal[10][2];int G[10][10];int dx[] = {1,0,0,-1};int dy[] = {0,-1,1,0};char dir[] = {‘d’,‘l’,‘r’,‘u’};int manhattan(){ int sum = 0; for(int i=0;i<3;i++){ for(int j=0;j<3;j++){ int w = G[i][j]; if(w==0)continue; sum += abs(goal[w][0] - i) + abs(goal[w][1] - j); } } return sum;}char sta[500];bool flag = 0;void idastar(int x,int y,int pre,int step,int top){ if(flag)return; for(int i=0;i<4;i++){ if(flag)return; int nx = x + dx[i]; int ny = y + dy[i]; if(nx > 2||ny>2||nx < 0||ny<0)continue; if(pre + i == 3)continue; swap(G[x][y],G[nx][ny]); int mht = manhattan(); if(mht==0){ cout << step+1 << endl; sta[step] = dir[i]; cout << sta << endl; flag = 1; return; } if(mht + step <= top){ if(flag) return; sta[step] = dir[i]; idastar(nx,ny,i,step+1,top); } swap(G[x][y],G[nx][ny]); }}int main(int argc, char const *argv[]){ int T = 0; cin >> T; int Case = 1; while(T–){ memset(sta,0,sizeof(sta)); string s1,s2; cin >> s1 >> s2; cout << “Case “ << Case++ << “: “; if(s1==s2){ cout << “0” << endl; cout <<endl; continue; } flag = 0; int bx,by; for(int i=0;i<s1.length();i++){ if(s1[i]==‘X’){ G[i/3][i%3] = 0; bx = i/3; by = i%3; }else{ G[i/3][i%3] = s1[i]-‘0’; } } for(int i=0;i<s2.length();i++){ int w = 0; if(s2[i]==‘X’){ w = 0; }else{ w = s2[i]-‘0’; } goal[w][0] = i/3; goal[w][1] = i%3; } int top = 0; while(++top){ idastar(bx,by,-1,0,top); if(flag) break; } } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"},{"name":"搜索","slug":"搜索","permalink":"https://www.djangoz.com/tags/搜索/"}]},{"title":"hdu1043Eight(八数码,IDA*)","slug":"hdu1043","date":"2018-10-14T07:45:52.000Z","updated":"2018-12-06T03:16:54.000Z","comments":true,"path":"2018/10/14/hdu1043/","link":"","permalink":"https://www.djangoz.com/2018/10/14/hdu1043/","excerpt":"","text":"题目链接 题意经典的八数码的题目,还要判断是否有解,wiki链接 解题思路用IDA*算法来解决,先算出当前状态和目标状态的哈曼顿距离,当距离为0时即解出答案。有两个可以剪枝的条件: 下一个方向和当前的方向不能相反 当前的步数加上当前的哈曼顿距离不能大于初始状态的哈曼顿距离 关于8数码是否有解,可参考这篇文章 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<functional>#include<map>#include<cmath>#include<string>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 1e6+5;int goal[10][2] = {{2,2},{0,0},{0,1},{0,2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}};int G[10][10];int dx[] = {1,0,0,-1};int dy[] = {0,-1,1,0};char dir[] = {‘d’,‘l’,‘r’,‘u’};int manhattan(){ int sum = 0; for(int i=0;i<3;i++){ for(int j=0;j<3;j++){ int w = G[i][j]; if(w==0)continue; sum += abs(goal[w][0] - i) + abs(goal[w][1] - j); } } return sum;}char sta[500];bool flag = 0;void idastar(int x,int y,int pre,int step,int top){ if(flag)return; for(int i=0;i<4;i++){ if(flag)return; int nx = x + dx[i]; int ny = y + dy[i]; if(nx > 2||ny>2||nx < 0||ny<0)continue; if(pre + i == 3)continue; swap(G[x][y],G[nx][ny]); int mht = manhattan(); if(mht==0){ sta[step] = dir[i]; printf(“%s\\n”,sta); flag = 1; return; } if(mht + step <= top){ if(flag) return; sta[step] = dir[i]; idastar(nx,ny,i,step+1,top); } swap(G[x][y],G[nx][ny]); }}bool isok(int state){ int sum = 0; for(int i=0;i<9;i++){ int num = 0; if(state[i]==0)continue; for(int j=i+1;j<9;j++){ if(state[i] > state[j]&&state[j]) num++; } sum += num; } return sum%2;}int main(int argc, char const argv[]){ char s[100]; while(~scanf(“ %c”,&s[0])){ memset(sta,0,sizeof(sta)); for(int i=1;i<9;i++){ scanf(“ %c”,&s[i]); } flag = 0; int bx,by; int state[10]; for(int i=0;i<9;i++){ if(s[i]==‘x’){ G[i/3][i%3] = 0; state[i] = 0; bx = i/3; by = i%3; }else{ G[i/3][i%3] = s[i]-‘0’; state[i] = s[i] - ‘0’; } } if(isok(state)){ printf(“unsolvable\\n”); continue; } int ans = manhattan(); if(ans==0){ printf(“\\n”); continue; } int top = 0; while(++top){ idastar(bx,by,-1,0,top); if(flag) break; } } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"},{"name":"搜索","slug":"搜索","permalink":"https://www.djangoz.com/tags/搜索/"}]},{"title":"poj2676(数独 搜索)","slug":"poj2676","date":"2018-10-09T14:56:53.000Z","updated":"2018-12-06T03:26:30.000Z","comments":true,"path":"2018/10/09/poj2676/","link":"","permalink":"https://www.djangoz.com/2018/10/09/poj2676/","excerpt":"","text":"题目链接 题意解出N个9x9的数独,(输出一个解即可) 解题思路用一个二维布尔数组来标记,点(x,y)的行,列,块的数字是否已经出现过,然后直接暴力搜索即可 AC代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<functional>#include<map>#include<cmath>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;bool vis[100][10];int G[10][10];int N = 10;int M = 20;inline int getid(int& x,int& y) //得到(x,y)所属的块{ if(x<3&&y<3) return M+1; else if(x<6&&y<3) return M+2; else if(x<9&&y<3)return M+3; else if(x<3&&y<6)return M+4; else if(x<6&&y<6) return M+5; else if(x<9&&y<6) return M+6; else if(x<3&&y<9)return M+7; else if(x<6&&y<9) return M+8; else return M+9;}bool ojbk = 0;void dfs(int x,int y){ if(ojbk)return; if(y>8){ y %= 9; x++; } if(x==9&&y==0){ for(int i=0;i<9;i++){ for(int j=0;j<9;j++){ cout << G[i][j]; } } ojbk = 1; } if(G[x][y]){ dfs(x,y+1); return; } int r = y; int c = N+x; int id = getid(x,y); for(int k=1;k<=9;k++){ if(vis[r][k]||vis[c][k]||vis[id][k]) continue; G[x][y] = k; vis[r][k] = 1; vis[c][k] = 1; vis[id][k] = 1; dfs(x,y+1); G[x][y] = 0; //回朔 vis[r][k] = 0; vis[c][k] = 0; vis[id][k] = 0; }}int main(int argc, char const *argv[]){ string s; while(cin >> s){ if(s==“end”)break; ojbk = 0; memset(vis,0,sizeof(vis)); int k = 0; for(int i=0;i<9;i++){ for(int j=0;j<9;j++){ if(s[k]==‘.’){ G[i][j] = 0; k++; continue; } G[i][j] = s[k++]-‘0’; if(G[i][j]==0)continue; vis[j][G[i][j]] = 1; vis[N+i][G[i][j]] = 1; vis[getid(i,j)][G[i][j]] = 1; } } dfs(0,0); cout << endl; } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"},{"name":"搜索","slug":"搜索","permalink":"https://www.djangoz.com/tags/搜索/"}]},{"title":"hdu3533Escape(预处理+bfs)","slug":"hdu3533","date":"2018-10-09T12:46:39.000Z","updated":"2018-12-06T03:17:40.000Z","comments":true,"path":"2018/10/09/hdu3533/","link":"","permalink":"https://www.djangoz.com/2018/10/09/hdu3533/","excerpt":"","text":"题目链接 题意A要从(0,0)到(m,n),A有体力值D,每秒都会消耗体力值1,有K座炮塔,每座炮塔都会发射子弹,给出炮塔射击的方向,并且具有一个射击周期,和子弹的速度。 当且仅当子弹在整数坐标(时间以1秒为最小单位)是才能击中A 炮塔会挡住子弹 人可以站着不动 当终点有炮塔的时候不能到达求A能否到达终点,如果能输出最短时间。 解题思路写这题的时候感觉有点掉头发。。。首先预处理子弹会在哪个位置有效击中,用一个三维数组来保存信息,对于每个子弹,先求出子弹能到达的最远位置,然后在0~d的时间内求,然后再用bfs来搜索可行的路线,当第一次能到达(m,n)就是所求答案。 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<functional>#include<map>#include<cmath>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxm = 1005;const int maxn = 105;int dx[] = {1,0,0,-1,0};int dy[] = {0,1,-1,0,0};char dir[] = {‘S’,‘E’,‘W’,‘N’};struct Node { int x,y; int step; Node(){} Node(int a,int b,int c):x(a),y(b),step(c){}};struct Castle { char c; int t,v; int x,y;};Castle E[maxn];bool mmp[maxm][maxn][maxn];bool B[maxn][maxn],vis[maxm][maxn][maxn];int m,n,k,d;void init(){ for(int it=0;it<k;it++){ int di; int X,Y; for(int i=0;i<4;i++) if(dir[i]==E[it].c) di = i; for(int i=1;i<maxm;i++){ int nx = E[it].x + dx[di]i; int ny = E[it].y + dy[di]i; if(nx < 0 || nx > m || ny <0 || ny > n || B[nx][ny]){ X = nx,Y = ny; //子弹最远能到达的坐标 break; } } if(E[it].c==‘S’){ for(int j=0;j<=d;j+=E[it].t){ //从0到d,子弹能打中的位置 int t = 0; for(int l = E[it].x+E[it].v;l<X;l+= E[it].v){ t++; if(j+t < d) mmp[j+t][l][E[it].y] = 1; } } }else if(E[it].c==‘N’){ for(int j=0;j<=d;j+=E[it].t){ int t = 0; for(int l=E[it].x-E[it].v;l>X;l-=E[it].v){ t++; if(j+t<d) mmp[j+t][l][E[it].y] = 1; } } }else if(E[it].c==‘E’){ for(int j=0;j<=d;j+=E[it].t){ int t = 0; for(int r = E[it].y+E[it].v;r < Y;r += E[it].v){ t++; if(t+j<=d) mmp[j+t][E[it].x][r] = 1; } } }else{ for(int j=0;j<=d;j+=E[it].t){ int t = 0; for(int r = E[it].y-E[it].v;r>Y;r-=E[it].v){ t++; if(j+t<d){ mmp[j+t][E[it].x][r] = 1; } } } } }}void bfs(){ queue<Node> Q; Node b(0,0,0); Q.push(b); vis[0][0][0] = 1; while(!Q.empty()){ Node t = Q.front(); Q.pop(); if(t.step>=d)break; if(t.x==m&&t.y==n){ cout << t.step << endl; return ; } for(int i=0;i<5;i++){ int nx = t.x + dx[i]; int ny = t.y + dy[i]; int step = t.step+1; if(nx <0||nx >m||ny<0||ny>n||vis[step][nx][ny])continue; if(B[nx][ny])continue; if(mmp[step][nx][ny])continue; vis[step][nx][ny] = 1; Q.push(Node(nx,ny,step)); } } cout << “Bad luck!”<< endl;}int main(int argc, char const *argv[]){ ios::sync_with_stdio(false); cin.tie(0); while(cin >> m >> n >> k >> d){ memset(B,0,sizeof(B)); memset(mmp,0,sizeof(mmp)); memset(vis,0,sizeof(vis)); for(int i=0;i<k;i++){ cin >> E[i].c >> E[i].t >> E[i].v >> E[i].x >> E[i].y; B[E[i].x][E[i].y] = 1; } init(); if(B[m][n]){ //当终点有炮塔的时候不能到达 cout << “Bad luck!”<< endl; }else bfs(); } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"},{"name":"搜索","slug":"搜索","permalink":"https://www.djangoz.com/tags/搜索/"}]},{"title":"hdu3416Marriage Match IV (网络流+最短路)","slug":"hdu3416","date":"2018-10-04T03:34:30.000Z","updated":"2018-10-04T03:46:42.000Z","comments":true,"path":"2018/10/04/hdu3416/","link":"","permalink":"https://www.djangoz.com/2018/10/04/hdu3416/","excerpt":"","text":"题目链接 题意N个城市M条路径,给定起点A,终点B,求有几条从A到B的最短路(其中每经过的路径不能重复) 解题思路先用最短路求出A到B的最短路Min,也求出A到每个城市的距离dis[N],然后反向求B到A的最短路,得到B到每个城市的最短距离dis2[N],然后遍历每条路径edge,如果dis[edge.from] + edge.len + dis2[edge.to]== Min,就说明这条路径一定是A到B的最短路中会经过的路径,,每条路径的容量为1,把每条符合条件的路径加入到最大流的图中,建完图后,以A为源点,B为汇点跑最大流即可(用EdmondsKarp会超时,跑Dinic即可) AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157#include<bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<int,int> pii;const int maxn = 1e5+5;const int INF = 0x3f3f3f3f;struct Edge { int from,to,cap,flow; Edge(){} Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}};struct Dinic{ int n,m,s,t; vector<Edge> edges; vector<int> G[maxn]; bool vis[maxn]; int d[maxn]; int cur[maxn]; void init(int n){ for(int i=0;i<n;i++) G[i].clear(); edges.clear(); } void AddEdge(int from,int to,int cap){ edges.push_back(Edge(from,to,cap,0)); edges.push_back(Edge(to,from,0,0)); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BFS() { memset(vis,0,sizeof(vis)); queue<int> Q; Q.push(s); d[s] = 0; vis[s] = 1; while(!Q.empty()){ int x = Q.front(); Q.pop(); for(int i=0;i<G[x].size();i++){ Edge& e = edges[G[x][i]]; if(!vis[e.to]&&e.cap > e.flow){ vis[e.to] = 1; d[e.to] = d[x]+1; Q.push(e.to); } } } return vis[t]; } int DFS(int x,int a){ if(x==t || a==0)return a; int flow = 0,f; for(int &i=cur[x];i<G[x].size();i++){ Edge& e = edges[G[x][i]]; if(d[x]+1==d[e.to] && (f=DFS(e.to,min(a,e.cap-e.flow)))>0){ e.flow += f; edges[G[x][i]^1].flow -= f; flow += f; a -= f; if(a==0)break; } } return flow; } int Maxflow(int s,int t){ this->s = s,this->t = t; int flow = 0; while(BFS()){ memset(cur,0,sizeof(cur)); flow += DFS(s,INF); } return flow; } };vector<Edge> G1[maxn];vector<Edge> G2[maxn];int N,M;int dis[maxn],dis2[maxn];bool vis[maxn];void dijkstra(int A,vector<Edge> G[maxn],int dis[maxn]){ memset(vis,0,sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > Q; dis[A] = 0; Q.push(pii(dis[A],A)); while(!Q.empty()){ pii t = Q.top(); Q.pop(); int d = t.first; int u = t.second; for(int i=0;i<G[u].size();i++){ Edge e = G[u][i]; if(e.cap + d < dis[e.to]){ dis[e.to] = e.cap + d; Q.push(pii(dis[e.to],e.to)); } } }}int main(int argc, char const *argv[]){ ios::sync_with_stdio(false); cin.tie(0); int T = 0; cin >> T; Dinic ek; while(T--){ cin >> N >> M; ek.init(N); for(int i=0;i<=N;i++){ G1[i].clear(); G2[i].clear(); } int a,b,c; Edge e; for(int i=0;i<M;i++){ cin >> a >> b >> c; e.from = a; e.to = b; e.cap = c; G1[e.from].push_back(e); swap(e.to,e.from); G2[e.from].push_back(e); } int A,B; cin >> A >> B; memset(dis,0x3f,sizeof(dis)); dijkstra(A,G1,dis); //正向跑最短路 memset(dis2,0x3f,sizeof(dis2)); int Min = dis[B]; dijkstra(B,G2,dis2); //反向跑最短路 for(int i=1;i<=N;i++){ for(int j=0;j<G1[i].size();j++){ e = G1[i][j]; if(dis[e.from] + e.cap + dis2[e.to]==Min){ ek.AddEdge(e.from,e.to,1); } } } cout << ek.Maxflow(A,B) << endl; } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"LightOJ1074Extended Traffic(bellman_ford最短路+负环标记)","slug":"LightOJ1074","date":"2018-10-03T03:19:42.000Z","updated":"2018-10-03T03:33:36.000Z","comments":true,"path":"2018/10/03/LightOJ1074/","link":"","permalink":"https://www.djangoz.com/2018/10/03/LightOJ1074/","excerpt":"","text":"题意T组样例,N个地点,每个地点有个繁忙度,地点间有M条街道,每条街道要收过路费(目的地繁忙度-起点繁忙度)^3 (3次方),有Q个查询,包含Q个目的地,求从起点1到每个目的地的最小花费。如果花费小于3或者无法到达目的地,则输出”?” 解题思路由于目的地繁忙度不一定大于起点繁忙度,所以图中有负环(一开始没想到,直接用dijkstra一直WA),所以要用bellman_ford来求最短路,当某些地点位于负环内,就肯定最终花费小于3。 AC代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include<bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<int,int> pii;const int maxn = 1e3+5;const int maxm = 1e6+5;const int inf = 0x2f3f3f3f; //这里给值小于初始dis数组赋值,原因可能是在跑bellman_ford的时候,负环和目的地的值相加变小了,直接判断目的地的值等于初始值的话,会waint N,M;int A[maxn];struct Edge { int from,to,cost;};int dis[maxn];bool vis[maxn];Edge G[maxm];void bellman_ford(){ memset(dis,0x3f,sizeof(dis)); dis[1] = 0; for(int i=1;i<=N;i++){ for(int j=0;j<M;j++){ Edge e = G[j]; if(dis[e.from] + e.cost < dis[e.to]){ dis[e.to] = e.cost + dis[e.from]; if(i==N){ vis[e.from] = 1; // vis[e.to] = 1; //标记负环内结点 } } } }}int main(int argc, char const *argv[]){ int T = 0; cin >> T; int Case = 1; while(T--){ cin >> N; for(int i=1;i<=N;i++){ cin >> A[i]; } cin >> M; int a,b; Edge e; memset(vis,0,sizeof(vis)); for(int i=0;i<M;i++){ cin >> a >> b; e.from = a; e.to = b; e.cost = pow(A[b]-A[a],3); G[i] = e; } int Q = 0; cin >> Q; cout << \"Case \" << Case++ << \":\" << endl; bellman_ford(); for(int i=0;i<Q;i++){ int t = 0; cin >> t; if(dis[t] < 3 || dis[t]>=inf || vis[t]){ cout << \"?\" << endl; }else{ cout << dis[t] << endl; } } } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"搞ACM的你伤不起(转载)","slug":"acm","date":"2018-10-02T10:42:55.000Z","updated":"2018-10-02T10:44:28.000Z","comments":true,"path":"2018/10/02/acm/","link":"","permalink":"https://www.djangoz.com/2018/10/02/acm/","excerpt":"","text":"看到这个感觉太真实了,笑死我了!!! RoBa原创,转载请注明出处 劳资六年前开始搞ACM啊!!!!!!!!!!从此踏上了尼玛不归路啊!!!!!!!!!!!!谁特么跟劳资讲算法是程序设计的核心啊!!!!!!尼玛除了面试题就没见过用算法的地方啊!!!!!!谁再跟劳资讲算法之美算法的力量,劳资一本算法导论拍死你啊!!!!!!!!那是搞ACM的入门书啊!!!!特么的入门书就一千多页啊!!!!!!!还没有习题答案啊,学完了你特么都不知道自己到底会不会啊有木有!!!!!!然后你就得看lrj的黑书啊!!!!!!还是特么的没有习题答案啊!!!!那书难的一B啊!!!!人家一个“显然”得出的结论够你想一礼拜啊有木有!!!!一个课后题够你想几个月啊有木有!!!!然后还有一堆堆的书啊!!!!每一类算法都足够写一本书啊!!!!每本都是砖头一样啊!!!!还都特么是英文的啊!!!!也有中文翻译版啊!!!!!!翻译得跟屎一样啊!!!!你看的时候得把它再变回英文才能懂啊!!!!!!有木有!!!!!! ACM的题目类型是没有范围的啊!!!!!!动态规划有木有!!!!数据结构有木有!!!!图论有木有!!!!!!计算几何有木有!!!!!!数论有木有!!!!!!要写两三百行的模拟题有木有!!!!!!特么连物理题化学题都有啊!!!!!!还有理论上就不可做的NP难问题啊!!!!!!特么理论上不可做的题也有人能AC啊!!!!坑爹啊!!!! 课本上学的东西完全不给力啊!!!!!!你以为学过一个最长公共子串就是懂动态规划了啊!!!!!!树型的有木有!!!!状态压缩的有木有!!!!插头的有木有!!!!而且特么写出来就超时啊!!!!!!你得四边形优化啊!!!!你得斜率优化啊!!!!你得队列优化啊!!!!特么恨不得把要算十年的程序优化到一秒啊!!!!你以为学过一个二叉搜索树就是懂数据结构了啊!!!!!!平衡啊旋转啊红啊黑啊有木有!!!!伸展啊随机权重啊合并啊拆分啊有木有!!!!!!你以为学过一个Dijkstra最短路就是懂图算法了啊!!!!!!特么的图里有几百万个点啊!!!!!!得用堆来优化啊!!!!而且边权要是负的就不对了啊!!!!还有环啊!!!!而且特么的你根本看不出是最短路问题啊!!!!!!为神马最短路算法可以用来解不等式啊!!!!还有网络流啊!!!!特么的课本上的算法铁定超时啊!!!!!!你得看论文去研究神马Dinic啊SAP啊!!!!!!而且你还是根本看不出是网络流啊!!!!!!网络流是在图上来求啊!!!!特么的图在哪里啊!!!!特么的八竿子打不着的问题都能变成网络流啊!!!!!! 这些你都学会了啊!!!!想参加比赛了啊!!!!发现想拿个成绩非常难啊!!!!!!校内选拔赛就好几百人报名啊有木有!!!!!!最后只能剩下十几个啊!!!!人家都是竞赛保送的啊!!!!!!中学就学了好几年了有木有!!!!怎么比的过啊!!!!!!进了校队以后你就不要想寒暑假了啊!!!!!!夏天劳资全身脱光了涂满花露水半夜刷题有木有!!!!!!冬天劳资跑遍校园找不到一个开门的食堂有木有!!!!!!而且特么老外的在线比赛都在半夜啊!!!!!!!!在机房通宵是常事啊有木有!!!!!! 比赛是三个人啊!!!!但是只有一台电脑啊!!!!!!特么的ACM组委会连多买几台电脑都不肯啊!!!!!!队友占着机器你就只能干着急啊!!!!!!想把他踹一边儿去啊!!!!!!没机器你就得在纸上调试啊!!!!!!你的脑子就是个CPU啊有木有!!!!你要是摊上一个啥都不会,连读题都误导你的队友,你就死定了啊!!!!!!不怕神一样的对手就怕猪一样的队友啊!!!! 你终于参加区域赛了啊!!!!一百多个队啊!!!!!!还有一大堆打星号的高中生们啊!!!!!!都是全国前几名级别的有木有!!!!还有一大堆打星号的老不死们啊!!!!!!毕业了还要来诈尸啊!!!!一开场那气球呼呼地挂啊!!!!你还没读完题人家已经AC了啊!!!!而且最先过的不一定是最简单的啊!!!!人家故意在误导你啊有木有!!!!!!比赛要五个小时啊!!!!结束了以后脑子都抽筋了啊!!!!人家做七八道题,你连一半都不到啊!!!!!!拿个毛的奖啊!!!!去个毛的总决赛啊!!!!!!去了总决赛也是被虐啊!!!!!!老毛子们更尼玛不是人啊有木有!!!!!!连清华都被虐啊有木有!!!!!! 你拿着一堆Honorable Mention毕业了啊!!!!想找工作啊!!!!谁说懂算法很容易找到好工作啊!!!!都特么要项目经验有木有!!!!劳资成天盯着一个黑乎乎的控制台窗口!!!!哪有时间做项目啊!!!!!!同学们都是网站啊桌面程序啊做过一堆啊!!!!人家对面试官侃侃而谈啊!!!劳资只好说劳资除了算法啥也不会啊!!而且其实算法也没学会啊!!!!!坑爹啊!!!! 终于工作了啊!!!!发现算法神马的完全用不上啊!!!!稍微复杂点的算法都有现成的库可以用啊!!!!!!要懂Shell编程有木有!!!!要懂多线程有木有!!!!要懂Socket有木有!!!!要懂分布式有木有!!!!要懂J2EE有木有!!!!要懂设计模式有木有!!!! 要懂以下省略三千字有木有!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 总之搞ACM的上辈子都是脑细胞死光钻到牛角尖里出不来的天使啊!!!!!!你伤不起啊!!!!!!","categories":[],"tags":[{"name":"闲言碎语","slug":"闲言碎语","permalink":"https://www.djangoz.com/tags/闲言碎语/"}]},{"title":"poj1062昂贵的聘礼(枚举+最短路)","slug":"poj1062","date":"2018-10-02T03:34:19.000Z","updated":"2018-10-02T03:49:20.000Z","comments":true,"path":"2018/10/02/poj1062/","link":"","permalink":"https://www.djangoz.com/2018/10/02/poj1062/","excerpt":"","text":"题意额,直接看题目吧,反正也是中文题,不好用几句话表述清楚~~~题目链接 解题思路因为等级差距不能间接交易,所以每个交易的等级都在一个区间内,这个区间必须包含大祭司的等级,可以把区间枚举,设大祭司等级为L,则需要从[L-M,L],一直枚举到[L,L+M],保证所有交易的点的等级都在这个区间内就可以了,然后在进行最短路处理,进行最短路的过程中,先不考虑直接购买物品的价值,直接全部走兑换的形式,然后把得到的数组加上直接购买物品的价值,从中找到最小值就是答案。 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<map>#include<cmath>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 1e4+5;struct Node { int val,level; int X; vector<pii> vp; };struct Edge { int from,to,val;};Node L[maxn];vector<Edge> G[maxn];int dis[maxn];bool vis[maxn];int N,M;int dijkstra(){ memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > Q; dis[1] = 0; Q.push(pii(dis[1],1)); while(!Q.empty()){ pii t = Q.top(); Q.pop(); int d = t.first; int u = t.second; for(int i=0;i<G[u].size();i++){ Edge e = G[u][i]; if(e.val + d < dis[e.to]){ dis[e.to] = e.val + d; Q.push(pii(dis[e.to],e.to)); } } } int ans = 1<<30; for(int i=1;i<=N;i++){ dis[i] += L[i].val; ans = min(ans,dis[i]); } return ans;}int main(int argc, char const *argv[]){ cin >> M >> N; int a,b; for(int i=1;i<=N;i++){ cin >> L[i].val >> L[i].level >> L[i].X; for(int j=0;j<L[i].X;j++){ cin >> a >> b; L[i].vp.push_back(pii(a,b)); } } Edge e; int left = max(0,L[1].level-M), right = left + M; int ans = 1<<30; while(left <= L[1].level){ for(int i=0;i<=N;i++){ G[i].clear(); } for(int i=1;i<=N;i++){ for(int j=0;j<L[i].X;j++){ int next = L[i].vp[j].first; if (L[next].level >= left && L[next].level <= right) { e.from = i; e.to = next; e.val = L[i].vp[j].second; G[e.from].push_back(e); } } } left++,right++; int te = dijkstra(); ans = min(ans,te); } cout << ans << endl; return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"poj1679The Unique MST(次小生成树)","slug":"poj1679","date":"2018-10-01T10:37:37.000Z","updated":"2018-10-01T10:40:04.000Z","comments":true,"path":"2018/10/01/poj1679/","link":"","permalink":"https://www.djangoz.com/2018/10/01/poj1679/","excerpt":"","text":"题意n个点m条边,判断最小生成树是否唯一 解题思路求出次小生成树和最小生成树大小是否相同即可 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<map>#include<cmath>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 1e6+5;int B[maxn];int N,M;struct Edge { int from,to,val; bool operator<(const Edge& e)const { return val < e.val; }};Edge E[maxn];int Find(int n){ return B[n]==n?n:B[n]=Find(B[n]);}void init(){ for(int i=0;i<=N;i++) B[i] = i;}int vis[maxn];int cnt = 0;int MST(){ sort(E,E+M); int ans = 0; for(int i=0;i<M;i++){ int t1 = Find(E[i].from); int t2 = Find(E[i].to); if(t1!=t2){ if(t1>t2)swap(t1,t2); B[t2] = t1; vis[cnt++] = i; ans += E[i].val; } if(cnt==N-1){ break; } } return ans;}int SMT(){ int res = 1<<30; for(int i=0;i<cnt;i++){ init(); int t = 0; int ans = 0; bool ok = 0; for(int j=0;j<M;j++){ if(j!=vis[i]){ int t1 = Find(E[j].from); int t2 = Find(E[j].to); if(t1!=t2){ t++; ans += E[j].val; if(t1>t2)swap(t1,t2); B[t2] = t1; } if(t==N-1){ ok = 1; break; } } } if(ok) res = min(res,ans); } if(res==1<<30)return -1; return res;}int main(int argc, char const *argv[]){ int T = 0; cin >> T; while(T--){ memset(vis,0,sizeof(vis)); cnt = 0; cin >> N >> M; init(); for(int i=0;i<M;i++){ cin >> E[i].from >> E[i].to >> E[i].val; } int t1 = MST(); int t2 = SMT(); if(t1==t2){ cout << \"Not Unique!\" << endl; }else{ cout << t1 << endl; } } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"牛客国庆集训派对Day1-New Game!(几何+最短路)","slug":"nowcoderDay1L","date":"2018-10-01T08:03:59.000Z","updated":"2018-10-01T10:26:12.000Z","comments":true,"path":"2018/10/01/nowcoderDay1L/","link":"","permalink":"https://www.djangoz.com/2018/10/01/nowcoderDay1L/","excerpt":"","text":"题目链接 题意给互相平行的直线L1,L2,和N个圆,角色在直线上、圆上、园内行走不消耗体力。在其他位置上由S点走到T点消耗的体力为S和T的欧几里得距离。求最少需要多少体力。 解题思路这题和poj2502那题很相似,关键在建图,这里考察了圆到圆的最短距离和线到圆的最短距离,一开始写的代码以为圆内要消耗体力,然后计算了一下内含情况的最短距离,结果也AC了,后来发现不需要,可能是题目数据没这种情况吧。 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114#include<bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<double,int> pii;const int maxn = 1e6+5;const double inf = 1e30;int n,A,B,C1,C2;struct Edge { int from,to; double val;};vector<Edge> G[maxn];struct Node { int x,y; double r;};Node C[maxn];double getval(Node a,Node b){ if(a.r < b.r){ swap(a,b); } double len = sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); // if(len + b.r < a.r){ //内含情况的判断 // return a.r - (b.r + len); // } if(len > a.r + b.r){ return len - a.r - b.r; }else{ return 0; }}double dis[maxn];bool vis[maxn];void dijkstra(){ memset(vis,0,sizeof(vis)); fill(dis,dis+maxn,inf); dis[n] = 0; priority_queue<pii,vector<pii>,greater<pii> > Q; Q.push(pii(0,n)); while(!Q.empty()){ pii t = Q.top(); Q.pop(); double d = t.first; int u = t.second; if(vis[u])continue; vis[u] = 1; for(int i=0;i<G[u].size();i++){ Edge e = G[u][i]; if(e.val + d < dis[e.to]){ dis[e.to] = e.val + d; Q.push(pii(dis[e.to],e.to)); } } } cout << dis[n+1]<< endl;}int main(int argc, char const *argv[]){ cin >> n >> A >> B >> C1 >> C2; for(int i=0;i<n;i++){ cin >> C[i].x >> C[i].y >> C[i].r; } Edge e; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(i==j)continue; e.from = i; e.to = j; e.val = getval(C[i],C[j]); G[e.from].push_back(e); swap(e.from,e.to); G[e.from].push_back(e); } } double di = sqrt(A*A+B*B); for(int i=0;i<n;i++){ double t = abs(A*C[i].x + B*C[i].y + C1)/di - C[i].r; if(t < 0) t = 0; e.from = n; e.to = i; e.val = t; G[e.from].push_back(e); swap(e.from, e.to); G[e.from].push_back(e); } for(int i=0;i<n;i++){ double t = 1.0*abs(A*C[i].x + B*C[i].y + C2)/di - C[i].r; if(t < 0) t = 0; e.from = n+1; e.to = i; e.val = t; G[e.from].push_back(e); swap(e.from, e.to); G[e.from].push_back(e); } double t = 1.0*abs(C1-C2)/di; e.from = n; e.to = n+1; e.val = t; G[e.from].push_back(e); swap(e.from, e.to); G[e.from].push_back(e); dijkstra(); return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"poj2502Subway(建图+最短路)","slug":"poj2502","date":"2018-10-01T02:41:32.000Z","updated":"2018-10-01T03:45:12.000Z","comments":true,"path":"2018/10/01/poj2502/","link":"","permalink":"https://www.djangoz.com/2018/10/01/poj2502/","excerpt":"","text":"题目链接 题意给起点和终点的坐标,然后给出多条地铁每一站的坐标,每站地铁只能到相邻的地铁站,地铁的速度是40km/h,人行走的速度是10km/h,求起点到终点的最小时间(给出的坐标单位是m,最后求的时间单位是分钟) 解题思路这题关键点在与建图,把图建好后跑dijkstra就很简单了,之前一直wa是想复杂了,以为要考虑起点和终点在地铁站的情况和不同线路的地铁站相交的情况,后来想不需要这么麻烦,同一条线路相邻的40,不同的10就可以了,相交的话,以10km/h走时间花费是0。 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cmath>#include<cstring>#include<map>#include<queue>using namespace std;typedef long long ll;typedef pair<double,int> pii;typedef pair<int,pii> PII;const int inf = 1<<30;const int maxn = 1e5+5;struct Node { int x,y; Node(){} Node(int a,int b):x(a),y(b){}};Node beg,en;vector<Node> node;Node D[maxn];struct Edge{ int from,to; double val; double cost;};vector<Edge> G[maxn];double getval(Node &a,Node& b){ return sqrt((double)(abs(a.x-b.x)*abs(a.x-b.x)+abs(a.y-b.y)*abs(a.y-b.y)));}double getcost(double cnt,bool issubway){ double ans = 0; if(issubway){ ans = cnt/40000 * 60; }else{ ans = cnt/10000 * 60; } return ans;}double dis[maxn];bool vis[maxn];int cnt = 0;double dijkstra(){ memset(vis,0,sizeof(vis)); fill(dis,dis+maxn,inf); priority_queue<pii,vector<pii>,greater<pii> > Q; Q.push(pii(0,cnt)); while(!Q.empty()){ pii t = Q.top(); Q.pop(); int u = t.second; double d = t.first; if(vis[u])continue; vis[u] = 1; for(int i=0;i<G[u].size();i++){ Edge e = G[u][i]; if(e.cost + d < dis[e.to]){ dis[e.to] = e.cost + d; Q.push(pii(dis[e.to],e.to)); } } } return dis[cnt+1];}int main(int argc, char const *argv[]){ scanf(\"%d%d\",&beg.x,&beg.y); scanf(\"%d%d\",&en.x,&en.y); int x,y; int k = 0; Edge e; while(~scanf(\"%d%d\",&x,&y)){ if(x==-1&&y==-1){ for(int i=k;i<cnt-1;i++){ e.val = getval(D[i], D[i+1]); e.cost = getcost(e.val, 1); e.from = i; e.to = i+1; G[e.from].push_back(e); swap(e.from, e.to); G[e.from].push_back(e); } k = cnt; continue; } D[cnt] = Node(x,y); cnt++; } D[cnt] = beg; D[cnt+1] = en; for(int i=0;i<cnt+2;i++){ for(int j=i+1;j<cnt+2;j++){ e.val = getval(D[i], D[j]); e.cost = getcost(e.val, 0); e.from = i; e.to = j; G[e.from].push_back(e); swap(e.from, e.to); G[e.from].push_back(e); } } double ans = dijkstra(); printf(\"%d\\n\",(int)(ans+0.5)); return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"poj2240Arbitrage(判负环)","slug":"poj2240","date":"2018-09-30T04:13:25.000Z","updated":"2018-09-30T04:19:56.000Z","comments":true,"path":"2018/09/30/poj2240/","link":"","permalink":"https://www.djangoz.com/2018/09/30/poj2240/","excerpt":"","text":"题意给n种货币,m条货币之前的汇率,判断最后能否从中套利 解题思路典型的判定负环图的问题,题目给的货币字符串,用map来给不同的字符串一个映射就可以了 AC代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<map>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 105;const int maxm = 1e6+5;const int inf = 1<<30;struct Edge { int from,to; double cost; Edge(){} Edge(int a,int b,double c):from(a),to(b),cost(c){}};Edge E[maxm];int N,M;map<string,int> money;double dis[maxn];bool bellman_ford(){ for(int i=0;i<=N;i++){ dis[i] = 0; } dis[0] = 1; for(int i=0;i<N;i++){ for(int j=0;j<M;j++){ Edge e = E[j]; double t = dis[e.from] * e.cost; if(dis[e.to] < t){ dis[e.to] = t; if(i==N-1) return false; } } } return true;}int main(int argc, char const *argv[]){ ios::sync_with_stdio(false); cin.tie(0); int Case = 1; while(cin >> N,N){ money.clear(); string s; for(int i=0;i<N;i++){ cin >> s; money[s] = i; } cin >> M; double t; string s2; for(int i=0;i<M;i++){ cin >> s >> t >> s2; E[i] = Edge(money[s],money[s2],t); } bool ok = bellman_ford(); cout << \"Case \"<< Case++ << \": \"; if(!ok){ cout << \"Yes\" << endl; }else{ cout << \"No\" << endl; } } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"poj1502MPI Maelstrom(最短路)","slug":"poj1502","date":"2018-09-30T03:40:51.000Z","updated":"2018-09-30T03:45:02.000Z","comments":true,"path":"2018/09/30/poj1502/","link":"","permalink":"https://www.djangoz.com/2018/09/30/poj1502/","excerpt":"","text":"题意给定一个下三角矩阵,询问从1开始到其他点的最短路径中,最长的那个是多少?(其中x代表没路径) 解题思路水题,dijkstra即可AC,唯一不同的是多一个字符串处理 AC代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<map>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 1e3+5;struct Edge{ int to,cost;};vector<Edge> G[maxn];int dis[maxn];bool vis[maxn];int N;void dijkstra(){ memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > Q; Q.push(pii(0,1)); dis[1] = 0; while(!Q.empty()){ pii t = Q.top(); Q.pop(); int u = t.second; int d = t.first; if(vis[u])continue; vis[u] = 1; for(int i=0;i<G[u].size();i++){ Edge e = G[u][i]; if(e.cost + d < dis[e.to]){ dis[e.to] = e.cost + d; Q.push(pii(dis[e.to],e.to)); } } } int ans = 0; for(int i=2;i<=N;i++){ ans = max(ans,dis[i]); } cout<< ans << endl;}int main(int argc, char const *argv[]){ ios::sync_with_stdio(false); cin.tie(0); cin >> N; int t; cin.get(); string s; for(int j=2;j<=N;j++){ getline(cin,s); int k = 1; int len = s.size(); for(int i=0;i<len;i++){ int num = 0; if(s[i]==' ')continue; if(s[i]=='x'){ k++; continue; } while(s[i]>='0'&&s[i]<='9'){ num *= 10; num += (s[i]-'0'); i++; } G[k].push_back((Edge){j,num}); G[j].push_back((Edge){k,num}); k++; } } dijkstra(); return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"2018icpc南京网络赛L题Magical Girl Haz(dijkstra+dp)","slug":"2018icpcnanjingL","date":"2018-09-29T08:22:49.000Z","updated":"2018-09-29T08:41:56.000Z","comments":true,"path":"2018/09/29/2018icpcnanjingL/","link":"","permalink":"https://www.djangoz.com/2018/09/29/2018icpcnanjingL/","excerpt":"","text":"题目链接 题意有N个城市M条路径,可以使K条路径长度变为0,求1到N最短路 解题思路求最短路很好求,但是题目多了一个限制条件,可以使K条路径长度变为0,这就有点麻烦了,后来想到,这也有点像01背包选与不选的感觉,求最短路的时候加个状态就可以求解了 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263#include<bits/stdc++.h>using namespace std;typedef long long ll;const int maxn = 1e5+500;struct Edge { int to,val;};vector<Edge> G[maxn];typedef pair<int,int> pii;typedef pair<ll,pii> PII;ll dis[15][maxn];int dijkstra(int N,int K){ priority_queue<PII,vector<PII>,greater<PII> > Q; Q.push(PII(0,pii(1,K))); memset(dis,0x3f,sizeof(dis)); dis[K][1] = 0; while(!Q.empty()){ PII t = Q.top(); Q.pop(); int d = t.first; int u = t.second.first; int k = t.second.second; for(int i=0;i<G[u].size();i++){ Edge e = G[u][i]; if( dis[k][e.to] > dis[k][u] + e.val){ dis[k][e.to] = dis[k][u] + e.val; Q.push(PII(dis[k][e.to],pii(e.to,k))); } if(k > 0 && dis[k][u] < dis[k-1][e.to]){ dis[k-1][e.to] = dis[k][u]; Q.push(PII(dis[k-1][e.to],pii(e.to,k-1))); } } } return dis[0][N];}int main(int argc, char const *argv[]){ int T = 0; scanf(\"%d\",&T); int N,M,K; while(T--){ scanf(\"%d%d%d\",&N,&M,&K); for(int i=0;i<=N;i++){ G[i].clear(); } int a,b,c; for(int i=0;i<M;i++){ scanf(\"%d%d%d\",&a,&b,&c); Edge t = (Edge){b,c}; G[a].push_back(t); } int ans = dijkstra(N,K); printf(\"%d\\n\",ans); } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"2018icpc北京网络赛B题Tomb Raider题解","slug":"2018icpcBeiJingB","date":"2018-09-28T02:38:51.000Z","updated":"2018-09-29T08:40:54.000Z","comments":true,"path":"2018/09/28/2018icpcBeiJingB/","link":"","permalink":"https://www.djangoz.com/2018/09/28/2018icpcBeiJingB/","excerpt":"","text":"题目链接 题意给n个字符串,每个字符串是一个环(就是说起点任意),求n个字符串的最长公共子序列(LCS) 解题思路比赛的时候刚看到这个题目要求n个字符串的lcs,并且没个字符串可以起点不一样,就是说字符串s都有s.length个不同的排列,求所有n个s里s.length个lcs,时限只有1s,求两个字符串的lcs有n^2的复杂度,思路肯定不是这样,而且n和s.length\b很小,发现可以直接暴力求每个字符串的所有子串(相当于求集合的所有子集),然后用set\b保存,答案就是n个set里最长的公共\b串。 AC代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475#include<bits/stdc++.h>using namespace std;string S[12];set<string> cset[12];void getstr(string a,int mark,int length,int k){ bool allZero=true; int limit=1<<length; string s; for(int i=0;i<length;++i) { if(((1<<i)&mark)!=0) { allZero=false; s += a[i]; } } if(s!=\"\"||s.size()>1) cset[k].insert(s);}void subset(string a,int length,int k){ if(length>31) return; int lowFlag=0; int highFlag=(1<<length)-1; for(int i=lowFlag;i<=highFlag;++i) { getstr(a,i,length,k); }}int main(){ int n = 0; while(cin >> n){ for(int i=0;i<n;i++){ cset[i].clear(); cin >> S[i]; int len = S[i].size(); S[i] += S[i]; for(int j=0;j<len;j++){ string s(S[i].begin()+j,S[i].begin()+len+j); subset(s,s.size(),i); } } vector<string> ans; for(string s:cset[0]){ bool ok = 1; for(int i=1;i<n;i++){ if(cset[i].count(s)==0){ ok = 0; break; } } if(ok) ans.push_back(s); } if(ans.size()==0){ cout << \"0\" << endl; continue; } sort(ans.begin(),ans.end()); string res; int len = 0; for(string s:ans){ if(s.size()>len){ len = s.size(); res = s; } } cout << res << endl; } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"poj1797题解(最大生成树||最短路)","slug":"poj1797","date":"2018-09-28T02:07:30.000Z","updated":"2018-12-08T08:30:19.000Z","comments":true,"path":"2018/09/28/poj1797/","link":"","permalink":"https://www.djangoz.com/2018/09/28/poj1797/","excerpt":"","text":"\b 题意起点1到终点n有m条桥,每座桥都有\b允许最大的重量通过,现在求运输车能从1到n运输的最大\b重量是多少 解题思路相当于让选择的那条路最大,然后求这条路里的最小值,而且这个值\b比其他路的值都大。可用变形的dijkstra来求解,也可以用最大生成树来求解。 dijkstra解法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<map>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int inf = 1<<30;const int maxn = 1e6+5;struct Node { int to,val; bool operator<(const Node& a)const { return val < a.val; }};vector<Node> G[maxn];int dis[maxn];int dijkstra(int n){ memset(dis,-1,sizeof(dis)); int ans = inf; priority_queue<pii> Q; Q.push(pii(inf,1)); dis[1] = inf; while(!Q.empty()){ pii t = Q.top(); Q.pop(); int d = t.first; int u = t.second; if(u==n)break; if(dis[u] > d) continue; for(int i=0;i<G[u].size();i++){ Node e = G[u][i]; if(dis[e.to] < min(dis[u],e.val)){ dis[e.to] = min(dis[u],e.val); Q.push(pii(dis[e.to],e.to)); } } } return dis[n];}int main(int argc, char const *argv[]){ int T = 0; int Case = 1; scanf(\"%d\",&T); while(T--){ int n,m; scanf(\"%d%d\",&n,&m); for(int i=0;i<=n;i++)G[i].clear(); int x,y,val; for(int i=0;i<m;i++){ scanf(\"%d%d%d\",&x,&y,&val); Node t; t.to = y; t.val = val; G[x].push_back(t); t.to = x; G[y].push_back(t); } int ans = dijkstra(n); printf(\"Scenario #%d:\\n\",Case++); printf(\"%d\\n\\n\",ans); } return 0;} 最大生成树解法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061#include<vector>#include<algorithm>#include<cstdio>#include<iostream>#include<set>#include<cstring>#include<map>#include<queue>using namespace std;typedef long long ll;typedef pair<int,int> pii;typedef pair<int,pii> PII;const int maxn = 1e6+5;struct Node { int x,y,val; bool operator<(const Node& a)const { return val > a.val; }};Node G[maxn];int A[maxn];int Find(int n){ return n==A[n]?n:A[n]=Find(A[n]);}int main(int argc, char const *argv[]){ int T = 0; int Case = 1; scanf(\"%d\",&T); while(T--){ int n,m; scanf(\"%d%d\",&n,&m); for(int i=0;i<=n;i++){ A[i] = i; } for(int i=0;i<m;i++){ scanf(\"%d%d%d\",&G[i].x,&G[i].y,&G[i].val); } sort(G,G+m); int ans = 1<<30; for(int i=0;i<m;i++){ int t1 = Find(G[i].x); int t2 = Find(G[i].y); if(t1!=t2){ if(t1>t2)swap(t1,t2); A[t2] = t1; if(Find(1)==Find(n)){ //最先找到让1和n连通的路径就是答案 ans = G[i].val; break; } } } printf(\"Scenario #%d:\\n\",Case++); printf(\"%d\\n\\n\",ans); } return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"题解","slug":"题解","permalink":"https://www.djangoz.com/tags/题解/"}]},{"title":"打算写写ACM题解","slug":"writeACM","date":"2018-09-27T16:00:00.000Z","updated":"2018-12-06T06:59:48.000Z","comments":true,"path":"2018/09/28/writeACM/","link":"","permalink":"https://www.djangoz.com/2018/09/28/writeACM/","excerpt":"","text":"之前本来是没打算来写ACM训练相关的题目的,觉得这些题解网上一搜一大堆,自己写了也没用,但是现在想一想,没多久打完icpc现场赛后应该就要退坑了,现在写写近期的题解,以后也可以拿来怀恋一下这段刷题的时光,说不定以后要用到这些知识也可以拿来复习一下。ACM题解相关的博文也同步到博客园上去,地址","categories":[],"tags":[{"name":"闲言碎语","slug":"闲言碎语","permalink":"https://www.djangoz.com/tags/闲言碎语/"}]},{"title":"ACM图论常用模板(自用)","slug":"useful-model","date":"2018-08-13T16:00:00.000Z","updated":"2018-10-04T03:50:20.000Z","comments":true,"path":"2018/08/14/useful-model/","link":"","permalink":"https://www.djangoz.com/2018/08/14/useful-model/","excerpt":"","text":"一直想找个时间整理一下自己常用的模板,方便自己查找。图论还有很多算法,后期待完善。 最小生成树kruskal hdu1233也可以用贪心的方法,先定义一个数组,排序后并查集。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950#include<bits/stdc++.h>using namespace std;const int maxn = 20005;struct Node{ int x,y,val; Node(){} Node(int a,int b,int c):x(a),y(b),val(c){} bool operator<(const Node& a)const { return val > a.val; }};int B[maxn];int Find(int n){ return n == B[n] ? n:B[n]=Find(B[n]);}int main(){ // ios::sync_with_stdio(false); // cin.tie(0); int N = 0; while(scanf(\"%d\",&N)&&N){ priority_queue<Node> Q; int a,b,c; int T = N*(N-1)/2; for(int i=1;i<=T;i++){ B[i] = i; } for(int i=1;i<=T;i++){ scanf(\"%d%d%d\",&a,&b,&c); Q.push(Node(a,b,c)); } int cnt = 0; int ans = 0; while(!Q.empty()&&cnt<N-1){ Node t = Q.top();Q.pop(); int t1 = Find(t.x); int t2 = Find(t.y); if(t1!=t2){ B[t2] = t1; ans += t.val; cnt++; } } printf(\"%d\\n\",ans); } return 0;} prim算法大体上和Dijksra算法相似 poj1258123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657#include<iostream>#include<cstring>#include<vector>#include<queue>using namespace std;const int maxn = 105;typedef pair<int,int> pii;int G[maxn][maxn];int dis[maxn];int N = 0;bool vis[maxn];int prim(){ int ans = 0; memset(dis,0x7f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > Q; Q.push(pii(0,0)); dis[0] = 0; while(!Q.empty()){ pii t = Q.top(); Q.pop(); int u = t.first; int v = t.second; if(vis[v]) continue; vis[v] = 1; ans += u; for(int i=0;i<N;i++){ if(i!=v){ int d = G[v][i]; if(dis[i] > d&&!vis[i]){ //唯一和dijkstra算法不一样的地方 dis[i] = d; // Q.push(pii(dis[i],i)); } } } } return ans;}int main(int argc, char const *argv[]){ ios::sync_with_stdio(false); cin.tie(0); while(cin >> N){ for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { cin >> G[i][j]; } } cout << prim() << endl; } return 0;} 最短路dijkstra算法 poj2387用vecotr邻接表来表示图,适用于稀疏图优先队列版本的dijkstra算法。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354#include<iostream>#include<queue>#include<cstring>#include<vector>using namespace std;const int maxn = 10005;int T,N;vector<int> G[maxn];vector<int> D[maxn];int dis[maxn];const int inf = 1<<31;typedef pair<int,int> pii;int dijkstra(){ memset(dis,10,sizeof(dis)); bool vis[maxn]; memset(vis,0,sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > Q; Q.push(pii(0,1)); while(!Q.empty()){ pii t = Q.top(); Q.pop(); int d = t.first; int a = t.second; if(vis[a])continue; vis[a] = 1; for(int i=0;i<G[a].size();i++){ int j = G[a][i]; int di = D[a][i]; if(dis[j] > di + d){ dis[j] = di + d; Q.push(pii(dis[j],j)); } } } return dis[N];}int main(){ cin >> T >> N; int a,b,c; for(int i=0;i<T;i++){ cin >> a >> b >> c; G[a].push_back(b); G[b].push_back(a); D[a].push_back(c); D[b].push_back(c); } cout << dijkstra() << endl; return 0;} bellman_ford算法 poj3259前M条边是双向边,后W条边是单向边,然后直接用bellman_ford算法判负环(自己用spfa判负环 结果WA了,不知道是测试数据卡spfa还是我spfa写错了)。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#include<iostream>#include<algorithm>#include<cstring>#include<queue>#include<vector>using namespace std;const int maxn = 1005;typedef pair<int,int> pii;int dis[maxn];int N,M,W;int k=0;struct Edge { int to,from,cost;};Edge E[5000];bool bellman_Ford(){ memset(dis,0,sizeof(dis)); for(int i=1;i<=N;i++){ for(int j=0;j<k;j++){ Edge e = E[j]; if(dis[e.to] > dis[e.from] + e.cost){ dis[e.to] = dis[e.from] + e.cost; if(i==N) return false; } } } return true;}int main(int argc, char const *argv[]){ int T = 0; cin >> T; while(T--){ cin >> N >> M >> W; k = 0; int a,b,c; for(int i=0;i<M;i++){ cin >> E[k].from >> E[k].to >> E[k].cost; E[k+1].from = E[k].to; E[k+1].to = E[k].from; E[k+1].cost = E[k].cost; k += 2; } for(int i=0;i<W;i++){ cin >> E[k].from >> E[k].to >> E[k].cost; E[k].cost = -E[k].cost; k++; } bool ok = bellman_Ford(); if(ok){ cout << \"NO\" << endl; }else{ cout << \"YES\" << endl; } } return 0;} SPFA算法1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465#include<cstdio>using namespace std;struct node{int x; int value; int next;};node e[60000];int visited[1505],dis[1505],st[1505],queue[1000];int main(){ int n,m,u,v,w,start,h,r,cur; freopen(\"c.in\",\"r\",stdin); freopen(\"c.out\",\"w\",stdout); while(scanf(\"%d%d\",&n,&m)!=EOF) { for(int i=1;i<=1500;i++) {visited[i]=0; dis[i]=-1; st[i]=-1; //这个初始化给下边那个while循环带来影响 } for(int i=1;i<=m;i++) { scanf(\"%d%d%d\\n\",&u,&v,&w); e[i].x=v; //记录后继节点 相当于链表中的创建一个节点,并使得数据域先记录 e[i].value=w; e[i].next=st[u]; //记录顶点节点的某一个边表节点的下标,相当于在链表中吧该边表节点的next指针先指向他的后继边表节点 st[u]=i; //把该顶点的指针指向边表节点,相当于链表中的插入中,头结点的指针改变 } start=1; visited[start]=1; dis[start]=0; h=0; r=1; queue[r]=start; while(h!=r) { h=(h+1)%1000; cur=queue[h]; int tmp=st[cur]; visited[cur]=0; while(tmp!=-1) { if (dis[e[tmp].x]<dis[cur]+e[tmp].value) //改成大于号才对 { dis[e[tmp].x]=dis[cur]+e[tmp].value; if(visited[e[tmp].x]==0) { visited[e[tmp].x]=1; r=(r+1)%1000; queue[r]=e[tmp].x; } } tmp=e[tmp].next; } } printf(\"%d\\n\",dis[n]); } return 0; } Flody算法1234567891011for (int i = 0; i < n; i++) { // 初始化为0 for (int j = 0; j < n; j++) scanf(\"%lf\", &dis[i][j]); } for (int k = 0; k < n; k++) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]); } }} 网络流增广路算法 计蒜课排涝模板题EdmondsKarp也可用于二分图的最大基数匹配,在二分图的左边后右边都添加一个结点,然后分别和二分图两边的结点相连即可。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149#include <bits/stdc++.h>using namespace std;const int maxn = 205;const int INF = 1<<30;struct Edge{ int from,to,cap,flow; Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}};struct Dinic //更快{ int n,m,s,t; vector<Edge> edges; vector<int> G[maxn]; bool vis[maxn]; int d[maxn]; int cur[maxn]; void init(int n){ for(int i=0;i<n;i++) G[i].clear(); edges.clear(); } void AddEdge(int from,int to,int cap){ edges.push_back(Edge(from,to,cap,0)); edges.push_back(Edge(to,from,0,0)); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BFS() { memset(vis,0,sizeof(vis)); queue<int> Q; Q.push(s); d[s] = 0; vis[s] = 1; while(!Q.empty()){ int x = Q.front(); Q.pop(); for(int i=0;i<G[x].size();i++){ Edge& e = edges[G[x][i]]; if(!vis[e.to]&&e.cap > e.flow){ vis[e.to] = 1; d[e.to] = d[x]+1; Q.push(e.to); } } } return vis[t]; } int DFS(int x,int a){ if(x==t || a==0)return a; int flow = 0,f; for(int &i=cur[x];i<G[x].size();i++){ Edge& e = edges[G[x][i]]; if(d[x]+1==d[e.to] && (f=DFS(e.to,min(a,e.cap-e.flow)))>0){ e.flow += f; edges[G[x][i]^1].flow -= f; flow += f; a -= f; if(a==0)break; } } return flow; } int Maxflow(int s,int t){ this->s = s,this->t = t; int flow = 0; while(BFS()){ memset(cur,0,sizeof(cur)); flow += DFS(s,INF); } return flow; } };struct EdmondsKarp{ int n,m; vector<Edge> edges; vector<int> G[maxn]; int a[maxn]; int p[maxn]; void init(int n){ for(int i=0;i<n;i++) G[i].clear(); edges.clear(); } void AddEdge(int from,int to,int cap){ edges.push_back(Edge(from,to,cap,0)); edges.push_back(Edge(to,from,0,0)); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } int Maxflow(int s,int t){ int flow = 0; while(1){ memset(a,0,sizeof(a)); queue<int> Q; Q.push(s); a[s] = INF; while(!Q.empty()){ int x = Q.front();Q.pop(); for(int i=0;i<G[x].size();i++){ Edge& e = edges[G[x][i]]; if(!a[e.to]&&e.cap>e.flow){ p[e.to] = G[x][i]; a[e.to] = min(a[x],e.cap-e.flow); Q.push(e.to); } } if(a[t]) break; } if(!a[t])break; for(int u=t;u!=s;u=edges[p[u]].from){ edges[p[u]].flow += a[t]; edges[p[u]^1].flow -= a[t]; } flow += a[t]; } return flow; }};int main(){ ios::sync_with_stdio(false); cin.tie(0); int n,m; cin >> n >> m; EdmondsKarp s; int a,b,c; for(int i=0;i<n;i++){ cin >> a >> b >>c; s.AddEdge(a,b,c); } cout << s.Maxflow(1,m) <<endl;} 最小费用最大流12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#include<bits/stdc++.h>using namespace std;typedef long long ll;const int INF = 1<<30;const int maxn = 10005;struct Edge{ int from ,to,cap,flow,cost; Edge(int u,int v,int c,int f,int w):from(u),to(v),cap(c),flow(f),cost(w){}};struct MCMF{ int n,m; vector<Edge> edges; vector<int> G[maxn]; int inq[maxn]; //是否在队列中 int d[maxn]; //Bellman-Ford int p[maxn]; //上一条弧 int a[maxn]; //可改进量 void init(int n) { this-> n = n; for(int i=0;i<n;i++){ G[i].clear(); } edges.clear(); } void AddEdge(int from,int to,int cap,int cost) { edges.push_back(Edge(from,to,cap,0,cost)); edges.push_back(Edge(to,from,0,0,-cost)); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BellmanFord(int s,int t,int& flow,ll& cost) { for(int i=0;i<n;i++) d[i] = INF; memset(inq,0,sizeof(inq)); d[s] = 0;inq[s] = 1;p[s] = 0;a[s] = INF; queue<int> Q; Q.push(s); while(!Q.empty()){ int u = Q.front();Q.pop(); inq[u] = 0; for(int i=0;i<G[u].size();i++){ Edge& e = edges[G[u][i]]; if(e.cap > e.flow && d[e.to] > d[u] + e.cost){ d[e.to] = d[u] + e.cost; p[e.to] = G[u][i]; a[e.to] = min(a[u],e.cap - e.flow); if(!inq[e.to]){ Q.push(e.to); inq[e.to] = 1; } } } if(d[t] == INF) return false; flow += a[t]; cost += (ll)d[t] + a[t]; edges[p[u]].flow += a[t]; edges[p[u]^1].flow -= a[t]; } return true; } int MincostMaxflow(int s,int t,ll& cost){ int flow = 0;cost = 0; while(BellmanFord(s,t,flow,cost)); return flow; }}; 二分图二分图判断(交叉染色) leetcode 886 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960static int speed_up = []() { ios::sync_with_stdio(false); cin.tie(nullptr); return 0; }();const int MAX_N = 2005;int V;vector<int> G[MAX_N];int color[MAX_N]; bool dfs(int v, int c){ color[v] = c; for(int i = 0; i < G[v].size(); i++) { int j = G[v][i]; if(color[j] == c) return false; if(color[j] == 0 && !dfs(j, -c)) return false; } return true;} bool solve(){ for(int i = 1; i <= V; i++) { if(color[i] == 0) { if(!dfs(i,1)) { return false; } } } return true;}class Solution {public: bool possibleBipartition(int N, vector<vector<int>>& dislikes) { V = N; for(int i=0;i<=N;i++){ G[i].clear(); } memset(color,0,sizeof(color)); for(int i=0;i<dislikes.size();i++){ int u = dislikes[i][0]; int v = dislikes[i][1]; G[u].push_back(v); G[v].push_back(u); } return solve(); }}; 无向图割边割点和双连通分量 uva315123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109#include <bits/stdc++.h>using namespace std;#define mclear(x) memset((x), 0, sizeof((x)))typedef pair<int, int> pii;const int MAX = 5100;int n, m, deep;vector<int> path[MAX];int vis[MAX], low[MAX];vector<int> cutpoint; //割点vector<pii> bridge; //割边,桥int nbcc; //双连通分量数stack<pii> order;vector<int> bcc[MAX]; //双连通分量void dfs(int pos, int father){ int i, j, total = 0; bool cut = false; int reback = 0; //处理平行边 vis[pos] = low[pos] = deep++; int ls = path[pos].size(); for (j = 0; j < ls; j++) { i = path[pos][j]; if (i == father) reback++; if (vis[i] == 0) { pii e(pos, i); order.push(e); dfs(i, pos); if (low[i] >= vis[pos]) { nbcc++; bcc[nbcc].clear(); pii r; do { r = order.top(); order.pop(); bcc[nbcc].push_back(r.second); } while (e != r); bcc[nbcc].push_back(r.first); } total++; low[pos] = min(low[i], low[pos]); if ((vis[pos] == 1 && total > 1) || (vis[pos] != 1 && low[i] >= vis[pos])) cut = true; if (low[i] > vis[pos]) bridge.push_back(e); } else if (i != father) { low[pos] = min(vis[i], low[pos]); } } if (reback > 1) low[pos] = min(low[pos], vis[father]); if (cut) cutpoint.push_back(pos);}void find_cut(){ int i; mclear(vis); mclear(low); cutpoint.clear(); bridge.clear(); nbcc = 0; while (!order.empty()) order.pop(); for (i = 1; i <= n; i++) { if (vis[i] == 0) { deep = 1; dfs(i, -1); } }}int main(){ while (~scanf(\"%d\", &n) && n) { int a = 0; char c; vector<int> vet; for (int i = 0; i <= n; i++) { path[i].clear(); } int u = 0; while (scanf(\"%d\", &u) && u) { while (scanf(\"%c\", &c)&&c==' ') { scanf(\"%d\",&a); path[u].push_back(a); path[a].push_back(u); } } find_cut(); cout << cutpoint.size() << endl; } return 0;} 强连通分量SCC的Tarjan算法 poj12361234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586#include<iostream>#include<cstring>#include<queue>#include<cstdio>#include<stack>#include<algorithm>#include<vector>using namespace std;const int maxn = 105;vector<int> G[maxn];bool M[maxn][maxn];int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;int out[maxn],in[maxn];stack<int> S;int dfs(int u){ pre[u] = lowlink[u] = ++dfs_clock; S.push(u); for(int i=0;i<G[u].size();i++){ int v = G[u][i]; if(!pre[v]){ dfs(v); lowlink[u] = min(lowlink[u],lowlink[v]); }else if(!sccno[v]){ lowlink[u] = min(lowlink[u],pre[v]); } } if(lowlink[u] == pre[u]){ scc_cnt++; while(1){ int x = S.top();S.pop(); sccno[x] = scc_cnt; if(x==u)break; } }}void find_scc(int n){ dfs_clock = scc_cnt = 0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); memset(out,0,sizeof(out)); memset(in,0,sizeof(in)); for(int i=1;i<=n;i++){ if(!pre[i])dfs(i); } if(scc_cnt==1){ cout << \"1\\n0\\n\"; return; } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(M[i][j]&&sccno[i]!=sccno[j]){ out[sccno[i]]++; in[sccno[j]]++; } } } int ans1 = 0,ans2 = 0; for(int i=1;i<=scc_cnt;i++){ if(in[i]==0)ans1++; if(out[i]==0)ans2++; } cout << ans1 << endl << max(ans1,ans2) << endl;}int main(){ int N = 0; cin >> N; int a = 0; for(int i=1;i<=N;i++){ while(cin >> a&&a){ G[i].push_back(a); M[i][a] = 1; } } find_scc(N); return 0;}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"ACM","slug":"ACM","permalink":"https://www.djangoz.com/tags/ACM/"},{"name":"图论","slug":"图论","permalink":"https://www.djangoz.com/tags/图论/"},{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"https://www.djangoz.com/tags/算法与数据结构/"}]},{"title":"单例模式下多线程的同步问题","slug":"Singleton_sync","date":"2018-06-22T16:00:00.000Z","updated":"2018-06-24T03:02:06.000Z","comments":true,"path":"2018/06/23/Singleton_sync/","link":"","permalink":"https://www.djangoz.com/2018/06/23/Singleton_sync/","excerpt":"","text":"最近在看head first 设计模式,学习了单例模式的使用,自己在实际编程中也经常使用这种设计模式。在用swing练习编写贪吃蛇的小游戏的时候,打算将平时学习的东西运用到实际中去,发现了使用多线程很容易出错,对Java的多线程还是不熟悉啊。贪吃蛇中玩家控制的蛇是唯一的,于是就把蛇这个类弄成了单例模式那样,然后再开一个线程控制蛇的移动。1234567891011121314151617181920212223242526public class Snake { private static Snake snake; ... private Snake(){ ... new Thread(new Game()).start(); } public static Snake getInstance() { if(snake==null){ snake = new Snake(); System.out.println(\"test\"); } return snake; } ...}public class Game implements Runnable { @Override public void run() { Snake snake = Snake.getInstance(); if(snake.isrunning) snake.update(); } ...} 结果运行后控制台输出了多个test 出现这个现象我想到了多线程中snake不同步的问题,在Game这个线程中snake还没有被实例化,于是也new了一个对象出来,就有了两个Snake的对象,从而引发了问题,在head first 设计模式中说过,单例模式处理多线程的方法,利用双重检查加锁,首先检查是否实例已经创建了,如果尚未创建,“才”进行同步。这样一来,只有第一次会同步,减少使用同步,增加程序性能1234567891011121314151617public class Snake { private volatile static Snake snake; ... public static Snake getInstance() { if(snake==null){ //检查实例,如果不存在,就进入同步区块。 /*只有第一次才彻底执行这里的代码*/ synchronized (Snake.class){ if(snake==null) { //进入区块后,再检查一次,如果仍是null,才创建实例 snake = new Snake(); System.out.println(\"test\"); } } return snake; } ...} 修改后正常运行 源码地址","categories":[{"name":"java","slug":"java","permalink":"https://www.djangoz.com/categories/java/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"https://www.djangoz.com/tags/设计模式/"}]},{"title":"博客复活","slug":"blog_resurrection","date":"2018-06-10T16:00:00.000Z","updated":"2018-06-11T05:42:20.000Z","comments":true,"path":"2018/06/11/blog_resurrection/","link":"","permalink":"https://www.djangoz.com/2018/06/11/blog_resurrection/","excerpt":"","text":"前几周手痒把电脑装上hackintosh系统,在\b安装系统的时候本来以为一个盘分出几十G空间就可以了,结果要把整个盘都格式化才行,\b\b当时就直接把盘格式化了。\b\b装好系统后,才想起\b整个博客项目都在那个盘上面,以前想到md文件也没有\b备份,\b整个博客源文件都不见了。最近才有时间慢慢恢复博客,才发现文章写得也有\b十来篇了,\b\b\b\b\b恢复用htm\bl转md\b\u001c也挺麻烦的,现在明白了数据备份的重要性。现在博客\u001b终于成功复活,贴张装好hackintosh成功的图片","categories":[],"tags":[{"name":"闲言碎语","slug":"闲言碎语","permalink":"https://www.djangoz.com/tags/闲言碎语/"}]},{"title":"python实用小工具","slug":"useful_in-python","date":"2018-04-18T16:00:00.000Z","updated":"2018-06-10T14:10:50.000Z","comments":true,"path":"2018/04/19/useful_in-python/","link":"","permalink":"https://www.djangoz.com/2018/04/19/useful_in-python/","excerpt":"","text":"1. 简单下载服务器 在需要下载的路径中执行如下命令: 12345#python2python -m SimpleHTTPServer#python3python -m http.server 此进程会在端口8000监听,访问IP:8000即可浏览到当前目录的文件: 点击文件即可进行下载。 也可直接对文件进行wget: 1wget <your ip address>:/8000/filename 2. JSON解析小工具123456# echo '{"age":10, "name":"name1"}' | python -m json.tool{ "age": 10, "name": "name1"} 3. 非交互式导入包1python -c "import flask" 4. pip 常用操作1) 安装pip 1yum install -y python-pip 2) 查找包 1pip search flask 3) 安装指定版本包 1pip install flask==0.8 4) 删除包 1pip uninstall flask 5) 查看包信息 1pip show flask 6) 检查包依赖是否完整 1pip check flask 7) 查看已安装包列表 1pip list 8) 导出所有已安装包 1pip freeze > requirements.txt 9) 安装导出的所有包,可用于在其他服务器安装 1pip install -r requirements.txt 10) pip命令补全,tab键可补全。 12pip completion –bash >> ~/.profilesource ~/.profile 5. 加速pip安装 pip默认源地址pypi.python.org可能不稳定,可以用国内的阿里云源或者豆瓣的源代替,以豆瓣为例: 1pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask 每次都这样修改比较麻烦,可以直接修改pip的配置文件将源加入: 123mkdir -p ~/.pipcd ~/.pipvim pip.conf 加入如下内容: 1234[global]index-url = https://pypi.tuna.tsinghua.edu.cn/simple[install]trusted-host=mirrors.aliyun.com windows下,直接在user目录中创建一个pip目录,如:C:\\Users\\xx\\pip,新建文件pip.ini。内容同上。 6. ipython 增强型的python交互式工具。 12pip3 install ipythonipython","categories":[{"name":"python","slug":"python","permalink":"https://www.djangoz.com/categories/python/"}],"tags":[{"name":"小程序","slug":"小程序","permalink":"https://www.djangoz.com/tags/小程序/"}]},{"title":"在Ubuntu上通过nginx+gunicorn+https部署Flask应用接入微信公众号","slug":"flask_app","date":"2018-03-09T16:00:00.000Z","updated":"2018-12-26T07:35:37.000Z","comments":true,"path":"2018/03/10/flask_app/","link":"","permalink":"https://www.djangoz.com/2018/03/10/flask_app/","excerpt":"","text":"突然想开发一个微信公众号,就算没人关注也可以平时自己拿来使用,于是想到了用Flask做公众号的后端,用nginx来反向代理,并启用SSL/TLS。下面分节介绍搭建过程。 安装python环境自己平时用的python3,于是就用python3来搭的,ubuntu默认安装了python3,首先安装包管理12sudo apt install python3-pippip3 install --upgrade pip 然后安装virtualenv与其他库12345pip3 install virtualenvmkdir flask_appcd flask_appvirtualenv envsource env/bin/activate 创建requirements.txt文件如果想让Gunicorn支持异步 workers 的话需要安装一下三个python包(greenlet,eventlet,gevent),在清单文件中已给出,gunicorn还需要库函数 libevent(1.4.x or 2.0.4)下面是Flask常用的库1234567891011121314151617181920212223242526Flask==0.10.1Flask-Login==0.2.11Flask-Mail==0.9.1Flask-Moment==0.4.0Flask-PageDown==0.1.5Flask-SQLAlchemy==2.0Flask-Script==2.0.5Flask-WTF==0.10.2Flask-Cache==0.13.1Flask-Restless==0.15.0Flask-Uploads==0.1.3Jinja2==2.7.3Mako==1.0.0Markdown==2.5.1MarkupSafe==0.23SQLAlchemy==0.9.8WTForms==2.0.1Werkzeug==0.9.6html5lib==1.0b3itsdangerous==0.24six==1.8.0awesome-slugify==1.6gevent==1.2.2eventlet==0.22.1greenlet==0.4.13gunicorn==19.7.0 这么多要安装的库,一个一个得装非常麻烦,运行 (env) root@ubunt:~/flask_app# pip install -r requirements.txt 即可全部安装完成 项目文件接下来就是上传 Flask的项目文件,这里先使用最简单的Flask应用 (env) root@ubunt:~/flask_app# vim run.py 123456789from flask import Flask app = Flask(__name__) @app.route(\"/\")def hello(): return \"Hello World!\"if __name__ == '__main__': app.run(host='127.0.0.1',port=5000) 然后运行gunicorn12345678(env) root@ubunt:~/flask_app# gunicorn -w 4 -b 127.0.1:5000 run:app[2018-03-11 03:24:02 +0000] [19339] [INFO] Starting gunicorn 19.7.0[2018-03-11 03:24:02 +0000] [19339] [INFO] Listening at: http://127.0.0.1:5000 (19339)[2018-03-11 03:24:02 +0000] [19339] [INFO] Using worker: sync[2018-03-11 03:24:02 +0000] [19342] [INFO] Booting worker with pid: 19342[2018-03-11 03:24:02 +0000] [19343] [INFO] Booting worker with pid: 19343[2018-03-11 03:24:02 +0000] [19344] [INFO] Booting worker with pid: 19344[2018-03-11 03:24:02 +0000] [19345] [INFO] Booting worker with pid: 19345 安装配置nginx安装nginx (env) root@ubunt:~/flask_app# apt install nginx 修改nginx配置文件1234567891011121314(env) root@ubunt:~/flask_app# vim /etc/nginx/sites-available/default server { listen 80 default_server; listen [::]:80 default_server; location / { proxy_pass http://localhost:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect default; }} 修改配置文件后,重启nginx服务 (env) root@ubunt:~/flask_app# /etc/init.d/nginx restart 此时打开浏览器,输入http://你的服务器ip就可以看到运行结果 现在基本配置就已经弄好了 开启SSLnginx下配置ssl是很简单的,无论是去认证中心买SSL安全证书还是自签署证书,我自己是用的cloudflare的免费SSL证书,在cloudflare上的操作就不介绍了,在cloudflare上申请证书后会有一个公钥和密钥,把这两个保存到本地,接下来就直接介绍配置SSL我自己的证书是放在/etc/ssl/private/目录里面的 1234567891011121314151617server { listen 443 ssl default_server; listen [::]:443 ssl default_server; server_name ***; #你的域名; ssl on; ssl_certificate /etc/ssl/private/***.crt; #公钥地址 ssl_certificate_key /etc/ssl/private/***.key; #私钥地址 location / { proxy_pass http://localhost:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect default; }} 修改配置文件后,重启nginx服务 (env) root@ubunt:~/flask_app# /etc/init.d/nginx restart flask接入微信公众号 首先,需要写 flask 后台代码,以下以一个简单的后台做为示范。 (env) root@ubunt:~/flask_app# vim run.py 1234567891011121314151617181920212223242526272829303132333435# -*- coding=utf-8 -*-import timefrom flask import Flask,g,request,make_responseimport hashlibimport xml.etree.ElementTree as ETapp = Flask(__name__)@app.route('/',methods=['GET','POST'])def wechat_auth(): if request.method == 'GET': token='Your token' #微信配置所需的token data = request.args signature = data.get('signature','') timestamp = data.get('timestamp','') nonce = data.get('nonce','') echostr = data.get('echostr','') s = [timestamp,nonce,token] s.sort() s = ''.join(s) return make_response(echostr) else: rec = request.stream.read() xml_rec = ET.fromstring(rec) tou = xml_rec.find('ToUserName').text fromu = xml_rec.find('FromUserName').text content = xml_rec.find('Content').text xml_rep = \"<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content><FuncFlag>0</FuncFlag></xml>\" response = make_response(xml_rep % (fromu,tou,str(int(time.time())), content)) response.content_type='application/xml' return response return 'Hello weixin!'if __name__ == '__main__': app.run(debug=True) 然后运行gunicorn (env) root@ubunt:~/flask_app# gunicorn -w 4 -b 127.0.1:5000 run:app 注: 其中的1response = make_response(xml_rep % (fromu,tou,str(int(time.time())), content))# 是将xml_rep 的值按顺序返回给微信 这个是返回值。所以如果要做什么微信自动回复就将内容添到content中,然后微信前端就会回复给用户。注: 如果要消息加解密方式设为安全模式的话,参考官方文档部署完获得后台网址就可以进行下一步:开始填写微信接口。其他的都随意填,关键的两个值是下图的箭头所指。 填好后会出现配置成功的标志。 这时就能在微信上使用这个公众号了。 本文所实现的功能是你向公众号发布一条信息,公众号返回同样的信息给你。 微信上最后结果 参考链接: 在Centos使用Nginx,uWSGI部署Flask应用 centos下通过gunicorn+nginx+supervisor部署Flask项目 nginx配置ssl加密 阿里云部署 Flask + WSGI + Nginx 详解 Gunicorn快速入门 微信公众号开发 flask后台的快速部署","categories":[{"name":"python","slug":"python","permalink":"https://www.djangoz.com/categories/python/"}],"tags":[{"name":"运维","slug":"运维","permalink":"https://www.djangoz.com/tags/运维/"}]},{"title":"ADB 用法大全","slug":"awesome_adb","date":"2018-02-01T16:00:00.000Z","updated":"2018-06-11T04:55:26.000Z","comments":true,"path":"2018/02/02/awesome_adb/","link":"","permalink":"https://www.djangoz.com/2018/02/02/awesome_adb/","excerpt":"","text":"注: 本篇文章为GitHub转载过来,原地址 ADB,即 Android Debug Bridge,它是 Android 开发/测试人员不可替代的强大工具,也是 Android 设备玩家的好玩具。 持续更新中,欢迎提 PR 和 Issue 补充指正,觉得有用的可以将 此 GitHub 仓库 Star 收藏备用。 注: 有部分命令的支持情况可能与 Android 系统版本及定制 ROM 的实现有关。 基本用法命令语法adb 命令的基本语法如下: 1adb [-d|-e|-s <serialNumber>] <command> 如果只有一个设备/模拟器连接时,可以省略掉 [-d|-e|-s <serialNumber>] 这一部分,直接使用 adb <command>。 为命令指定目标设备如果有多个设备/模拟器连接,则需要为命令指定目标设备。 参数 含义 -d 指定当前唯一通过 USB 连接的 Android 设备为命令目标 -e 指定当前唯一运行的模拟器为命令目标 -s <serialNumber> 指定相应 serialNumber 号的设备/模拟器为命令目标 在多个设备/模拟器连接的情况下较常用的是 -s <serialNumber> 参数,serialNumber 可以通过 adb devices 命令获取。如: 123456$ adb devicesList of devices attachedcf264b8f deviceemulator-5554 device10.129.164.6:5555 device 输出里的 cf264b8f、emulator-5554 和 10.129.164.6:5555 即为 serialNumber。 比如这时想指定 cf264b8f 这个设备来运行 adb 命令获取屏幕分辨率: 1adb -s cf264b8f shell wm size 又如想给 10.129.164.6:5555 这个设备安装应用(这种形式的 serialNumber 格式为 <IP>:<Port>,一般为无线连接的设备或 Genymotion 等第三方 Android 模拟器): 1adb -s 10.129.164.6:5555 install test.apk 遇到多设备/模拟器的情况均使用这几个参数为命令指定目标设备,下文中为简化描述,不再重复。 启动/停止启动 adb server 命令: 1adb start-server (一般无需手动执行此命令,在运行 adb 命令时若发现 adb server 没有启动会自动调起。) 停止 adb server 命令: 1adb kill-server 查看 adb 版本命令: 1adb version 示例输出: 12Android Debug Bridge version 1.0.36Revision 8f855a3d9b35-android 以 root 权限运行 adbdadb 的运行原理是 PC 端的 adb server 与手机端的守护进程 adbd 建立连接,然后 PC 端的 adb client 通过 adb server 转发命令,adbd 接收命令后解析运行。 所以如果 adbd 以普通权限执行,有些需要 root 权限才能执行的命令无法直接用 adb xxx 执行。这时可以 adb shell 然后 su 后执行命令,也可以让 adbd 以 root 权限执行,这个就能随意执行高权限命令了。 命令: 1adb root 正常输出: 1restarting adbd as root 现在再运行 adb shell,看看命令行提示符是不是变成 # 了? 有些手机 root 后也无法通过 adb root 命令让 adbd 以 root 权限执行,比如三星的部分机型,会提示 adbd cannot run as root in production builds,此时可以先安装 adbd Insecure,然后 adb root 试试。 相应地,如果要恢复 adbd 为非 root 权限的话,可以使用 adb unroot 命令。 指定 adb server 的网络端口命令: 1adb -P <port> start-server 默认端口为 5037。 设备连接管理查询已连接设备/模拟器命令: 1adb devices 输出示例: 1234List of devices attachedcf264b8f deviceemulator-5554 device10.129.164.6:5555 device 输出格式为 [serialNumber] [state],serialNumber 即我们常说的 SN,state 有如下几种: offline —— 表示设备未连接成功或无响应。 device —— 设备已连接。注意这个状态并不能标识 Android 系统已经完全启动和可操作,在设备启动过程中设备实例就可连接到 adb,但启动完毕后系统才处于可操作状态。 no device —— 没有设备/模拟器连接。 以上输出显示当前已经连接了三台设备/模拟器,cf264b8f、emulator-5554 和 10.129.164.6:5555 分别是它们的 SN。从 emulator-5554 这个名字可以看出它是一个 Android 模拟器,而 10.129.164.6:5555 这种形为 <IP>:<Port> 的 serialNumber 一般是无线连接的设备或 Genymotion 等第三方 Android 模拟器。 常见异常输出: 没有设备/模拟器连接成功。 1List of devices attached 设备/模拟器未连接到 adb 或无响应。 12List of devices attachedcf264b8f offline USB 连接通过 USB 连接来正常使用 adb 需要保证几点: 硬件状态正常。 包括 Android 设备处于正常开机状态,USB 连接线和各种接口完好。 Android 设备的开发者选项和 USB 调试模式已开启。 可以到「设置」-「开发者选项」-「Android 调试」查看。 如果在设置里找不到开发者选项,那需要通过一个彩蛋来让它显示出来:在「设置」-「关于手机」连续点击「版本号」7 次。 设备驱动状态正常。 这一点貌似在 Linux 和 Mac OS X 下不用操心,在 Windows 下有可能遇到需要安装驱动的情况,确认这一点可以右键「计算机」-「属性」,到「设备管理器」里查看相关设备上是否有黄色感叹号或问号,如果没有就说明驱动状态已经好了。否则可以下载一个手机助手类程序来安装驱动先。 通过 USB 线连接好电脑和设备后确认状态。 1adb devices 如果能看到 1xxxxxx device 说明连接成功。 无线连接(需要借助 USB 线)除了可以通过 USB 连接设备与电脑来使用 adb,也可以通过无线连接——虽然连接过程中也有需要使用 USB 的步骤,但是连接成功之后你的设备就可以在一定范围内摆脱 USB 连接线的限制啦! 操作步骤: 将 Android 设备与要运行 adb 的电脑连接到同一个局域网,比如连到同一个 WiFi。 将设备与电脑通过 USB 线连接。 应确保连接成功(可运行 adb devices 看是否能列出该设备)。 让设备在 5555 端口监听 TCP/IP 连接: 1adb tcpip 5555 断开 USB 连接。 找到设备的 IP 地址。 一般能在「设置」-「关于手机」-「状态信息」-「IP地址」找到,也可以使用下文里 [查看设备信息 - IP 地址][1] 一节里的方法用 adb 命令来查看。 通过 IP 地址连接设备。 1adb connect <device-ip-address> 这里的 <device-ip-address> 就是上一步中找到的设备 IP 地址。 确认连接状态。 1adb devices 如果能看到 1<device-ip-address>:5555 device 说明连接成功。 如果连接不了,请确认 Android 设备与电脑是连接到了同一个 WiFi,然后再次执行 adb connect <device-ip-address> 那一步; 如果还是不行的话,通过 adb kill-server 重新启动 adb 然后从头再来一次试试。 断开无线连接 命令: 1adb disconnect <device-ip-address> 无线连接(无需借助 USB 线)注:需要 root 权限。 上一节「无线连接(需要借助 USB 线)」是官方文档里介绍的方法,需要借助于 USB 数据线来实现无线连接。 既然我们想要实现无线连接,那能不能所有步骤下来都是无线的呢?答案是能的。 在 Android 设备上安装一个终端模拟器。 已经安装过的设备可以跳过此步。我使用的终端模拟器下载地址是:Terminal Emulator for Android Downloads 将 Android 设备与要运行 adb 的电脑连接到同一个局域网,比如连到同一个 WiFi。 打开 Android 设备上的终端模拟器,在里面依次运行命令: 12susetprop service.adb.tcp.port 5555 找到 Android 设备的 IP 地址。 一般能在「设置」-「关于手机」-「状态信息」-「IP地址」找到,也可以使用下文里 [查看设备信息 - IP 地址][1] 一节里的方法用 adb 命令来查看。 在电脑上通过 adb 和 IP 地址连接 Android 设备。 1adb connect <device-ip-address> 这里的 <device-ip-address> 就是上一步中找到的设备 IP 地址。 如果能看到 connected to <device-ip-address>:5555 这样的输出则表示连接成功。 节注一: 有的设备,比如小米 5S + MIUI 8.0 + Android 6.0.1 MXB48T,可能在第 5 步之前需要重启 adbd 服务,在设备的终端模拟器上运行: 1restart adbd 如果 restart 无效,尝试以下命令: 12stop adbdstart adbd 应用管理查看应用列表查看应用列表的基本命令格式是 1adb shell pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER] 即在 adb shell pm list packages 的基础上可以加一些参数进行过滤查看不同的列表,支持的过滤参数如下: 参数 显示列表 无 所有应用 -f 显示应用关联的 apk 文件 -d 只显示 disabled 的应用 -e 只显示 enabled 的应用 -s 只显示系统应用 -3 只显示第三方应用 -i 显示应用的 installer -u 包含已卸载应用 <FILTER> 包名包含 <FILTER> 字符串 所有应用命令: 1adb shell pm list packages 输出示例: 12345678910111213package:com.android.smoketestpackage:com.example.android.livecubespackage:com.android.providers.telephonypackage:com.google.android.googlequicksearchboxpackage:com.android.providers.calendarpackage:com.android.providers.mediapackage:com.android.protipspackage:com.android.documentsuipackage:com.android.gallerypackage:com.android.externalstorage...// other packages here... 系统应用命令: 1adb shell pm list packages -s 第三方应用命令: 1adb shell pm list packages -3 包名包含某字符串的应用比如要查看包名包含字符串 mazhuang 的应用列表,命令: 1adb shell pm list packages mazhuang 当然也可以使用 grep 来过滤: 1adb shell pm list packages | grep mazhuang 安装 APK命令格式: 1adb install [-lrtsdg] <path_to_apk> 参数: adb install 后面可以跟一些可选参数来控制安装 APK 的行为,可用参数及含义如下: 参数 含义 -l 将应用安装到保护目录 /mnt/asec -r 允许覆盖安装 -t 允许安装 AndroidManifest.xml 里 application 指定 android:testOnly="true" 的应用 -s 将应用安装到 sdcard -d 允许降级覆盖安装 -g 授予所有运行时权限 运行命令后如果见到类似如下输出(状态为 Success)代表安装成功: 123[100%] /data/local/tmp/1.apk pkg: /data/local/tmp/1.apkSuccess 上面是当前最新版 v1.0.36 的 adb 的输出,会显示 push apk 文件到手机的进度百分比。 使用旧版本 adb 的输出则是这样的: 12312040 KB/s (22205609 bytes in 1.801s) pkg: /data/local/tmp/SogouInput_android_v8.3_sweb.apkSuccess 而如果状态为 Failure 则表示安装失败,比如: 123[100%] /data/local/tmp/map-20160831.apk pkg: /data/local/tmp/map-20160831.apkFailure [INSTALL_FAILED_ALREADY_EXISTS] 常见安装失败输出代码、含义及可能的解决办法如下: 输出 含义 解决办法 INSTALL_FAILED_ALREADY_EXISTS 应用已经存在,或卸载了但没卸载干净 adb install 时使用 -r 参数,或者先 adb uninstall <packagename> 再安装 INSTALL_FAILED_INVALID_APK 无效的 APK 文件 INSTALL_FAILED_INVALID_URI 无效的 APK 文件名 确保 APK 文件名里无中文 INSTALL_FAILED_INSUFFICIENT_STORAGE 空间不足 清理空间 INSTALL_FAILED_DUPLICATE_PACKAGE 已经存在同名程序 INSTALL_FAILED_NO_SHARED_USER 请求的共享用户不存在 INSTALL_FAILED_UPDATE_INCOMPATIBLE 以前安装过同名应用,但卸载时数据没有移除;或者已安装该应用,但签名不一致 先 adb uninstall <packagename> 再安装 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE 请求的共享用户存在但签名不一致 INSTALL_FAILED_MISSING_SHARED_LIBRARY 安装包使用了设备上不可用的共享库 INSTALL_FAILED_REPLACE_COULDNT_DELETE 替换时无法删除 INSTALL_FAILED_DEXOPT dex 优化验证失败或空间不足 INSTALL_FAILED_OLDER_SDK 设备系统版本低于应用要求 INSTALL_FAILED_CONFLICTING_PROVIDER 设备里已经存在与应用里同名的 content provider INSTALL_FAILED_NEWER_SDK 设备系统版本高于应用要求 INSTALL_FAILED_TEST_ONLY 应用是 test-only 的,但安装时没有指定 -t 参数 INSTALL_FAILED_CPU_ABI_INCOMPATIBLE 包含不兼容设备 CPU 应用程序二进制接口的 native code INSTALL_FAILED_MISSING_FEATURE 应用使用了设备不可用的功能 INSTALL_FAILED_CONTAINER_ERROR 1. sdcard 访问失败;2. 应用签名与 ROM 签名一致,被当作内置应用。 1. 确认 sdcard 可用,或者安装到内置存储;2. 打包时不与 ROM 使用相同签名。 INSTALL_FAILED_INVALID_INSTALL_LOCATION 1. 不能安装到指定位置;2. 应用签名与 ROM 签名一致,被当作内置应用。 1. 切换安装位置,添加或删除 -s 参数;2. 打包时不与 ROM 使用相同签名。 INSTALL_FAILED_MEDIA_UNAVAILABLE 安装位置不可用 一般为 sdcard,确认 sdcard 可用或安装到内置存储 INSTALL_FAILED_VERIFICATION_TIMEOUT 验证安装包超时 INSTALL_FAILED_VERIFICATION_FAILURE 验证安装包失败 INSTALL_FAILED_PACKAGE_CHANGED 应用与调用程序期望的不一致 INSTALL_FAILED_UID_CHANGED 以前安装过该应用,与本次分配的 UID 不一致 清除以前安装过的残留文件 INSTALL_FAILED_VERSION_DOWNGRADE 已经安装了该应用更高版本 使用 -d 参数 INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE 已安装 target SDK 支持运行时权限的同名应用,要安装的版本不支持运行时权限 INSTALL_PARSE_FAILED_NOT_APK 指定路径不是文件,或不是以 .apk 结尾 INSTALL_PARSE_FAILED_BAD_MANIFEST 无法解析的 AndroidManifest.xml 文件 INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION 解析器遇到异常 INSTALL_PARSE_FAILED_NO_CERTIFICATES 安装包没有签名 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES 已安装该应用,且签名与 APK 文件不一致 先卸载设备上的该应用,再安装 INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING 解析 APK 文件时遇到 CertificateEncodingException INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME manifest 文件里没有或者使用了无效的包名 INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID manifest 文件里指定了无效的共享用户 ID INSTALL_PARSE_FAILED_MANIFEST_MALFORMED 解析 manifest 文件时遇到结构性错误 INSTALL_PARSE_FAILED_MANIFEST_EMPTY 在 manifest 文件里找不到找可操作标签(instrumentation 或 application) INSTALL_FAILED_INTERNAL_ERROR 因系统问题安装失败 INSTALL_FAILED_USER_RESTRICTED 用户被限制安装应用 INSTALL_FAILED_DUPLICATE_PERMISSION 应用尝试定义一个已经存在的权限名称 INSTALL_FAILED_NO_MATCHING_ABIS 应用包含设备的应用程序二进制接口不支持的 native code INSTALL_CANCELED_BY_USER 应用安装需要在设备上确认,但未操作设备或点了取消 在设备上同意安装 INSTALL_FAILED_ACWF_INCOMPATIBLE 应用程序与设备不兼容 INSTALL_FAILED_TEST_ONLY APK 文件是使用 Android Studio 直接 RUN 编译出来的文件 通过 Gradle 的 assembleDebug 或 assembleRelease 重新编译,或者 Generate Signed APK does not contain AndroidManifest.xml 无效的 APK 文件 is not a valid zip file 无效的 APK 文件 Offline 设备未连接成功 先将设备与 adb 连接成功 unauthorized 设备未授权允许调试 error: device not found 没有连接成功的设备 先将设备与 adb 连接成功 protocol failure 设备已断开连接 先将设备与 adb 连接成功 Unknown option: -s Android 2.2 以下不支持安装到 sdcard 不使用 -s 参数 No space left on device 空间不足 清理空间 Permission denied … sdcard … sdcard 不可用 signatures do not match the previously installed version; ignoring! 已安装该应用且签名不一致 先卸载设备上的该应用,再安装 参考:PackageManager.java adb install 内部原理简介 adb install 实际是分三步完成: push apk 文件到 /data/local/tmp。 调用 pm install 安装。 删除 /data/local/tmp 下的对应 apk 文件。 所以,必要的时候也可以根据这个步骤,手动分步执行安装过程。 卸载应用命令: 1adb uninstall [-k] <packagename> <packagename> 表示应用的包名,-k 参数可选,表示卸载应用但保留数据和缓存目录。 命令示例: 1adb uninstall com.qihoo360.mobilesafe 表示卸载 360 手机卫士。 清除应用数据与缓存命令: 1adb shell pm clear <packagename> <packagename> 表示应用名包,这条命令的效果相当于在设置里的应用信息界面点击了「清除缓存」和「清除数据」。 命令示例: 1adb shell pm clear com.qihoo360.mobilesafe 表示清除 360 手机卫士的数据和缓存。 查看前台 Activity命令: 1adb shell dumpsys activity activities | grep mFocusedActivity 输出示例: 1mFocusedActivity: ActivityRecord{8079d7e u0 com.cyanogenmod.trebuchet/com.android.launcher3.Launcher t42} 其中的 com.cyanogenmod.trebuchet/com.android.launcher3.Launcher 就是当前处于前台的 Activity。 查看正在运行的 Services命令: 1adb shell dumpsys activity services [<packagename>] <packagename> 参数不是必须的,指定 <packagename> 表示查看与某个包名相关的 Services,不指定表示查看所有 Services。 <packagename> 不一定要给出完整的包名,比如运行 adb shell dumpsys activity services org.mazhuang,那么包名 org.mazhuang.demo1、org.mazhuang.demo2 和 org.mazhuang123 等相关的 Services 都会列出来。 查看应用详细信息命令: 1adb shell dumpsys package <packagename> 输出中包含很多信息,包括 Activity Resolver Table、Registered ContentProviders、包名、userId、安装后的文件资源代码等路径、版本信息、权限信息和授予状态、签名版本信息等。 <packagename> 表示应用包名。 输出示例: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475Activity Resolver Table: Non-Data Actions: android.intent.action.MAIN: 5b4cba8 org.mazhuang.guanggoo/.SplashActivity filter 5ec9dcc Action: \"android.intent.action.MAIN\" Category: \"android.intent.category.LAUNCHER\" AutoVerify=falseRegistered ContentProviders: org.mazhuang.guanggoo/com.tencent.bugly.beta.utils.BuglyFileProvider: Provider{7a3c394 org.mazhuang.guanggoo/com.tencent.bugly.beta.utils.BuglyFileProvider}ContentProvider Authorities: [org.mazhuang.guanggoo.fileProvider]: Provider{7a3c394 org.mazhuang.guanggoo/com.tencent.bugly.beta.utils.BuglyFileProvider} applicationInfo=ApplicationInfo{7754242 org.mazhuang.guanggoo}Key Set Manager: [org.mazhuang.guanggoo] Signing KeySets: 501Packages: Package [org.mazhuang.guanggoo] (c1d7f): userId=10394 pkg=Package{55f714c org.mazhuang.guanggoo} codePath=/data/app/org.mazhuang.guanggoo-2 resourcePath=/data/app/org.mazhuang.guanggoo-2 legacyNativeLibraryDir=/data/app/org.mazhuang.guanggoo-2/lib primaryCpuAbi=null secondaryCpuAbi=null versionCode=74 minSdk=15 targetSdk=25 versionName=1.1.74 splits=[base] apkSigningVersion=2 applicationInfo=ApplicationInfo{7754242 org.mazhuang.guanggoo} flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] privateFlags=[ RESIZEABLE_ACTIVITIES ] dataDir=/data/user/0/org.mazhuang.guanggoo supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity] timeStamp=2017-10-22 23:50:53 firstInstallTime=2017-10-22 23:50:25 lastUpdateTime=2017-10-22 23:50:55 installerPackageName=com.miui.packageinstaller signatures=PackageSignatures{af09595 [53c7caa2]} installPermissionsFixed=true installStatus=1 pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] requested permissions: android.permission.READ_PHONE_STATE android.permission.INTERNET android.permission.ACCESS_NETWORK_STATE android.permission.ACCESS_WIFI_STATE android.permission.READ_LOGS android.permission.WRITE_EXTERNAL_STORAGE android.permission.READ_EXTERNAL_STORAGE install permissions: android.permission.INTERNET: granted=true android.permission.ACCESS_NETWORK_STATE: granted=true android.permission.ACCESS_WIFI_STATE: granted=true User 0: ceDataInode=1155675 installed=true hidden=false suspended=false stopped=true notLaunched=false enabled=0 gids=[3003] runtime permissions: android.permission.READ_EXTERNAL_STORAGE: granted=true android.permission.READ_PHONE_STATE: granted=true android.permission.WRITE_EXTERNAL_STORAGE: granted=true User 999: ceDataInode=0 installed=false hidden=false suspended=false stopped=true notLaunched=true enabled=0 gids=[3003] runtime permissions:Dexopt state: [org.mazhuang.guanggoo] Instruction Set: arm64 path: /data/app/org.mazhuang.guanggoo-2/base.apk status: /data/app/org.mazhuang.guanggoo-2/oat/arm64/base.odex [compilation_filter=speed-profile, status=kOatUpToDa te] 查看应用安装路径命令: 1adb shell pm path <PACKAGE> 输出应用安装路径 输出示例: 123adb shell pm path ecarx.weatherpackage:/data/app/ecarx.weather-1.apk 与应用交互主要是使用 am <command> 命令,常用的 <command> 如下: command 用途 start [options] <INTENT> 启动 <INTENT> 指定的 Activity startservice [options] <INTENT> 启动 <INTENT> 指定的 Service broadcast [options] <INTENT> 发送 <INTENT> 指定的广播 force-stop <packagename> 停止 <packagename> 相关的进程 <INTENT> 参数很灵活,和写 Android 程序时代码里的 Intent 相对应。 用于决定 intent 对象的选项如下: 参数 含义 -a <ACTION> 指定 action,比如 android.intent.action.VIEW -c <CATEGORY> 指定 category,比如 android.intent.category.APP_CONTACTS -n <COMPONENT> 指定完整 component 名,用于明确指定启动哪个 Activity,如 com.example.app/.ExampleActivity <INTENT> 里还能带数据,就像写代码时的 Bundle 一样: 参数 含义 --esn <EXTRA_KEY> null 值(只有 key 名) `-e –es <EXTRA_KEY> <EXTRA_STRING_VALUE>` string 值 --ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> boolean 值 --ei <EXTRA_KEY> <EXTRA_INT_VALUE> integer 值 --el <EXTRA_KEY> <EXTRA_LONG_VALUE> long 值 --ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> float 值 --eu <EXTRA_KEY> <EXTRA_URI_VALUE> URI --ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE> component name --eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...] integer 数组 --ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...] long 数组 启动应用/ 调起 Activity命令格式: 1adb shell am start [options] <INTENT> 例如: 1adb shell am start -n com.tencent.mm/.ui.LauncherUI 表示调起微信主界面。 1adb shell am start -n org.mazhuang.boottimemeasure/.MainActivity --es \"toast\" \"hello, world\" 表示调起 org.mazhuang.boottimemeasure/.MainActivity 并传给它 string 数据键值对 toast - hello, world。 调起 Service命令格式: 1adb shell am startservice [options] <INTENT> 例如: 1adb shell am startservice -n com.tencent.mm/.plugin.accountsync.model.AccountAuthenticatorService 表示调起微信的某 Service。 另外一个典型的用例是如果设备上原本应该显示虚拟按键但是没有显示,可以试试这个: 1adb shell am startservice -n com.android.systemui/.SystemUIService 停止 Service命令格式: 1adb shell am stopservice [options] <INTENT> 发送广播命令格式: 1adb shell am broadcast [options] <INTENT> 可以向所有组件广播,也可以只向指定组件广播。 例如,向所有组件广播 BOOT_COMPLETED: 1adb shell am broadcast -a android.intent.action.BOOT_COMPLETED 又例如,只向 org.mazhuang.boottimemeasure/.BootCompletedReceiver 广播 BOOT_COMPLETED: 1adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n org.mazhuang.boottimemeasure/.BootCompletedReceiver 这类用法在测试的时候很实用,比如某个广播的场景很难制造,可以考虑通过这种方式来发送广播。 既能发送系统预定义的广播,也能发送自定义广播。如下是部分系统预定义广播及正常触发时机: action 触发时机 android.net.conn.CONNECTIVITY_CHANGE 网络连接发生变化 android.intent.action.SCREEN_ON 屏幕点亮 android.intent.action.SCREEN_OFF 屏幕熄灭 android.intent.action.BATTERY_LOW 电量低,会弹出电量低提示框 android.intent.action.BATTERY_OKAY 电量恢复了 android.intent.action.BOOT_COMPLETED 设备启动完毕 android.intent.action.DEVICE_STORAGE_LOW 存储空间过低 android.intent.action.DEVICE_STORAGE_OK 存储空间恢复 android.intent.action.PACKAGE_ADDED 安装了新的应用 android.net.wifi.STATE_CHANGE WiFi 连接状态发生变化 android.net.wifi.WIFI_STATE_CHANGED WiFi 状态变为启用/关闭/正在启动/正在关闭/未知 android.intent.action.BATTERY_CHANGED 电池电量发生变化 android.intent.action.INPUT_METHOD_CHANGED 系统输入法发生变化 android.intent.action.ACTION_POWER_CONNECTED 外部电源连接 android.intent.action.ACTION_POWER_DISCONNECTED 外部电源断开连接 android.intent.action.DREAMING_STARTED 系统开始休眠 android.intent.action.DREAMING_STOPPED 系统停止休眠 android.intent.action.WALLPAPER_CHANGED 壁纸发生变化 android.intent.action.HEADSET_PLUG 插入耳机 android.intent.action.MEDIA_UNMOUNTED 卸载外部介质 android.intent.action.MEDIA_MOUNTED 挂载外部介质 android.os.action.POWER_SAVE_MODE_CHANGED 省电模式开启 (以上广播均可使用 adb 触发) 强制停止应用命令: 1adb shell am force-stop <packagename> 命令示例: 1adb shell am force-stop com.qihoo360.mobilesafe 表示停止 360 安全卫士的一切进程与服务。 收紧内存命令:1adb shell am send-trim-memory <pid> <level> pid: 进程 IDlevel: HIDDEN、RUNNING_MODERATE、BACKGROUND、 RUNNING_LOW、MODERATE、RUNNING_CRITICAL、COMPLETE 命令示例: 1adb shell am send-trim-memory 12345 RUNNING_LOW 表示向 pid=12345 的进程,发出 level=RUNNING_LOW 的收紧内存命令。 文件管理复制设备里的文件到电脑命令: 1adb pull <设备里的文件路径> [电脑上的目录] 其中 电脑上的目录 参数可以省略,默认复制到当前目录。 例: 1adb pull /sdcard/sr.mp4 ~/tmp/ 小技巧:设备上的文件路径可能需要 root 权限才能访问,如果你的设备已经 root 过,可以先使用 adb shell 和 su 命令在 adb shell 里获取 root 权限后,先 cp /path/on/device /sdcard/filename 将文件复制到 sdcard,然后 adb pull /sdcard/filename /path/on/pc。 复制电脑里的文件到设备命令: 1adb push <电脑上的文件路径> <设备里的目录> 例: 1adb push ~/sr.mp4 /sdcard/ 小技巧:设备上的文件路径普通权限可能无法直接写入,如果你的设备已经 root 过,可以先 adb push /path/on/pc /sdcard/filename,然后 adb shell 和 su 在 adb shell 里获取 root 权限后,cp /sdcard/filename /path/on/device。 模拟按键/输入在 adb shell 里有个很实用的命令叫 input,通过它可以做一些有趣的事情。 input 命令的完整 help 信息如下: 12345678910111213141516171819202122Usage: input [<source>] <command> [<arg>...]The sources are: mouse keyboard joystick touchnavigation touchpad trackball stylus dpad gesture touchscreen gamepadThe commands and default sources are: text <string> (Default: touchscreen) keyevent [--longpress] <key code number or name> ... (Default: keyboard) tap <x> <y> (Default: touchscreen) swipe <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen) press (Default: trackball) roll <dx> <dy> (Default: trackball) 比如使用 adb shell input keyevent <keycode> 命令,不同的 keycode 能实现不同的功能,完整的 keycode 列表详见 KeyEvent,摘引部分我觉得有意思的如下: keycode 含义 3 HOME 键 4 返回键 5 打开拨号应用 6 挂断电话 24 增加音量 25 降低音量 26 电源键 27 拍照(需要在相机应用里) 64 打开浏览器 82 菜单键 85 播放/暂停 86 停止播放 87 播放下一首 88 播放上一首 122 移动光标到行首或列表顶部 123 移动光标到行末或列表底部 126 恢复播放 127 暂停播放 164 静音 176 打开系统设置 187 切换应用 207 打开联系人 208 打开日历 209 打开音乐 210 打开计算器 220 降低屏幕亮度 221 提高屏幕亮度 223 系统休眠 224 点亮屏幕 231 打开语音助手 276 如果没有 wakelock 则让系统休眠 下面是 input 命令的一些用法举例。 电源键命令: 1adb shell input keyevent 26 执行效果相当于按电源键。 菜单键命令: 1adb shell input keyevent 82 HOME 键命令: 1adb shell input keyevent 3 返回键命令: 1adb shell input keyevent 4 音量控制增加音量: 1adb shell input keyevent 24 降低音量: 1adb shell input keyevent 25 静音: 1adb shell input keyevent 164 媒体控制播放/暂停: 1adb shell input keyevent 85 停止播放: 1adb shell input keyevent 86 播放下一首: 1adb shell input keyevent 87 播放上一首: 1adb shell input keyevent 88 恢复播放: 1adb shell input keyevent 126 暂停播放: 1adb shell input keyevent 127 点亮/熄灭屏幕可以通过上文讲述过的模拟电源键来切换点亮和熄灭屏幕,但如果明确地想要点亮或者熄灭屏幕,那可以使用如下方法。 点亮屏幕: 1adb shell input keyevent 224 熄灭屏幕: 1adb shell input keyevent 223 滑动解锁如果锁屏没有密码,是通过滑动手势解锁,那么可以通过 input swipe 来解锁。 命令(参数以机型 Nexus 5,向上滑动手势解锁举例): 1adb shell input swipe 300 1000 300 500 参数 300 1000 300 500 分别表示起始点x坐标 起始点y坐标 结束点x坐标 结束点y坐标。 输入文本在焦点处于某文本框时,可以通过 input 命令来输入文本。 命令: 1adb shell input text hello 现在 hello 出现在文本框了。 查看日志Android 系统的日志分为两部分,底层的 Linux 内核日志输出到 /proc/kmsg,Android 的日志输出到 /dev/log。 Android 日志命令格式: 1[adb] logcat [<option>] ... [<filter-spec>] ... 常用用法列举如下: 按级别过滤日志Android 的日志分为如下几个优先级(priority): V —— Verbose(最低,输出得最多) D —— Debug I —— Info W —— Warning E —— Error F —— Fatal S —— Silent(最高,啥也不输出) 按某级别过滤日志则会将该级别及以上的日志输出。 比如,命令: 1adb logcat *:W 会将 Warning、Error、Fatal 和 Silent 日志输出。 (注: 在 macOS 下需要给 *:W 这样以 * 作为 tag 的参数加双引号,如 adb logcat "*:W",不然会报错 no matches found: *:W。) 按 tag 和级别过滤日志<filter-spec> 可以由多个 <tag>[:priority] 组成。 比如,命令: 1adb logcat ActivityManager:I MyApp:D *:S 表示输出 tag ActivityManager 的 Info 以上级别日志,输出 tag MyApp 的 Debug 以上级别日志,及其它 tag 的 Silent 级别日志(即屏蔽其它 tag 日志)。 日志格式可以用 adb logcat -v <format> 选项指定日志输出格式。 日志支持按以下几种 <format>: brief 默认格式。格式为: 1<priority>/<tag>(<pid>): <message> 示例: 1D/HeadsetStateMachine( 1785): Disconnected process message: 10, size: 0 process 格式为: 1<priority>(<pid>) <message> 示例: 1D( 1785) Disconnected process message: 10, size: 0 (HeadsetStateMachine) tag 格式为: 1<priority>/<tag>: <message> 示例: 1D/HeadsetStateMachine: Disconnected process message: 10, size: 0 raw 格式为: 1<message> 示例: 1Disconnected process message: 10, size: 0 time 格式为: 1<datetime> <priority>/<tag>(<pid>): <message> 示例: 108-28 22:39:39.974 D/HeadsetStateMachine( 1785): Disconnected process message: 10, size: 0 threadtime 格式为: 1<datetime> <pid> <tid> <priority> <tag>: <message> 示例: 108-28 22:39:39.974 1785 1832 D HeadsetStateMachine: Disconnected process message: 10, size: 0 long 格式为: 12[ <datetime> <pid>:<tid> <priority>/<tag> ]<message> 示例: 12[ 08-28 22:39:39.974 1785: 1832 D/HeadsetStateMachine ]Disconnected process message: 10, size: 0 指定格式可与上面的过滤同时使用。比如: 1adb logcat -v long ActivityManager:I *:S 清空日志1adb logcat -c 内核日志命令: 1adb shell dmesg 输出示例: 1234567<6>[14201.684016] PM: noirq resume of devices complete after 0.982 msecs<6>[14201.685525] PM: early resume of devices complete after 0.838 msecs<6>[14201.753642] PM: resume of devices complete after 68.106 msecs<4>[14201.755954] Restarting tasks ... done.<6>[14201.771229] PM: suspend exit 2016-08-28 13:31:32.679217193 UTC<6>[14201.872373] PM: suspend entry 2016-08-28 13:31:32.780363596 UTC<6>[14201.872498] PM: Syncing filesystems ... done. 中括号里的 [14201.684016] 代表内核开始启动后的时间,单位为秒。 通过内核日志我们可以做一些事情,比如衡量内核启动时间,在系统启动完毕后的内核日志里找到 Freeing init memory 那一行前面的时间就是。 查看设备信息型号命令: 1adb shell getprop ro.product.model 输出示例: 1Nexus 5 电池状况命令: 1adb shell dumpsys battery 输入示例: 123456789101112Current Battery Service state: AC powered: false USB powered: true Wireless powered: false status: 2 health: 2 present: true level: 44 scale: 100 voltage: 3872 temperature: 280 technology: Li-poly 其中 scale 代表最大电量,level 代表当前电量。上面的输出表示还剩下 44% 的电量。 屏幕分辨率命令: 1adb shell wm size 输出示例: 1Physical size: 1080x1920 该设备屏幕分辨率为 1080px * 1920px。 如果使用命令修改过,那输出可能是: 12Physical size: 1080x1920Override size: 480x1024 表明设备的屏幕分辨率原本是 1080px 1920px,当前被修改为 480px 1024px。 屏幕密度命令: 1adb shell wm density 输出示例: 1Physical density: 420 该设备屏幕密度为 420dpi。 如果使用命令修改过,那输出可能是: 12Physical density: 480Override density: 160 表明设备的屏幕密度原来是 480dpi,当前被修改为 160dpi。 显示屏参数命令: 1adb shell dumpsys window displays 输出示例: 1234WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays) Display: mDisplayId=0 init=1080x1920 420dpi cur=1080x1920 app=1080x1794 rng=1080x1017-1810x1731 deferred=false layoutNeeded=false 其中 mDisplayId 为 显示屏编号,init 是初始分辨率和屏幕密度,app 的高度比 init 里的要小,表示屏幕底部有虚拟按键,高度为 1920 - 1794 = 126px 合 42dp。 android_id命令: 1adb shell settings get secure android_id 输出示例: 151b6be48bac8c569 IMEI在 Android 4.4 及以下版本可通过如下命令获取 IMEI: 1adb shell dumpsys iphonesubinfo 输出示例: 123Phone Subscriber Info: Phone Type = GSM Device ID = 860955027785041 其中的 Device ID 就是 IMEI。 而在 Android 5.0 及以上版本里这个命令输出为空,得通过其它方式获取了(需要 root 权限): 123adb shellsuservice call iphonesubinfo 1 输出示例: 1234Result: Parcel( 0x00000000: 00000000 0000000f 00360038 00390030 '........8.6.0.9.' 0x00000010: 00350035 00320030 00370037 00350038 '5.5.0.2.7.7.8.5.' 0x00000020: 00340030 00000031 '0.4.1... ') 把里面的有效内容提取出来就是 IMEI 了,比如这里的是 860955027785041。 参考:adb shell dumpsys iphonesubinfo not working since Android 5.0 Lollipop Android 系统版本命令: 1adb shell getprop ro.build.version.release 输出示例: 15.0.2 IP 地址每次想知道设备的 IP 地址的时候都得「设置」-「关于手机」-「状态信息」-「IP地址」很烦对不对?通过 adb 可以方便地查看。 命令: 1adb shell ifconfig | grep Mask 输出示例: 12inet addr:10.130.245.230 Mask:255.255.255.252inet addr:127.0.0.1 Mask:255.0.0.0 那么 10.130.245.230 就是设备 IP 地址。 在有的设备上这个命令没有输出,如果设备连着 WiFi,可以使用如下命令来查看局域网 IP: 1adb shell ifconfig wlan0 输出示例: 1wlan0: ip 10.129.160.99 mask 255.255.240.0 flags [up broadcast running multicast] 或 12345678wlan0 Link encap:UNSPEC inet addr:10.129.168.57 Bcast:10.129.175.255 Mask:255.255.240.0 inet6 addr: fe80::66cc:2eff:fe68:b6b6/64 Scope: Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:496520 errors:0 dropped:0 overruns:0 frame:0 TX packets:68215 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:3000 RX bytes:116266821 TX bytes:8311736 如果以上命令仍然不能得到期望的信息,那可以试试以下命令(部分系统版本里可用): 1adb shell netcfg 输出示例: 123456789101112131415161718192021wlan0 UP 10.129.160.99/20 0x00001043 f8:a9:d0:17:42:4dlo UP 127.0.0.1/8 0x00000049 00:00:00:00:00:00p2p0 UP 0.0.0.0/0 0x00001003 fa:a9:d0:17:42:4dsit0 DOWN 0.0.0.0/0 0x00000080 00:00:00:00:00:00rmnet0 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rmnet1 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rmnet3 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rmnet2 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rmnet4 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rmnet6 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rmnet5 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rmnet7 DOWN 0.0.0.0/0 0x00000000 00:00:00:00:00:00rev_rmnet3 DOWN 0.0.0.0/0 0x00001002 4e:b7:e4:2e:17:58rev_rmnet2 DOWN 0.0.0.0/0 0x00001002 4e:f0:c8:bf:7a:cfrev_rmnet4 DOWN 0.0.0.0/0 0x00001002 a6:c0:3b:6b:c4:1frev_rmnet6 DOWN 0.0.0.0/0 0x00001002 66:bb:5d:64:2e:e9rev_rmnet5 DOWN 0.0.0.0/0 0x00001002 0e:1b:eb:b9:23:a0rev_rmnet7 DOWN 0.0.0.0/0 0x00001002 7a:d9:f6:81:40:5arev_rmnet8 DOWN 0.0.0.0/0 0x00001002 4e:e2:a9:bb:d0:1brev_rmnet0 DOWN 0.0.0.0/0 0x00001002 fe:65:d0:ca:82:a9rev_rmnet1 DOWN 0.0.0.0/0 0x00001002 da:d8:e8:4f:2e:fe 可以看到网络连接名称、启用状态、IP 地址和 Mac 地址等信息。 Mac 地址命令: 1adb shell cat /sys/class/net/wlan0/address 输出示例: 1f8:a9:d0:17:42:4d 这查看的是局域网 Mac 地址,移动网络或其它连接的信息可以通过前面的小节「IP 地址」里提到的 adb shell netcfg 命令来查看。 CPU 信息命令: 1adb shell cat /proc/cpuinfo 输出示例: 1234567891011121314151617181920212223Processor : ARMv7 Processor rev 0 (v7l)processor : 0BogoMIPS : 38.40processor : 1BogoMIPS : 38.40processor : 2BogoMIPS : 38.40processor : 3BogoMIPS : 38.40Features : swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivtCPU implementer : 0x51CPU architecture: 7CPU variant : 0x2CPU part : 0x06fCPU revision : 0Hardware : Qualcomm MSM 8974 HAMMERHEAD (Flattened Device Tree)Revision : 000bSerial : 0000000000000000 这是 Nexus 5 的 CPU 信息,我们从输出里可以看到使用的硬件是 Qualcomm MSM 8974,processor 的编号是 0 到 3,所以它是四核的,采用的架构是 ARMv7 Processor rev 0 (v71)。 内存信息命令: 1adb shell cat /proc/meminfo 输出示例: 12345678910111213141516171819202122232425262728293031323334353637MemTotal: 1027424 kBMemFree: 486564 kBBuffers: 15224 kBCached: 72464 kBSwapCached: 24152 kBActive: 110572 kBInactive: 259060 kBActive(anon): 79176 kBInactive(anon): 207736 kBActive(file): 31396 kBInactive(file): 51324 kBUnevictable: 3948 kBMlocked: 0 kBHighTotal: 409600 kBHighFree: 132612 kBLowTotal: 617824 kBLowFree: 353952 kBSwapTotal: 262140 kBSwapFree: 207572 kBDirty: 0 kBWriteback: 0 kBAnonPages: 265324 kBMapped: 47072 kBShmem: 1020 kBSlab: 57372 kBSReclaimable: 7692 kBSUnreclaim: 49680 kBKernelStack: 4512 kBPageTables: 5912 kBNFS_Unstable: 0 kBBounce: 0 kBWritebackTmp: 0 kBCommitLimit: 775852 kBCommitted_AS: 13520632 kBVmallocTotal: 385024 kBVmallocUsed: 61004 kBVmallocChunk: 209668 kB 其中,MemTotal 就是设备的总内存,MemFree 是当前空闲内存。 更多硬件与系统属性设备的更多硬件与系统属性可以通过如下命令查看: 1adb shell cat /system/build.prop 这会输出很多信息,包括前面几个小节提到的「型号」和「Android 系统版本」等。 输出里还包括一些其它有用的信息,它们也可通过 adb shell getprop <属性名> 命令单独查看,列举一部分属性如下: 属性名 含义 ro.build.version.sdk SDK 版本 ro.build.version.release Android 系统版本 ro.build.version.security_patch Android 安全补丁程序级别 ro.product.model 型号 ro.product.brand 品牌 ro.product.name 设备名 ro.product.board 处理器型号 ro.product.cpu.abilist CPU 支持的 abi 列表[节注一] persist.sys.isUsbOtgEnabled 是否支持 OTG dalvik.vm.heapsize 每个应用程序的内存上限 ro.sf.lcd_density 屏幕密度 节注一: 一些小厂定制的 ROM 可能修改过 CPU 支持的 abi 列表的属性名,如果用 ro.product.cpu.abilist 属性名查找不到,可以这样试试: 1adb shell cat /system/build.prop | grep ro.product.cpu.abi 示例输出: 12ro.product.cpu.abi=armeabi-v7aro.product.cpu.abi2=armeabi 修改设置注: 修改设置之后,运行恢复命令有可能显示仍然不太正常,可以运行 adb reboot 重启设备,或手动重启。 修改设置的原理主要是通过 settings 命令修改 /data/data/com.android.providers.settings/databases/settings.db 里存放的设置值。 分辨率命令: 1adb shell wm size 480x1024 表示将分辨率修改为 480px * 1024px。 恢复原分辨率命令: 1adb shell wm size reset 屏幕密度命令: 1adb shell wm density 160 表示将屏幕密度修改为 160dpi。 恢复原屏幕密度命令: 1adb shell wm density reset 显示区域命令: 1adb shell wm overscan 0,0,0,200 四个数字分别表示距离左、上、右、下边缘的留白像素,以上命令表示将屏幕底部 200px 留白。 恢复原显示区域命令: 1adb shell wm overscan reset 关闭 USB 调试模式命令: 1adb shell settings put global adb_enabled 0 恢复: 用命令恢复不了了,毕竟关闭了 USB 调试 adb 就连接不上 Android 设备了。 去设备上手动恢复吧:「设置」-「开发者选项」-「Android 调试」。 状态栏和导航栏的显示隐藏本节所说的相关设置对应 Cyanogenmod 里的「扩展桌面」。 命令: 1adb shell settings put global policy_control <key-values> <key-values> 可由如下几种键及其对应的值组成,格式为 <key1>=<value1>:<key2>=<value2>。 key 含义 immersive.full 同时隐藏 immersive.status 隐藏状态栏 immersive.navigation 隐藏导航栏 immersive.preconfirms ? 这些键对应的值可则如下值用逗号组合: value 含义 apps 所有应用 * 所有界面 packagename 指定应用 -packagename 排除指定应用 例如: 1adb shell settings put global policy_control immersive.full=* 表示设置在所有界面下都同时隐藏状态栏和导航栏。 1adb shell settings put global policy_control immersive.status=com.package1,com.package2:immersive.navigation=apps,-com.package3 表示设置在包名为 com.package1 和 com.package2 的应用里隐藏状态栏,在除了包名为 com.package3 的所有应用里隐藏导航栏。 实用功能屏幕截图截图保存到电脑: 1adb exec-out screencap -p > sc.png 如果 adb 版本较老,无法使用 exec-out 命令,这时候建议更新 adb 版本。无法更新的话可以使用以下麻烦点的办法: 先截图保存到设备里: 1adb shell screencap -p /sdcard/sc.png 然后将 png 文件导出到电脑: 1adb pull /sdcard/sc.png 可以使用 adb shell screencap -h 查看 screencap 命令的帮助信息,下面是两个有意义的参数及含义: 参数 含义 -p 指定保存文件为 png 格式 -d display-id 指定截图的显示屏编号(有多显示屏的情况下) 实测如果指定文件名以 .png 结尾时可以省略 -p 参数;否则需要使用 -p 参数。如果不指定文件名,截图文件的内容将直接输出到 stdout。 另外一种一行命令截图并保存到电脑的方法: Linux 和 Windows 1adb shell screencap -p | sed \"s/\\r$//\" > sc.png Mac OS X 1adb shell screencap -p | gsed \"s/\\r$//\" > sc.png 这个方法需要用到 gnu sed 命令,在 Linux 下直接就有,在 Windows 下 Git 安装目录的 bin 文件夹下也有。如果确实找不到该命令,可以下载 sed for Windows 并将 sed.exe 所在文件夹添加到 PATH 环境变量里。 而在 Mac 下使用系统自带的 sed 命令会报错: 1sed: RE error: illegal byte sequence 需要安装 gnu-sed,然后使用 gsed 命令: 1brew install gnu-sed 录制屏幕录制屏幕以 mp4 格式保存到 /sdcard: 1adb shell screenrecord /sdcard/filename.mp4 需要停止时按 Ctrl-C,默认录制时间和最长录制时间都是 180 秒。 如果需要导出到电脑: 1adb pull /sdcard/filename.mp4 可以使用 adb shell screenrecord --help 查看 screenrecord 命令的帮助信息,下面是常见参数及含义: 参数 含义 –size WIDTHxHEIGHT 视频的尺寸,比如 1280x720,默认是屏幕分辨率。 –bit-rate RATE 视频的比特率,默认是 4Mbps。 –time-limit TIME 录制时长,单位秒。 –verbose 输出更多信息。 重新挂载 system 分区为可写注:需要 root 权限。 /system 分区默认挂载为只读,但有些操作比如给 Android 系统添加命令、删除自带应用等需要对 /system 进行写操作,所以需要重新挂载它为可读写。 步骤: 进入 shell 并切换到 root 用户权限。 命令: 12adb shellsu 查看当前分区挂载情况。 命令: 1mount 输出示例: 12345678910111213141516171819202122232425rootfs / rootfs ro,relatime 0 0tmpfs /dev tmpfs rw,seclabel,nosuid,relatime,mode=755 0 0devpts /dev/pts devpts rw,seclabel,relatime,mode=600 0 0proc /proc proc rw,relatime 0 0sysfs /sys sysfs rw,seclabel,relatime 0 0selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0debugfs /sys/kernel/debug debugfs rw,relatime 0 0none /var tmpfs rw,seclabel,relatime,mode=770,gid=1000 0 0none /acct cgroup rw,relatime,cpuacct 0 0none /sys/fs/cgroup tmpfs rw,seclabel,relatime,mode=750,gid=1000 0 0none /sys/fs/cgroup/memory cgroup rw,relatime,memory 0 0tmpfs /mnt/asec tmpfs rw,seclabel,relatime,mode=755,gid=1000 0 0tmpfs /mnt/obb tmpfs rw,seclabel,relatime,mode=755,gid=1000 0 0none /dev/memcg cgroup rw,relatime,memory 0 0none /dev/cpuctl cgroup rw,relatime,cpu 0 0none /sys/fs/cgroup tmpfs rw,seclabel,relatime,mode=750,gid=1000 0 0none /sys/fs/cgroup/memory cgroup rw,relatime,memory 0 0none /sys/fs/cgroup/freezer cgroup rw,relatime,freezer 0 0/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ro,seclabel,relatime,data=ordered 0 0/dev/block/platform/msm_sdcc.1/by-name/userdata /data ext4 rw,seclabel,nosuid,nodev,relatime,noauto_da_alloc,data=ordered 0 0/dev/block/platform/msm_sdcc.1/by-name/cache /cache ext4 rw,seclabel,nosuid,nodev,relatime,data=ordered 0 0/dev/block/platform/msm_sdcc.1/by-name/persist /persist ext4 rw,seclabel,nosuid,nodev,relatime,data=ordered 0 0/dev/block/platform/msm_sdcc.1/by-name/modem /firmware vfat ro,context=u:object_r:firmware_file:s0,relatime,uid=1000,gid=1000,fmask=0337,dmask=0227,codepage=cp437,iocharset=iso8859-1,shortname=lower,errors=remount-ro 0 0/dev/fuse /mnt/shell/emulated fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0/dev/fuse /mnt/shell/emulated/0 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0 找到其中我们关注的带 /system 的那一行: 1/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ro,seclabel,relatime,data=ordered 0 0 重新挂载。 命令: 1mount -o remount,rw -t yaffs2 /dev/block/platform/msm_sdcc.1/by-name/system /system 这里的 /dev/block/platform/msm_sdcc.1/by-name/system 就是我们从上一步的输出里得到的文件路径。 如果输出没有提示错误的话,操作就成功了,可以对 /system 下的文件为所欲为了。 查看连接过的 WiFi 密码注:需要 root 权限。 命令: 123adb shellsucat /data/misc/wifi/*.conf 输出示例: 123456789101112131415161718network={ ssid=\"TP-LINK_9DFC\" scan_ssid=1 psk=\"123456789\" key_mgmt=WPA-PSK group=CCMP TKIP auth_alg=OPEN sim_num=1 priority=13893}network={ ssid=\"TP-LINK_F11E\" psk=\"987654321\" key_mgmt=WPA-PSK sim_num=1 priority=17293} ssid 即为我们在 WLAN 设置里看到的名称,psk 为密码,key_mgmt 为安全加密方式。 设置系统日期和时间注:需要 root 权限。 命令: 123adb shellsudate -s 20160823.131500 表示将系统日期和时间更改为 2016 年 08 月 23 日 13 点 15 分 00 秒。 重启手机命令: 1adb reboot 检测设备是否已 root命令: 12adb shellsu 此时命令行提示符是 $ 则表示没有 root 权限,是 # 则表示已 root。 使用 Monkey 进行压力测试Monkey 可以生成伪随机用户事件来模拟单击、触摸、手势等操作,可以对正在开发中的程序进行随机压力测试。 简单用法: 1adb shell monkey -p <packagename> -v 500 表示向 <packagename> 指定的应用程序发送 500 个伪随机事件。 Monkey 的详细用法参考 官方文档。 开启/关闭 WiFi注:需要 root 权限。 有时需要控制设备的 WiFi 状态,可以用以下指令完成。 开启 WiFi: 12adb rootadb shell svc wifi enable 关闭 WiFi: 12adb rootadb shell svc wifi disable 若执行成功,输出为空;若未取得 root 权限执行此命令,将执行失败,输出 Killed。 刷机相关命令重启到 Recovery 模式命令: 1adb reboot recovery 从 Recovery 重启到 Android命令: 1adb reboot 重启到 Fastboot 模式命令: 1adb reboot bootloader 通过 sideload 更新系统如果我们下载了 Android 设备对应的系统更新包到电脑上,那么也可以通过 adb 来完成更新。 以 Recovery 模式下更新为例: 重启到 Recovery 模式。 命令: 1adb reboot recovery 在设备的 Recovery 界面上操作进入 Apply update-Apply from ADB。 注:不同的 Recovery 菜单可能与此有差异,有的是一级菜单就有 Apply update from ADB。 通过 adb 上传和更新系统。 命令: 1adb sideload <path-to-update.zip> 安全相关命令启用/禁用 SELinux启用 SELinux 12adb rootadb shell setenforce 1 禁用 SELinux 12adb rootadb shell setenforce 0 启用/禁用 dm_verity启用 dm_verity 12adb rootadb enable-verity 禁用 dm_verity 12adb rootadb disable-verity 更多 adb shell 命令Android 系统是基于 Linux 内核的,所以 Linux 里的很多命令在 Android 里也有相同或类似的实现,在 adb shell 里可以调用。本文档前面的部分内容已经用到了 adb shell 命令。 查看进程命令: 1adb shell ps 输出示例: 12345678USER PID PPID VSIZE RSS WCHAN PC NAMEroot 1 0 8904 788 ffffffff 00000000 S /initroot 2 0 0 0 ffffffff 00000000 S kthreadd...u0_a71 7779 5926 1538748 48896 ffffffff 00000000 S com.sohu.inputmethod.sogou:classicu0_a58 7963 5926 1561916 59568 ffffffff 00000000 S org.mazhuang.boottimemeasure...shell 8750 217 10640 740 00000000 b6f28340 R ps 各列含义: 列名 含义 USER 所属用户 PID 进程 ID PPID 父进程 ID NAME 进程名 查看实时资源占用情况命令: 1adb shell top 输出示例: 1234567891011121314User 0%, System 6%, IOW 0%, IRQ 0%User 3 + Nice 0 + Sys 21 + Idle 280 + IOW 0 + IRQ 0 + SIRQ 3 = 307 PID PR CPU% S #THR VSS RSS PCY UID Name 8763 0 3% R 1 10640K 1064K fg shell top 131 0 3% S 1 0K 0K fg root dhd_dpc 6144 0 0% S 115 1682004K 115916K fg system system_server 132 0 0% S 1 0K 0K fg root dhd_rxf 1731 0 0% S 6 20288K 788K fg root /system/bin/mpdecision 217 0 0% S 6 18008K 356K fg shell /sbin/adbd ... 7779 2 0% S 19 1538748K 48896K bg u0_a71 com.sohu.inputmethod.sogou:classic 7963 0 0% S 18 1561916K 59568K fg u0_a58 org.mazhuang.boottimemeasure ... 各列含义: 列名 含义 PID 进程 ID PR 优先级 CPU% 当前瞬间占用 CPU 百分比 S 进程状态(R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程) #THR 线程数 VSS Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) RSS Resident Set Size 实际使用物理内存(包含共享库占用的内存) PCY 调度策略优先级,SP_BACKGROUND/SPFOREGROUND UID 进程所有者的用户 ID NAME 进程名 top 命令还支持一些命令行参数,详细用法如下: 1234567Usage: top [ -m max_procs ] [ -n iterations ] [ -d delay ] [ -s sort_column ] [ -t ] [ -h ] -m num 最多显示多少个进程 -n num 刷新多少次后退出 -d num 刷新时间间隔(单位秒,默认值 5) -s col 按某列排序(可用 col 值:cpu, vss, rss, thr) -t 显示线程信息 -h 显示帮助文档 查看进程 UID有两种方案: adb shell dumpsys package <packagename> | grep userId= 如: 12$ adb shell dumpsys package org.mazhuang.guanggoo | grep userId= userId=10394 通过 ps 命令找到对应进程的 pid 之后 adb shell cat /proc/<pid>/status | grep Uid 如: 123456$ adb shellgemini:/ $ ps | grep org.mazhuang.guanggoou0_a394 28635 770 1795812 78736 SyS_epoll_ 0000000000 S org.mazhuang.guanggoogemini:/ $ cat /proc/28635/status | grep UidUid: 10394 10394 10394 10394gemini:/ $ 其它如下是其它常用命令的简单描述,前文已经专门讲过的命令不再额外说明: 命令 功能 cat 显示文件内容 cd 切换目录 chmod 改变文件的存取模式/访问权限 df 查看磁盘空间使用情况 grep 过滤输出 kill 杀死指定 PID 的进程 ls 列举目录内容 mount 挂载目录的查看和管理 mv 移动或重命名文件 ps 查看正在运行的进程 rm 删除文件 top 查看进程的资源占用情况 常见问题启动 adb server 失败出错提示 1error: protocol fault (couldn't read status): No error 可能原因 adb server 进程想使用的 5037 端口被占用。 解决方案 找到占用 5037 端口的进程,然后终止它。以 Windows 下为例: 12345netstat -ano | findstr LISTENING...TCP 0.0.0.0:5037 0.0.0.0:0 LISTENING 1548... 这里 1548 即为进程 ID,用命令结束该进程: 1taskkill /PID 1548 然后再启动 adb 就没问题了。 com.android.ddmlib.AdbCommandRejectedException在 Android Studio 里新建一个模拟器,但是用 adb 一直连接不上,提示: 1234com.android.ddmlib.AdbCommandRejectedException: device unauthorized.This adb server's $ADB_VENDOR_KEYS is not setTry 'adb kill-server' if that seems wrong.Otherwise check for a confirmation dialog on your device. 在手机上安装一个终端然后执行 su 提示没有该命令,这不正常。 于是删除该模拟器后重新下载安装一次,这次就正常了。 adb 的非官方实现 fb-adb - A better shell for Android devices (for Mac). 相关命令 aapt am dumsys pm uiautomator 致谢感谢朋友们无私的分享与补充(排名不分先后)。 zxning,linhua55,codeskyblue,seasonyuu,fan123199,zhEdward,0x8BADFOOD,keith666666,shawnlinboy,s-xq,lucky9322。 参考链接 Android Debug Bridge ADB Shell Commands logcat Command-line Tool Android ADB命令大全 adb 命令行的使用记录 Android ADB命令大全(通过ADB命令查看wifi密码、MAC地址、设备信息、操作文件、查看文件、日志信息、卸载、启动和安装APK等) 那些做Android开发必须知道的ADB命令 adb shell top 像高手一样使用ADB命令行(2)","categories":[{"name":"Android","slug":"Android","permalink":"https://www.djangoz.com/categories/Android/"}],"tags":[{"name":"小技巧","slug":"小技巧","permalink":"https://www.djangoz.com/tags/小技巧/"}]},{"title":"基于图的bfs的电影天堂爬虫","slug":"Movie_heaven-crawler","date":"2017-12-30T16:00:00.000Z","updated":"2018-06-10T13:53:34.000Z","comments":true,"path":"2017/12/31/Movie_heaven-crawler/","link":"","permalink":"https://www.djangoz.com/2017/12/31/Movie_heaven-crawler/","excerpt":"","text":"最近看算法(第四版) 一书中图这一章的时候,发现网页也是图,网页之间的超链接就是图节点的连线,由此想到了用广度优先算法来遍历网站,爬取想要的信息。 GitHub地址,目录里面的movies.txt是在服务器上面跑了几个小时后,爬取了差不多三万个资源下载地址 分析网页源码 由图可以看出电影天堂所有电影的链接都是以’/html….html’来表示的,于是就用正则表达式来匹配 算法与数据结构小程序里面用队列来实现bfs,并用set来储存每一个链接,这样就达到了防止重复爬取链接,使程序陷入死循环了 匹配下载链接 当爬虫到了电影页面,也用正则表达式来匹配ftp链接 运行1python3 main.py 源码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869import re,requests,queueLINK = set()List = []times = 10009970 #爬取资源的次数URL = \"http://www.dytt8.net\"headers = { 'Referer':'http://www.dytt8.net/', 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36'}def getftp(link): try: global num r = requests.get(URL+link,headers=headers) r.encoding=r.apparent_encoding web = r.text movies = re.findall(r'\"(ftp[^\\'\"]+)\"',web) name = re.search('<title>.+《(.+)》.+<\\/title>',web).group(1) tplt = \"{0:{2}^10}\\t{1:{2}^90}\\n\" #定义格式化字符串 for movie in movies: List.append(tplt.format(name,movie,chr(12288))) num += 1 print(num) print(movie,name) except: print(\"error getftp\") passnum = 0def bfs(url): Q = queue.Queue() #定义一个队列 Q.put(url) global num while not Q.empty(): try: url = Q.get() r = requests.get(URL+url,headers=headers) r.encoding=r.apparent_encoding text = r.text links = re.findall(r'[^\\'\"<>]+\\.html',text) except: continue for link in links: if link in LINK: continue getftp(link) LINK.add(link) Q.put(link) if num>times: returndef main(): #开始爬取的链接 url = '/plus/sitemap.html' bfs(url) #写入文件 with open('movies.txt','w+',encoding='utf-8') as movies: for strs in List: movies.write(strs) with open('urls.txt','w+',encoding='utf-8') as url_file: for link in LINK: url_file.write(URL+link+'\\n')if __name__ == '__main__': main()","categories":[{"name":"python","slug":"python","permalink":"https://www.djangoz.com/categories/python/"}],"tags":[{"name":"小技巧","slug":"小技巧","permalink":"https://www.djangoz.com/tags/小技巧/"}]},{"title":"简单地用python玩了一下钓鱼网站","slug":"play","date":"2017-11-30T16:00:00.000Z","updated":"2018-06-10T13:49:58.000Z","comments":true,"path":"2017/12/01/play/","link":"","permalink":"https://www.djangoz.com/2017/12/01/play/","excerpt":"","text":"最近在空间里好友发了个遇到钓鱼网站的说说然后闲得无聊用python玩了一下这个网站。(本人水平比较渣,勿喷) 打开这个网站显示的是登陆QQ邮箱的钓鱼网站 然后打开浏览器的开发者工具先随便输入一个账号密码,然后抓包,查到post的数据格式和目标网页 在图中可以看到post上去的网址data的格式也知道了,就可以用python模拟post数据了,给网站提交点垃圾信息,以假乱真,这个还可以用IP代理和多线程来搞,博主现在还没达到那种级别,现在就是练练手 贴一下代码1234567891011121314151617181920212223242526272829303132333435363738import requests,randomtimes = 100 #设置提交次数,我这里就做一下测试,提交的比较少url = 'http://jiayaun.plshw.cn/mail/save.asp'headers = { \"User-Agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36\", \"Referer\":\"http://jiayaun.plshw.cn/mail\",}passwd = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-=!@#$%^&*()\"id = \"0123456789\"def passwd_generator(): #随机生成10-20位的密码 return ''.join(random.choice(passwd) for _ in range(random.randint(10,20)))def id_generator(): #随机生成9位的QQ号码 return ''.join(random.choice(id) for _ in range(9))def main(): time = 1 for i in range(times): try: data = { \"u\":id_generator(), \"p\":passwd_generator(), \"verifycode\":\"\" } request = requests.post(url,headers = headers,data=data) if request.status_code == 200: print(\"已成功发送\"+str(time)+\"次\") time += 1 else: print(\"ERROR\") except: continue if __name__ == '__main__': main() 跑完代码又去查了一下这个域名的信息 通过域名信息留下的邮箱反查 这里面域名比较多,就没一个一个去尝试了,这里面应该还有其他的钓鱼网站 ping了一下这个域名得到IP地址然后用nmap扫了一下该ip服务器的端口 最后用DOS玩了一下(第一次用这个)弄的是钓鱼网站,应该不算违法吧,我可是遵纪守法的好公民,不会干坏事的( ̄y▽, ̄)╭","categories":[{"name":"python","slug":"python","permalink":"https://www.djangoz.com/categories/python/"}],"tags":[{"name":"小技巧","slug":"小技巧","permalink":"https://www.djangoz.com/tags/小技巧/"}]},{"title":"IDEA 查看类UML关系图","slug":"idea_uml","date":"2017-11-18T16:00:00.000Z","updated":"2018-06-10T13:44:32.000Z","comments":true,"path":"2017/11/19/idea_uml/","link":"","permalink":"https://www.djangoz.com/2017/11/19/idea_uml/","excerpt":"","text":"说明平时写java代码的时候,代码经常会显得很长,很不容易看出Java类之间的关系后来发现IDEA有一个很棒的功能,可以直接根据代码来查看改文件内的类UML关系图 先去设置里面打开查看UML功能setting->plugins->搜索UML即可把搜索出来这个插件选上 查看java源代码内的类关系UML图双击shift键打开keymap,然后搜索UML,选择Show Local Changes as UML直接按快捷键Ctrl+Alt+Shift+D也可以 在UML图内点击鼠标右键,Show Categories,可切换查看各个类的属性和概括图 查看类UML图可以放大觉得生成的UML图小?这都不是问题,按Alt键,即可放大查看UML图 查看整个文件夹下的所有java类UML关系图写java的时候通常不会把所有类放在一个源文件里面,这时候就需要查看整个文件夹内的Java类关系图了而IDEA一样能做到,在左边的Project视图里面鼠标右键将要查看的文件夹,选择Diagrams->Show Diagrams->Java Class Diagrams即可","categories":[{"name":"java","slug":"java","permalink":"https://www.djangoz.com/categories/java/"}],"tags":[{"name":"小技巧","slug":"小技巧","permalink":"https://www.djangoz.com/tags/小技巧/"}]},{"title":"湖北工业大学教务系统查分,查课小程序","slug":"Login_hbut","date":"2017-10-26T16:00:00.000Z","updated":"2018-06-10T13:38:38.000Z","comments":true,"path":"2017/10/27/Login_hbut/","link":"","permalink":"https://www.djangoz.com/2017/10/27/Login_hbut/","excerpt":"","text":"首先贴下github项目地址 (如果觉得这个程序好,求个star) 运行该小程序需安装requests和bs4库,还需要安装能操作excel的xlwt库 123pip install requestspip install bs4pip install xlwt 现在该程序不能自动识别验证码,需手动在控制台输入验证码, 现可免输入验证码即可登陆(需先在github上获取该库,然后使用分支nocode)使用命令即可 1git checkout nocode 然后输入学号和密码即可查询,作者本人大二,所以现在可支持查询大一两个学期和大二上的成绩,成绩会自动保存在本地。由于课表不好格式化输出,如需查课表,程序会生成一个excel文件,然后直接查看即可 思路首先用requests库模拟登陆学校的教务系统,然后再进入成绩查询的页面,得到该页面的html源码,用BeatufulSoup来解析页面,提取出每门课的学分和成绩用列表来储存,最后格式化输出,并将输出写入文件。我把直接用函数来表示每个步骤,自我感觉思路比较清晰 源码查分小程序12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091import requestsfrom bs4 import BeautifulSoup,elementimport oslogin_url = 'http://run.hbut.edu.cn/Account/LogOn'checkimg = 'http://run.hbut.edu.cn/Account/GetValidateCode'StuGrade = 'http://run.hbut.edu.cn/StuGrade/Index'g_20171 = '?SemesterName=20171&SemesterNameStr=2017学年%20第一学期'g_20162 = '?SemesterName=20162&SemesterNameStr=2016学年%20第二学期'g_20161 = '?SemesterName=20161&SemesterNameStr=2016学年%20第一学期'file = open('grade.txt','w+')session = requests.Session()headers = { 'Referer' : 'http://run.hbut.edu.cn/', \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36\",}#获取登陆验证码def GetValidateCode(): checkcodecontent = session.get(checkimg,headers=headers) with open('checkcode.jpg','wb') as f: f.write(checkcodecontent.content) print('验证码已写入到本地!') os.startfile(\"checkcode.jpg\") checkcode = input(\"请输入验证码:\") payload = { 'Role':'Student', 'UserName': input('请输入账号:'), 'Password': input('请输入密码:'), 'ValidateCode': checkcode } return payload#获取课程成绩页面def getHtmlText(SemesterName,payload): respose = session.post(login_url,headers=headers,data=payload) print('服务器端返回码:',respose.status_code) if SemesterName is '1': SemesterName = g_20161 if SemesterName is '2': SemesterName = g_20162 if SemesterName is '3': SemesterName = g_20171 grade_url = StuGrade+SemesterName stugrade = session.get(grade_url,headers=headers) return stugrade.text #提取各科成绩def GetFromText(txt): form = [] soup = BeautifulSoup(txt,'html.parser') for tr in soup.find('table').children: try: if isinstance(tr,element.Tag): tds = tr('td') Class = tds[1].string #课程 Credit = tds[4].string #学分 Grade = tds[5].string #成绩 form.append([''.join(Class.split()),''.join(Credit.split()),''.join(Grade.split())]) except: continue return form#格式输出各科成绩def printgrade(ulist,num): tplt = \"{0:{3}^25}\\t{1:^10}\\t{2:^10}\\n\" print(tplt.format(\"课程\",\"学分\",\"成绩\",chr(12288))) file.write(tplt.format(\"课程\",\"学分\",\"成绩\",chr(12288))) for i in range(num): u = ulist[i] print(tplt.format(u[0],u[1],u[2],chr(12288))) file.write(tplt.format(u[0],u[1],u[2],chr(12288)))def main(): try: payload = GetValidateCode() print('16学年第一学期: 1') print('16学年第二学期: 2') print('17学年第一学期: 3') SenesterName = input('请输入查询的学期:') txt = getHtmlText(SenesterName,payload) form = GetFromText(txt) printgrade(form,len(form)); except : print(\"Error\") finally: file.close()main() 查课小程序123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687import requestsfrom bs4 import BeautifulSoupimport xlwtimport oslogin_url = 'http://run.hbut.edu.cn/Account/LogOn'checkimg = 'http://run.hbut.edu.cn/Account/GetValidateCode'Schedule = 'http://run.hbut.edu.cn/ArrangeTask/MyselfSchedule'workbook = xlwt.Workbook(encoding='utf-8')worksheet = workbook.add_sheet('MyselfSchedule')session = requests.Session()headers = { 'Referer' : 'http://run.hbut.edu.cn/', \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36\",}#获取登陆验证码def GetValidateCode(): checkcodecontent = session.get(checkimg,headers=headers) with open('checkcode.jpg','wb') as f: f.write(checkcodecontent.content) print('验证码已写入到本地!') os.startfile(\"checkcode.jpg\") checkcode = input(\"请输入验证码:\") payload = { 'Role':'Student', 'UserName': input('请输入账号:'), 'Password': input('请输入密码:'), 'ValidateCode': checkcode } return payload#获取课程表网页def getHtmlText(payload): respose = session.post(login_url,headers=headers,data=payload) print('服务器端返回码:',respose.status_code) work = session.get(Schedule,headers=headers) return work.text#提取网页中的课程表def getFormText(string): soup = BeautifulSoup(string,'html.parser') list = [] for tr in soup.find('table').children: try: time = tr('th')[0].string if time==None: continue day1 = tr('td')[0].string day2 = tr('td')[1].string day3 = tr('td')[2].string day4 = tr('td')[3].string day5 = tr('td')[4].string day6 = tr('td')[5].string day7 = tr('td')[6].string list.append([time,day1,day2,day3,day4,day5,day6,day7]) except: continue return list#将提取到的课程表写入excel表格中def WirteXls(list): worksheet.write(0,1,\"星期一\") worksheet.write(0,2,\"星期二\") worksheet.write(0,3,\"星期三\") worksheet.write(0,4,\"星期四\") worksheet.write(0,5,\"星期五\") worksheet.write(0,6,\"星期六\") worksheet.write(0,7,\"星期日\") for i in range(len(list)): u = list[i] for k in range(8): worksheet.write(i+1,k,u[k])def main(): try: payload = GetValidateCode() string = getHtmlText(payload) string = string.replace('<br />','') list = getFormText(string) WirteXls(list) finally: workbook.save('MyselfSchedule.xls')main()","categories":[{"name":"python","slug":"python","permalink":"https://www.djangoz.com/categories/python/"}],"tags":[{"name":"小程序","slug":"小程序","permalink":"https://www.djangoz.com/tags/小程序/"}]},{"title":"统计文本中单词频率","slug":"Vocabulary_Statistics","date":"2017-10-17T16:00:00.000Z","updated":"2018-06-10T13:37:38.000Z","comments":true,"path":"2017/10/18/Vocabulary_Statistics/","link":"","permalink":"https://www.djangoz.com/2017/10/18/Vocabulary_Statistics/","excerpt":"","text":"想起高中的时候英语一直很差,后来发现背英语中出现频率最高的单词是一种很高效的学习方法,于是现在写出来一个小程序 更新最近数据结构课程设计,老师给的题目里面有个就是分析文本,统计单词频率,用c++来写,所以就增加c++版本的,源码在github里已更新,因为是课程设计,就没用c++的map库来写,用自己定义的红黑树来实现的 github项目地址(如果觉得这个程序好,求个star)这个小程序可以统计文件input.txt文本中所有英文单词的词频(由高到低排序),并给出每个单词的翻译这个程序有两种方法,第一种用goole-cloud库,收费的,不过运行时间快一些,第二种模拟网页请求,时间略长,不能大量翻译.本程序用的两种方法都可以运行,第一种在本机运行后,超出了免费翻译次数,于是就用的第二种,要用第一种的话,把源代码中注释的代码取消注释即可,再把第二种的注释就可以了 一.运行前需要安装google-cloud库,并安装Google Cloud SDK1.安装相关模块1pip install --upgrade google-cloud-translate 2.调用api进行翻译之前,需要安装相关证书安装 Google Cloud SDK因为在本地上运行,使用的第一种方式,安装之后会有自动弹出验证邮箱的界面,如果没有在Google cloud sdk shell 运行1gcloud auth application-default login 二.使用urllib取googletranslate模拟一个头部,请求到translate.google.com去,然后解析出翻译文本 运行方式把将要统计的文本放入Input.txt文件中,直接运行即可1python word.py 源代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#coding=utf8import re# from google.cloud import translate import urllib.requestimport sys# translate_client = translate.Client()# language = 'zh_CN' #将要翻译的语言length = 4 #提选出小于此长度的单词input = open(\"words.txt\",'r',encoding='utf-8')output = open('ouput.txt','w+',encoding='utf-8')typ = sys.getfilesystemencoding()def translate(querystr, to_l=\"zh\", from_l=\"en\"): C_agent = {'User-Agent': \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36\"} flag = 'class=\"t0\">' tarurl = \"http://translate.google.com/m?hl=%s&sl=%s&q=%s \\ \" % (to_l, from_l, querystr.replace(\" \", \"+\")) request = urllib.request.Request(tarurl, headers=C_agent) page = str(urllib.request.urlopen(request).read().decode(typ)) target = page[page.find(flag) + len(flag):] target = target.split(\"<\")[0] return targettry: all_word = input.read() l = re.split(r'[^A-Za-z]+',all_word) d = {} for i in l: i = i.lower() if d.get(i)==None: d[i] = 1 else: d[i] = d[i] +1 ju = 0 for word in sorted(d,key=d.get,reverse=True): if len(word)<length: continue # translation = translate_client.translate(word,target_language=language) # tran = translation['translatedText'] tran = translate(word) string = \"%-18s%-10s%-7d\\t\"%(word,tran,d[word]) # string = \"%-20s%-7d\\t\" %(word,d[word]) ju += 1 if ju%8==0: string += '\\n' output.write(string)finally: input.close() output.close()","categories":[{"name":"python","slug":"python","permalink":"https://www.djangoz.com/categories/python/"}],"tags":[{"name":"小程序","slug":"小程序","permalink":"https://www.djangoz.com/tags/小程序/"}]},{"title":"识别括号优先级的简易计算器","slug":"simple_calculator","date":"2017-09-29T16:00:00.000Z","updated":"2018-06-17T04:26:06.000Z","comments":true,"path":"2017/09/30/simple_calculator/","link":"","permalink":"https://www.djangoz.com/2017/09/30/simple_calculator/","excerpt":"","text":"github项目地址该计算器用java实现更新:现已支持多位数的输入(利用正则表达式提取输入的数值,再压入数值栈中)GUI用swing实现,可识别括号优先级是用了双栈,一个栈存取操作符,一个栈存取数据界面巨丑,待我学到更多后来更新此计算器。主要是拿来练手的(这是第一个自己用代码实现GUI的小程序!!!)主界面: CalculatorFrame.java12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;/*计算器框架*/public class CalculatorFrame { private JFrame frame; private JTextField number; private JPanel panel; private static String expression =\"\"; //输入的数据转划为字符串 public CalculatorFrame(){ frame = new JFrame(\"Calculator\"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(300,300); frame.setLocationRelativeTo(null); number = new JTextField(); number.setEditable(false); frame.add(number,BorderLayout.NORTH); panel = new JPanel(); panel.setLayout(new GridLayout(6,3)); frame.add(panel,BorderLayout.CENTER); for(int i =1;i<10;i++){ addButton(String.valueOf(i)); } addButton(\"+\"); addButton(\"0\"); addButton(\"-\"); addButton(\"*\"); addButton(\"C\"); //按钮C的作用是清空整个输入 addButton(\"/\"); addButton(\"(\"); addButton(\")\"); JButton equal = new JButton(\"=\"); equal.setActionCommand(\"=\"); equal.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e){ number.setText(\"\"+new Calculate().calculating(expression)); //显示计算得到的结果 expression = \"\"; // 计算后整个输入清空 } }); panel.add(equal); frame.add(panel); frame.setResizable(false); //设置窗口大小不可改变 frame.setVisible(true); } /*当不为输入不为“C”和“=”时,将输入转化为字符串,为“C”时清空输入*/ private void addButton(String name ){ JButton temp = new JButton(name); temp.setActionCommand(name); temp.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if(!\"C\".equals(name)){ expression += name; number.setText(expression); }else expression=\"\"; } }); panel.add(temp); }} Calculate.java1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556import java.util.*;/*读取一个字符串,然后在两个栈中计算表达式的值,这样可以判断()的优先级*/public class Calculate { private static Stack<String> ops = new Stack<>(); //操作栈 private static Stack<Double> nums = new Stack<>(); //数据栈 private static void calculate() { String op = ops.pop(); if (op.equals(\"+\")) nums.push(nums.pop() + nums.pop()); else if (op.equals(\"-\")) nums.push(-(nums.pop() - nums.pop())); else if (op.equals(\"*\")) nums.push(nums.pop() * nums.pop()); else if (op.equals(\"/\")) nums.push(nums.pop() / nums.pop()); } public double calculating(String s){ System.out.println(s); String[] strs = s.split(\"[^0-9]+\"); //利用正则表达式提取输入中的数值 for(String str:strs){ nums.push(Double.parseDouble(str)); //将提取的数值压到栈中 } for (int i = 0; i < s.length(); i++) { switch (s.charAt(i)) { case '(': break; case '+': ops.push(\"+\"); break; case '-': ops.push(\"-\"); break; case '*': ops.push(\"*\"); break; case '/': ops.push(\"/\"); break; case ')': { calculate(); break; } default: break; } } while (!ops.empty()) { calculate(); } return nums.pop(); //返回结果 }} run.java123456789101112import java.awt.*;public class run { public static void main(String[] args){ EventQueue.invokeLater(new Runnable() { @Override public void run() { new CalculatorFrame(); } }); }}","categories":[{"name":"java","slug":"java","permalink":"https://www.djangoz.com/categories/java/"}],"tags":[{"name":"小程序","slug":"小程序","permalink":"https://www.djangoz.com/tags/小程序/"}]},{"title":"Windows cmd使用常用bash命令","slug":"windows_use_bash","date":"2017-09-27T16:00:00.000Z","updated":"2018-06-10T13:20:10.000Z","comments":true,"path":"2017/09/28/windows_use_bash/","link":"","permalink":"https://www.djangoz.com/2017/09/28/windows_use_bash/","excerpt":"","text":"windows的cmd命令用起来不顺手,Linux系的bash命令酸爽多了。首先下载git,一般来说都有这个软件,有的话忽略。然后找到git的安装路径,我电脑git安装在D盘的 然后进入usr目录,再进入bin目录,把该目录链接添加一个环境变量。保存就可以了 然后就可以愉快的使用bash命令了,瞬间酸爽许多。ls rm mkdir等等都可以完美使用.** 最主要的是可以使用vim,让cmd瞬间强大了许多,有木有!!!","categories":[],"tags":[{"name":"小技巧","slug":"小技巧","permalink":"https://www.djangoz.com/tags/小技巧/"}]},{"title":"并查集(Union-Find)算法介绍","slug":"union_find","date":"2017-09-11T16:00:00.000Z","updated":"2018-06-10T14:21:02.000Z","comments":true,"path":"2017/09/12/union_find/","link":"","permalink":"https://www.djangoz.com/2017/09/12/union_find/","excerpt":"","text":"在看完algorithms-part1第一周的Union-Find视频和算法(第四版) 一书的Section 1.5后,本来也打算写一些笔记的,但是发现有篇文章写得很好,就转载过来了。先贴上自己学习过程中写的代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869//import edu.princeton.cs.introcs.StdIn;import java.util.Scanner;public class UnionFind { private int[] id; private int[] sz; private int count; public UnionFind(int N) { count = 0; id = new int[N]; sz = new int[N]; for (int i = 0; i < N; i++) { id[i] = i; } for (int i = 0; i < N; i++) { sz[i] = 1; } } public int count() { return count; } private int find(int i) { while (i != id[i]) { id[i] = id[id[i]]; //路径压缩 i = id[i]; } return i; } public boolean connected(int p, int q) { return find(p) == find(q); } public void union(int p, int q) { int i = find(p); int j = find(q); if (i == j) return; if (sz[i] < sz[j]) { //对树的高度加权,总是选择讲小树连接到大树 id[i] = j; sz[j] += sz[i]; } else { id[j] = i; sz[i] += sz[j]; } count++; } public static void main(String[] args) { Scanner in = new Scanner(System.in); // int N = StdIn.readInt(); int N = in.nextInt(); UnionFind uFind = new UnionFind(N); while (in.hasNext()) { // int p = StdIn.readInt(); // int q = StdIn.readInt(); int p = in.nextInt(); int q = in.nextInt(); if (uFind.connected(p, q)) continue; uFind.union(p, q); System.out.println(p + \" \" + q); } System.out.println(uFind.count + \"components\"); }} 本文主要介绍解决动态连通性一类问题的一种算法,使用到了一种叫做并查集的数据结构,称为Union-Find。更多的信息可以参考算法(第四版) 一书的Section 1.5,实际上本文也就是基于它的一篇读后感吧。原文中更多的是给出一些结论,我尝试给出一些思路上的过程,即为什么要使用这个方法,而不是别的什么方法。我觉得这个可能更加有意义一些,相比于记下一些结论。 关于动态连通性我们看一张图来了解一下什么是动态连通性: 假设我们输入了一组整数对,即上图中的(4, 3) (3, 8)等等,每对整数代表这两个points/sites是连通的。那么随着数据的不断输入,整个图的连通性也会发生变化,从上图中可以很清晰的发现这一点。同时,对于已经处于连通状态的points/sites,直接忽略,比如上图中的(8, 9)。 动态连通性的应用场景: 网络连接判断:如果每个pair中的两个整数分别代表一个网络节点,那么该pair就是用来表示这两个节点是需要连通的。那么为所有的pairs建立了动态连通图后,就能够尽可能少的减少布线的需要,因为已经连通的两个节点会被直接忽略掉。 变量名等同性(类似于指针的概念):在程序中,可以声明多个引用来指向同一对象,这个时候就可以通过为程序中声明的引用和实际对象建立动态连通图来判断哪些引用实际上是指向同一对象。 对问题建模:在对问题进行建模的时候,我们应该尽量想清楚需要解决的问题是什么。因为模型中选择的数据结构和算法显然会根据问题的不同而不同,就动态连通性这个场景而言,我们需要解决的问题可能是: 给出两个节点,判断它们是否连通,如果连通,不需要给出具体的路径 给出两个节点,判断它们是否连通,如果连通,需要给出具体的路径 就上面两种问题而言,虽然只有是否能够给出具体路径的区别,但是这个区别导致了选择算法的不同,本文主要介绍的是第一种情况,即不需要给出具体路径的Union-Find算法,而第二种情况可以使用基于DFS的算法。 建模思路:最简单而直观的假设是,对于连通的所有节点,我们可以认为它们属于一个组,因此不连通的节点必然就属于不同的组。随着Pair的输入,我们需要首先判断输入的两个节点是否连通。如何判断呢?按照上面的假设,我们可以通过判断它们属于的组,然后看看这两个组是否相同,如果相同,那么这两个节点连通,反之不连通。为简单起见,我们将所有的节点以整数表示,即对N个节点使用0到N-1的整数表示。而在处理输入的Pair之前,每个节点必然都是孤立的,即他们分属于不同的组,可以使用数组来表示这一层关系,数组的index是节点的整数表示,而相应的值就是该节点的组号了。该数组可以初始化为: 12for(int i = 0; i < size; i++) id[i] = i; 即对于节点i,它的组号也是i。 初始化完毕之后,对该动态连通图有几种可能的操作: 查询节点属于的组数组对应位置的值即为组号 判断两个节点是否属于同一个组分别得到两个节点的组号,然后判断组号是否相等 连接两个节点,使之属于同一个组分别得到两个节点的组号,组号相同时操作结束,不同时,将其中的一个节点的组号换成另一个节点的组号获取组的数目 初始化为节点的数目,然后每次成功连接两个节点之后,递减1 API我们可以设计相应的API: 注意其中使用整数来表示节点,如果需要使用其他的数据类型表示节点,比如使用字符串,那么可以用哈希表来进行映射,即将String映射成这里需要的Integer类型。 分析以上的API,方法connected和union都依赖于find,connected对两个参数调用两次find方法,而union在真正执行union之前也需要判断是否连通,这又是两次调用find方法。因此我们需要把find方法的实现设计的尽可能的高效。所以就有了下面的Quick-Find实现。 Quick-Find 算法:12345678910111213141516171819202122232425262728293031public class UF { private int[] id; // access to component id (site indexed) private int count; // number of components public UF(int N) { // Initialize component id array. count = N; id = new int[N]; for (int i = 0; i < N; i++) id[i] = i; } public int count() { return count; } public boolean connected(int p, int q) { return find(p) == find(q); } public int find(int p) { return id[p]; } public void union(int p, int q) { // 获得p和q的组号 int pID = find(p); int qID = find(q); // 如果两个组号相等,直接返回 if (pID == qID) return; // 遍历一次,改变组号使他们属于一个组 for (int i = 0; i < id.length; i++) if (id[i] == pID) id[i] = qID; count--; } } 举个例子,比如输入的Pair是(5, 9),那么首先通过find方法发现它们的组号并不相同,然后在union的时候通过一次遍历,将组号1都改成8。当然,由8改成1也是可以的,保证操作时都使用一种规则就行。 上述代码的find方法十分高效,因为仅仅需要一次数组读取操作就能够找到该节点的组号,但是问题随之而来,对于需要添加新路径的情况,就涉及到对于组号的修改,因为并不能确定哪些节点的组号需要被修改,因此就必须对整个数组进行遍历,找到需要修改的节点,逐一修改,这一下每次添加新路径带来的复杂度就是线性关系了,如果要添加的新路径的数量是M,节点数量是N,那么最后的时间复杂度就是MN,显然是一个平方阶的复杂度,对于大规模的数据而言,平方阶的算法是存在问题的,这种情况下,每次添加新路径就是“牵一发而动全身”,想要解决这个问题,关键就是要提高union方法的效率,让它不再需要遍历整个数组。 Quick-Union 算法:考虑一下,为什么以上的解法会造成“牵一发而动全身”?因为每个节点所属的组号都是单独记录,各自为政的,没有将它们以更好的方式组织起来,当涉及到修改的时候,除了逐一通知、修改,别无他法。所以现在的问题就变成了,如何将节点以更好的方式组织起来,组织的方式有很多种,但是最直观的还是将组号相同的节点组织在一起,想想所学的数据结构,什么样子的数据结构能够将一些节点给组织起来?常见的就是链表,图,树,什么的了。但是哪种结构对于查找和修改的效率最高?毫无疑问是树,因此考虑如何将节点和组的关系以树的形式表现出来。 如果不改变底层数据结构,即不改变使用数组的表示方法的话。可以采用parent-link的方式将节点组织起来,举例而言,id[p]的值就是p节点的父节点的序号,如果p是树根的话,id[p]的值就是p,因此最后经过若干次查找,一个节点总是能够找到它的根节点,即满足id[root] = root的节点也就是组的根节点了,然后就可以使用根节点的序号来表示组号。所以在处理一个pair的时候,将首先找到pair中每一个节点的组号(即它们所在树的根节点的序号),如果属于不同的组的话,就将其中一个根节点的父节点设置为另外一个根节点,相当于将一颗独立的树编程另一颗独立的树的子树。直观的过程如下图所示。但是这个时候又引入了问题。 在实现上,和之前的Quick-Find只有find和union两个方法有所不同:12345678910111213141516private int find(int p) { // 寻找p节点所在组的根节点,根节点具有性质id[root] = root while (p != id[p]) p = id[p]; return p; } public void union(int p, int q) { // Give p and q the same root. int pRoot = find(p); int qRoot = find(q); if (pRoot == qRoot) return; id[pRoot] = qRoot; // 将一颗树(即一个组)变成另外一课树(即一个组)的子树 count--; } 树这种数据结构容易出现极端情况,因为在建树的过程中,树的最终形态严重依赖于输入数据本身的性质,比如数据是否排序,是否随机分布等等。比如在输入数据是有序的情况下,构造的BST会退化成一个链表。在我们这个问题中,也是会出现的极端情况的,如下图所示。 为了克服这个问题,BST可以演变成为红黑树或者AVL树等等。 然而,在我们考虑的这个应用场景中,每对节点之间是不具备可比性的。因此需要想其它的办法。在没有什么思路的时候,多看看相应的代码可能会有一些启发,考虑一下Quick-Union算法中的union方法实现: 12345678910public void union(int p, int q) { // Give p and q the same root. int pRoot = find(p); int qRoot = find(q); if (pRoot == qRoot) return; id[pRoot] = qRoot; // 将一颗树(即一个组)变成另外一课树(即一个组)的子树 count--; } 上面 id[pRoot] = qRoot 这行代码看上去似乎不太对劲。因为这也属于一种“硬编码”,这样实现是基于一个约定,即p所在的树总是会被作为q所在树的子树,从而实现两颗独立的树的融合。那么这样的约定是不是总是合理的呢?显然不是,比如p所在的树的规模比q所在的树的规模大的多时,p和q结合之后形成的树就是十分不和谐的一头轻一头重的”畸形树“了。所以我们应该考虑树的大小,然后再来决定到底是调用:id[pRoot] = qRoot 或者是 id[qRoot] = pRoot 即总是size小的树作为子树和size大的树进行合并。这样就能够尽量的保持整棵树的平衡。 所以现在的问题就变成了:树的大小该如何确定?我们回到最初的情形,即每个节点最一开始都是属于一个独立的组,通过下面的代码进行初始化: 12for (int i = 0; i < N; i++) id[i] = i; // 每个节点的组号就是该节点的序号 以此类推,在初始情况下,每个组的大小都是1,因为只含有一个节点,所以我们可以使用额外的一个数组来维护每个组的大小,对该数组的初始化也很直观: 12345678910public void union(int p, int q) { int i = find(p); int j = find(q); if (i == j) return; // 将小树作为大树的子树 if (sz[i] < sz[j]) { id[i] = j; sz[j] += sz[i]; } else { id[j] = i; sz[i] += sz[j]; } count--; } Quick-Union 和 Weighted Quick-Union 的比较: 可以发现,通过sz数组决定如何对两棵树进行合并之后,最后得到的树的高度大幅度减小了。这是十分有意义的,因为在Quick-Union算法中的任何操作,都不可避免的需要调用find方法,而该方法的执行效率依赖于树的高度。树的高度减小了,find方法的效率就增加了,从而也就增加了整个Quick-Union算法的效率。 上图其实还可以给我们一些启示,即对于Quick-Union算法而言,节点组织的理想情况应该是一颗十分扁平的树,所有的孩子节点应该都在height为1的地方,即所有的孩子都直接连接到根节点。这样的组织结构能够保证find操作的最高效率。 那么如何构造这种理想结构呢?在find方法的执行过程中,不是需要进行一个while循环找到根节点嘛?如果保存所有路过的中间节点到一个数组中,然后在while循环结束之后,将这些中间节点的父节点指向根节点,不就行了么?但是这个方法也有问题,因为find操作的频繁性,会造成频繁生成中间节点数组,相应的分配销毁的时间自然就上升了。那么有没有更好的方法呢?还是有的,即将节点的父节点指向该节点的爷爷节点,这一点很巧妙,十分方便且有效,相当于在寻找根节点的同时,对路径进行了压缩,使整个树结构扁平化。相应的实现如下,实际上只需要添加一行代码: 12345678910private int find(int p) { while (p != id[p]) { // 将p节点的父节点设置为它的爷爷节点 id[p] = id[id[p]]; p = id[p]; } return p; } 至此,动态连通性相关的Union-Find算法基本上就介绍完了,从容易想到的Quick-Find到相对复杂但是更加高效的Quick-Union,然后到对Quick-Union的几项改进,让我们的算法的效率不断的提高。这几种算法的时间复杂度如下所示: 对大规模数据进行处理,使用平方阶的算法是不合适的,比如简单直观的Quick-Find算法,通过发现问题的更多特点,找到合适的数据结构,然后有针对性的进行改进,得到了Quick-Union算法及其多种改进算法,最终使得算法的复杂度降低到了近乎线性复杂度。 如果需要的功能不仅仅是检测两个节点是否连通,还需要在连通时得到具体的路径,那么就需要用到别的算法了,比如DFS或者BFS。 该文章转载于dm_vincent的专栏","categories":[{"name":"java","slug":"java","permalink":"https://www.djangoz.com/categories/java/"}],"tags":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"https://www.djangoz.com/tags/算法与数据结构/"}]},{"title":"java实现简易的stack","slug":"stack_in_java","date":"2017-09-08T16:00:00.000Z","updated":"2018-06-06T15:11:40.000Z","comments":true,"path":"2017/09/09/stack_in_java/","link":"","permalink":"https://www.djangoz.com/2017/09/09/stack_in_java/","excerpt":"","text":"用java实现stack模板栈是一种基于后进先出(LIFO)策略的线性数据结构。这就像手枪弹夹一样,先填进去的子弹都打出,后填进去的子弹后打出。该模板只有5个方法,分别是isEmpty(),size(),top(),pop(),push(T).代码如下:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class stack<Item> { private Node first; private int N; private class Node{ Item item; Node next; } public boolean isEmpty(){ //判断是否为空 return first ==null; } public int size(){ //栈的大小 return N; } public void push(Item item){ //添加元素 Node oldfirst = first; first = new Node(); first.item = item; first.next = oldfirst; N++; } public Item pop(){ //弹出元素 Item item = first.item; first = first.next; N--; return item; } public Item top(){ //访问栈顶元素 return first.item; } public static void main(String[] args) { stack<Integer> te = new stack<Integer>(); te.push(19); if(te.isEmpty()){ System.out.println(\"isEmpty!\"); }else System.out.println(\"Not Empty!\"); te.push(20); System.out.println(te.pop()); System.out.println(te.top()); System.out.println(te.pop()); if(te.isEmpty()){ System.out.println(\"isEmpty!\"); }else System.out.println(\"Not Empty!\"); }} 该程序输入为:12345Not Empty!201919isEmpty! 部分实现参考于算法(第四版)","categories":[{"name":"java","slug":"java","permalink":"https://www.djangoz.com/categories/java/"}],"tags":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"https://www.djangoz.com/tags/算法与数据结构/"}]},{"title":"java实现简易的queue","slug":"queue_in-java","date":"2017-09-08T16:00:00.000Z","updated":"2018-06-06T15:09:50.000Z","comments":true,"path":"2017/09/09/queue_in-java/","link":"","permalink":"https://www.djangoz.com/2017/09/09/queue_in-java/","excerpt":"","text":"用java实现queue模板队列是一种基于先进先出(FIFO)策略的线性数据结构。这就像在学校食堂买饭排队一样,排在前面的人先打饭,后面的人后打饭。该模板只有6个方法,分别是isEmpty(),size(),front(),back(),pop(),push(T).代码如下:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556public class queue<Item> { private Node first; private Node last; private int N; private class Node { Item item; Node next; } public boolean isEmpty(){ //判断是否为空 return this.first==null; } public int size(){ //队列的大小 return N; } public void push(Item T){ //向队列添加元素 Node temp = last; last = new Node(); last.item = T; last.next = null; if(isEmpty()) first = last; else temp.next = last; N++; } public Item pop(){ //弹出第一个元素,并返回第一个元素 Node temp = first; first = first.next; if(isEmpty()) last=null; N--; return temp.item; } public Item front(){ //访问队列第一个元素 return first.item; } public Item back(){} //访问队列最后一个元素 public static void main(String[] args) { queue<String> sb = new queue<String>(); sb.push(\"hi\"); if(sb.isEmpty()) System.out.println(\"isEmpty!\"); else System.out.println(\"NotEmpty!\"); sb.push(\"hello\"); sb.push(\"world\"); System.out.println(sb.front()); System.out.println(sb.pop()); System.out.println(sb.back()); System.out.println(sb.pop()); System.out.println(sb.pop()); if(sb.isEmpty()) System.out.println(\"isEmpty!\"); else System.out.println(\"NotEmpty!\"); }} 该程序输入为1234567NotEmpty!hihiworldhelloworldNotEmpty! 部分实现参考于算法(第四版)","categories":[{"name":"java","slug":"java","permalink":"https://www.djangoz.com/categories/java/"}],"tags":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"https://www.djangoz.com/tags/算法与数据结构/"}]},{"title":"windows下访问linux分区文件","slug":"windows_to-linux","date":"2017-08-18T03:27:50.000Z","updated":"2018-12-06T06:59:48.000Z","comments":true,"path":"2017/08/18/windows_to-linux/","link":"","permalink":"https://www.djangoz.com/2017/08/18/windows_to-linux/","excerpt":"","text":"Ext2Fsd Project是Windows下的应用程序,可以在windows下访问ext2/ext3/ext4文件系统,可以设置编码,分配盘符,更可以在windows下读写,使用性能好。","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"https://www.djangoz.com/tags/linux/"}]},{"title":"更换主题","slug":"change_theme","date":"2017-08-17T10:10:16.000Z","updated":"2018-12-06T06:59:48.000Z","comments":true,"path":"2017/08/17/change_theme/","link":"","permalink":"https://www.djangoz.com/2017/08/17/change_theme/","excerpt":"","text":"爱折腾的我又把原来网站换了框架,把jekyll换成了hexo,感觉hexo支持的主题看起来美观多了该主题来自indigo","categories":[],"tags":[{"name":"闲言碎语","slug":"闲言碎语","permalink":"https://www.djangoz.com/tags/闲言碎语/"}]},{"title":"在Linux的环境安装shadowsocksR客户端","slug":"linux_setup_ssr","date":"2017-08-15T16:00:00.000Z","updated":"2018-07-17T03:58:42.000Z","comments":true,"path":"2017/08/16/linux_setup_ssr/","link":"","permalink":"https://www.djangoz.com/2017/08/16/linux_setup_ssr/","excerpt":"","text":"ssr相比于ss更加稳定,推荐安装ssr之前在网上找到很久没找到如何在Linux环境下运行ssr,后来通过这个脚本安装ssr,亲测可用安装以后可以通过ssr启动或者ssr配置命令运行ssr使用root用户登录,运行以下命令: 123456# wget --no-check-certificate https://www.djangoz.com/ssr wget https://onlyless.github.io/ssrsudo mv ssr /usr/local/binsudo chmod 766 /usr/local/bin/ssrssr installssr config SSR的配置就不说明了,很简单的 更新该脚本会运行git的命令,所以前提先安装的git1sudo apt-get install git 设置开机自启动1sudo vim /etc/rc.local 里面直接添加要运行的命令123456789101112131415#!/bin/sh -e## rc.local## This script is executed at the end of each multiuser runlevel.# Make sure that the script will \"exit 0\" on success or any other# value on error.## In order to enable or disable this script just change the execution# bits.## By default this script does nothing.sudo ssr startexit 0 系统会默认启动,直接实测开机会自动运行思想就是直接让系统开机启动一条sudo命令,如果这个不行的话,可以去网上参考自己linux版本的开机启动脚本或命令,然后添加一条命令就可以了 1sudo ssr start","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"https://www.djangoz.com/tags/linux/"}]},{"title":"求二叉树的最大深度和最小深度","slug":"Depth_of_Binary_tree","date":"2017-08-14T16:00:00.000Z","updated":"2018-06-06T15:07:28.000Z","comments":true,"path":"2017/08/15/Depth_of_Binary_tree/","link":"","permalink":"https://www.djangoz.com/2017/08/15/Depth_of_Binary_tree/","excerpt":"","text":"运用递归的思想实现起来简单很多。 二叉树的定义123456struct TreeNode{ int val; TreeNode *left,*right; TreeNode(){} TreeNode(int _val):val(_val),left(NULL),right(NULL){}}; 具体实现二叉树就不写了。 求二叉树的最大深度123456int minDepth(TreeNode* root) { if(!root)return 0; if(!root->left) return 1+minDepth(root->right); if(!root->right) return 1+minDepth(root->left); return 1+min(minDepth(root->left),minDepth(root->right));}","categories":[{"name":"c++","slug":"c","permalink":"https://www.djangoz.com/categories/c/"}],"tags":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"https://www.djangoz.com/tags/算法与数据结构/"}]},{"title":"先随便写写","slug":"wirte_something","date":"2017-08-11T16:00:00.000Z","updated":"2018-06-06T14:55:54.000Z","comments":true,"path":"2017/08/12/wirte_something/","link":"","permalink":"https://www.djangoz.com/2017/08/12/wirte_something/","excerpt":"","text":"今天终于学会在静态网站上安装插件了(其实也不算,是直接用的别人的主题),原来也不难,昨天弄了老半天都没弄出来 还是写一写方法吧 (⊙o⊙)…还是以后在写吧,现在还不会用markdown写文档 初学者什么都不会~~o(>_<)o ~~ 还是直接贴我参考的地方吧(在GitHub页面上使用Jekyll插件](http://ixti.net/software/2013/01/28/using-jekyll-plugins-on-github-pages.html) 把主题作者的github项目主页贴一下,表示感谢","categories":[],"tags":[{"name":"闲言碎语","slug":"闲言碎语","permalink":"https://www.djangoz.com/tags/闲言碎语/"}]},{"title":"创建博客成功","slug":"first_time","date":"2017-08-10T16:00:00.000Z","updated":"2018-12-06T06:59:48.000Z","comments":true,"path":"2017/08/11/first_time/","link":"","permalink":"https://www.djangoz.com/2017/08/11/first_time/","excerpt":"","text":"创建时间2017-08-11 23:17:37弄了一天终于弄成个样子了,准备装插件,但是一直没有成功更新时间2017-08-12 00:04:57","categories":[],"tags":[{"name":"闲言碎语","slug":"闲言碎语","permalink":"https://www.djangoz.com/tags/闲言碎语/"}]}]}