Echor ~~~ 2021-02-20T12:38:56.771Z https://rebiocoder.github.io/ Echor Hexo [golang] android应用中websocket的处理 https://rebiocoder.github.io/2021/02/20/Untitled/ 2021-02-20T12:31:00.000Z 2021-02-20T12:38:56.771Z <![CDATA[

最近在学习golang,完成了一个聊天app,效果如下:

整个项目后端基于golang,主要通过websocket来实现。根据websocket的协议,在socket连接之后需要进行ping-pong发包,ping-pong应该由服务器自己实现,无需客户端的参与,用来保证websocket连接的正常,下面我们用golang来实现一下。
首先构建如下测试项目结构:

1
2
3
4
5
6
7
8
.
├── go.mod
├── go.sum
├── main
├── main.go
├── model.go
├── session.go
└── websock_hdl.go

在main.go中构建一个http服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

package main

import (
"net/http"
)
var global struct {
Store *SessionStore
}
func main() {
server := http.NewServeMux()
global.Store = &SessionStore{
sessCache: make(map[string]*Session),
}
server.HandleFunc("/ws", WebsocketHandler)
http.ListenAndServe(":6060", server)
}

在session.go中需要实现两个goroutine,一个readLoop用来监听客户端发给server的信息,一个writeLoop用来监听服务端发给客户端的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

// WriteLoop is loop
func (sess *Session) writeLoop() {
ticker := time.NewTicker((time.Second * 10 * 9) / 10)
defer func() {
ticker.Stop()
sess.closeWS()
}()
for {
select {
case msg, ok := <-sess.send:

fmt.Println("发送消息", msg)
case <-ticker.C:
sess.ws.SetWriteDeadline(time.Now().Add(time.Second * 1))
if err := sess.ws.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}

func (sess *Session) readLoop() {
defer func() {
sess.closeWS()
}()
sess.ws.SetReadLimit(1024)
sess.ws.SetReadDeadline(time.Now().Add(time.Second * 10))
sess.ws.SetPongHandler(func(appData string) error {
fmt.Println("客户端返回pong包", appData)
sess.ws.SetReadDeadline(time.Now().Add(time.Second * 10))
return nil
})
for {
_, raw, err := sess.ws.ReadMessage()
fmt.Println("收到客户端消息内容", string(raw))
}
}

在writeLoop中需要定义一个NewTicker,它的作用是每间隔一段固定的时间,将当前的时间发送到channel ticker.C中,这样我们可以通过监听该通道,用来在固定时间内向客户端发送一个ping消息。当websocket收到ping消息之后在readLoop中实现setPongHandler函数,用来向server返回一个事件,用来表示当前客户端连接并未断开。这个事件就是SetReadDeadline这个事件,它用来表示该websocket在几秒之后会失效(当超过该时间后会使websocket断开)。所以pong处理时需要更新websocket的ReadDeadline的时间,保证socket的长期连接。

在webcok_hdl.go中将http请求升级成websocket:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

type Student struct {
Name string `json:"name"`
Age int16 `json:"age"`
Class *Class `json:"class"`
}
type Class struct {
StudyNumber string `json:"studyNumber"`
Number int16 `json:"number"`
}

// WebsocketHandler 处理socket链接
func WebsocketHandler(w http.ResponseWriter, r *http.Request) {
//升级http请求
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
websock, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Error", err)
return
}
var sess *Session = global.Store.NewSession(websock)
fmt.Println("ws: 开始websocket监听")
go sess.writeLoop()
go sess.readLoop()
js, _ := json.Marshal(&Student{
Name: "abc",
Age: 18,
Class: &Class{
StudyNumber: "2016221097121",
Number: 189,
},
})
sess.send <- js
}

注意:在golang中函数与方法是有区别的,一个函数可以拥有自己的方法,一个方法会有自己的接受者,这与传统的面向对象的语言不同。所以我们可能会看见一些奇怪的使用方法,比如定义一个函数类型:

1
type HandlerFunc func(ResponseWriter, *Request)

定义一个Hnadler接口:

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

给HandlerFunc函数类型实现Handler接口中的方法:

1
2
3
4

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

在golang的net/http内置包中的HandlerFunc函数(用来将路由映射到视图函数)就运用了上述的技巧,使你能够将任意命名的函数只要参数是(ResponseWriter,*Resquest),就均能作为视图函数,来处理路由。如下:

1
server.HandleFunc("/ws", WebsocketHandler)

在源码中跟踪上述函数:

1
2
3
4

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}

HandleFunc中调用了Handle函数:

1
func (mux *ServeMux) Handle(pattern string, handler Handler)

Handle函数的第二个参数应该是一个Handle接口:

1
2
3
4

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

注意在Handle函数中,第二个参数使用了一个HandlerFunc函数(多一个r),这个函数的定义如下:

1
2
3
4
5
6
7

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

可见,这里的HandlerFunc(handler))可以理解为强制类型转化,将handler函数强制转化为Handler接口,这样就能够实现上述功能。

]]> <p>最近在学习golang,完成了一个聊天app,效果如下:<br><img src="https://ae01.alicdn.com/kf/U95f548f8ca494767b3d93c5a6012fa72G.jpg"></p> <p>整个项目后端基于golang,主要通过w [Golang]defer与return的返回顺序问题 https://rebiocoder.github.io/2021/01/10/%E4%BD%86%E6%98%AFSV/ 2021-01-10T11:45:00.000Z 2021-01-10T13:05:07.889Z <![CDATA[

defer在go中是一个全新的关键字,他的作用在于能够在go函数返回之后,延迟加载defer函数中的内容。这里对defer的作用进行如下测试:

  1. defer用于延迟加载
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func test2() {
    defer func() {
    fmt.Printf("你好,世界!\n")
    }()
    fmt.Printf("哈哈,我在外面\n")
    }
    func main() {
    test2()
    }
    输出结果如下:
    upload successful

2.函数中有多个defer函数
当函数中有多个defer时,从上到下,根据“先进后出”的原则 得到结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func test2() {
defer func() {
fmt.Printf("你好,世界!\n")
}()
defer func() {
fmt.Printf("我是第二个defer函数\n")
}()
fmt.Printf("哈哈,我在外面\n")
}
func main() {
// fmt.Printf("main函数里的name111: %s\n", name)
// myname := myfunc()
// fmt.Println("main 函数里的myname333: ", myname)
// fmt.Printf("main函数里的name222: %s\n", name)
// fmt.Println("main 函数里的myname: ", myname)
test2()
}

upload successful

3.当defer与return均存在时,return先返回结果,再执行defer中的内容。实验如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var name string = "go"

func myfunc() string {
defer func() {
name = "python"
}()
fmt.Printf("myfunc 函数里的name: %s\n", name)
return name
}

func main() {
fmt.Printf("main函数里的name111: %s\n", name)
myname := myfunc()
fmt.Println("main 函数里的myname333: ", myname)
fmt.Printf("main函数里的name222: %s\n", name)
fmt.Println("main 函数里的myname: ", myname)
}

upload successful

]]>
<p>defer在go中是一个全新的关键字,他的作用在于能够在go函数返回之后,延迟加载defer函数中的内容。这里对defer的作用进行如下测试:</p> <ol> <li>defer用于延迟加载<figure class="highlight plain"><table><t
[Golang]基础知识 https://rebiocoder.github.io/2021/01/05/%E8%AE%BF%E9%97%AE/ 2021-01-05T05:20:00.000Z 2021-01-05T08:51:53.009Z <![CDATA[

变量声明

1.单个变量声明 var name string 这样声明一个变量,注意如果没有赋初始值,那么字符串初始值为空字符串“”,int为0,float为0.0,bool为false,指针类型为nil。

在go中,单引号和双引号有区别。在初始化变量时,如果右值带有小数点,那么建议带上类型float32。

2.多个变量一起声明,声明多个变量

1
2
3
4
5
var (
old_name string
age int
gender float32
)

3.匿名变量的优点

  • 不分配内存,不占用内存空间
  • 不需要你为命名无用的变量名而纠结
  • 多次声明不会有任何问题

整型

upload successful
在go中最好不要直接使用int给变量作为type,原因在于在32位机器上,int表示4个字节相当于int32,在64位机器上,int 表示8个字节,相当于int64。

二进制表示一个数:0b110
八进制表示一个数: 0o14
十六进制表示一个数:0xC

fmt格式化代码:

1
2
3
4
5
6
7
8
9
%b 格式化为二进制,
%c unicode码值
%d 十进制
%o 八进制
%x 十六进制
%X 十六进制
%U unicode格式
%E 科学计数法
%f 浮点数格式

浮点型

浮点数类型的值一般由整数部分、小数点“.”和小数部分组成。

其中,整数部分和小数部分均由10进制表示法表示。不过还有另一种表示方法。那就是在其中加入指数部分。指数部分由“E”或“e”以及一个带正负号的10进制数组成。比如,3.7E-2表示浮点数0.037。又比如,3.7E+1表示浮点数37。

有一点需要注意,在Go语言里,浮点数的相关部分只能由10进制表示法表示,而不能由8进制表示法或16进制表示法表示。比如,03.7表示的一定是浮点数3.7。

float32 和 float64

Go语言中提供了两种精度的浮点数 float32 和 float64。

float32,也即我们常说的单精度,存储占用4个字节,也即4*8=32位,其中1位用来符号,8位用来指数,剩下的23位表示尾数。

upload successful

float64,也即我们熟悉的双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数。

upload successful

浮点数类型的取值范围可以从很微小到很巨大。浮点数取值范围的极限值可以在 math 包中找到:

  • 常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;
  • 常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;
  • float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。

注意浮点数表示精度的问题:

  1. float32的精度只能提供大约6个十进制数(表示后科学计数法后,小数点后6位)的精度

  2. float64的精度能提供大约15个十进制数(表示后科学计数法后,小数点后15位)的精度

byte和rune

byte用来表示一个ASCII码字符,rune占有四个字节,表示一个unicode字符。

单引号用来表示字符,如果你使用双引号,就意味着你要定义一个字符串,赋值时与前面声明的前面会不一致,这样在编译的时候就会出错。

byte 和 uint8 没有区别,rune 和 uint32 没有区别,那为什么还要多出一个 byte 和 rune 类型呢?

理由很简单,因为uint8 和 uint32 ,直观上让人以为这是一个数值,但是实际上,它也可以表示一个字符,所以为了消除这种直观错觉,就诞生了 byte 和 rune 这两个别名类型。

字符串

字符串一般用双引号表示””,但是字符串也可以用``反引号,在反引号中,会忽略转义字符。

数组与切片

数组是一个由固定长度的特定元素类型组成的序列。

注意:

  1. [3]int 与[4]int是不同的数据类型
  2. 如果在声明数组时,不想声明容量大小,可以使用name := [...]int{}类型的方式来让go自动识别大小。
  3. 可以给自定义的数组类型重命名,使用type关键字:
1
2
type arr [3]int
var name arr = arr {1,2,3}

数组 与 切片 有相同点,它们都是可以容纳若干类型相同的元素的容器

也有不同点,数组的容器大小固定,而切片本身是引用类型,它更像是 Python 中的 list ,我们可以对它 append 进行元素的添加。

取切片值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
m := make([]int, 2, 10)
fmt.Println(m)

fmt.Printf("\n")
myarr := []int{}
fmt.Println(myarr)
myarr = append(myarr, 2)
fmt.Println(myarr)
myarr = append(myarr, []int{7, 8, 9}...) //解包切片
fmt.Println(myarr)
myarr = append([]int{100}, myarr...) //在第一个位置插入
fmt.Println(myarr)
fmt.Println(myarr[len(myarr)-1])
myarr = append(myarr[0:len(arr)-1], append([]int{520, 520}, myarr[len(myarr)-1:]...)...) //在最后一个元素之前插入元素
fmt.Println(myarr)

字典与布尔类型

字典

字典的下标读取可以返回两个值,使用第二个返回值都表示对应的 key 是否存在,若存在ok为true,若不存在,则ok为false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//********************************************
var socres map[string]int = map[string]int{"english": 92, "chinese": 85}
fmt.Println(socres)
users := make(map[string]int) //初始化一个字典
fmt.Println(users)
users["Echor"] = 19 //添加元素
users["zhou"] = 20
fmt.Println(users)
users["zhou"] = 21 //添加重复键元素
fmt.Println(users)
fmt.Println(users["zhou"]) //得到存在元素
_, ok := users["sss"]
fmt.Println(ok) //得到不存在元素
delete(users, "zhou") //删除存在元素
fmt.Println(users)
delete(users, "sss") //删除不存在元素
fmt.Println(users)
fmt.Println(users)
for k, v := range socres { //循环得到键值对
fmt.Println(k, v)
}

布尔类型

关于布尔值,无非就两个值:true 和 false。只是这两个值,在不同的语言里可能不同。

而在 Go 中,真值用 true 表示,不但不与 1 相等,并且更加严格,不同类型无法进行比较,而假值用 false 表示,同样与 0 无法比较。

指针

指针的概念学了c语言将不难理解,变量分为普通变量和指针变量;

流程控制

if-else if- else 判断

在golang中if-else if -else有以下特点:

  1. 在if之后可以接一个表达式
  2. }else{  }else if{必须和左右括号在同一行

switch-case

注意:

  1. 在case后面可以接多个判断条件,他们的关系是”或“;
  2. 可以使用fallthrough进行穿透,但是只能穿透一层;
  3. 当条件都不匹配时,匹配default

for循环

for 循环的基本模型

1
2
3
4
for [condition |  ( init; condition; increment ) | Range]
{
statement(s);
}

注意:

  1. 在go中没有while,通过for{}(在for后面不加任何表达式来控制无线循环)
  2. for range遍历可以可迭代对象。for range的for后面不能添加任何表达式

goto

不建议使用,使用方法如下:

1
2
3
4
5
goto flag:
****
flag:
****
****

defer语句

  1. 延迟调用。defer 的用法很简单,只要在后面跟一个函数的调用,就能实现将这个 xxx 函数的调用延迟到当前函数执行完后再执行。
  2. 多个defer满足”先进后出“的条件
]]>
<h2 id="变量声明"><a href="#变量声明" class="headerlink" title="变量声明"></a>变量声明</h2><p>1.单个变量声明 <code>var name string</code> 这样声明一个变量,注意如果没有赋初始值,那么字符
tornado后端session用户认证鉴权解决方案 https://rebiocoder.github.io/2021/01/05/tornado%E5%90%8E%E7%AB%AFsession%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81%E9%89%B4%E6%9D%83%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/ 2021-01-05T02:39:00.000Z 2021-01-05T02:41:37.713Z <![CDATA[

tornado框架自身没有集成session鉴权,但是对cookie支持良好。由于现在前端vue部署在一个端口上,后端tornado分布式应用部署在多个端口上,这就会导致浏览器的跨域问题产生(url不同,即协议,域名,端口三者有不相同的地方)。

在前后端分离的项目中,由于页面的路由跳转由vue控制,后端应用只提供数据。这样就会使鉴权任务变得较为复杂。一个比较良好的解决思路是,在前端需要鉴权才能访问的路由中添加一个后端异步请求,该请求用来判断用户是否登录,这样来判断用户当前是否有权限访问该页面。同时,后端需要 登录才能访问的接口,用修饰器来判断用户当前是否登录。下面提供一种tornado实现session的解决方案。
在新建一个HTTP请求时,需要初始化一个session类,该类应该实现以下功能:

  1. 能够从请求头的cookie中得到session_id;

  2. 能够生成一个全新的session_id,并将其返回给client;

  3. 若server得到的session_id失效,则清空client的cookie;

  4. 若server得到的sess_id有效,则从redis中取出对应的用户信息

在一个HTTP请求结束时,需要判断,当前这个链接是否为”登录成功“的API,如果是,则需要将对应的用户信息存储到redis中,并生成一个新的session_id返回给用户。

代码如下:
BaseHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

class BaseHandler(web.RequestHandler):
def initialize(self):
self.session_save_tag = False
self.session = None

async def prepare(self):
await self.init_session()
if get_config().session['session_key_name'] in self.session:
self.current_user = LoginUser(self.session[get_config().session['session_key_name']])
def on_finish(self):
if self.session is not None and self.session_save_tag:
get_loop().add_callback(self.session.save)
get_logger().info("[一次HTTP请求结束]")
async def init_session(self):
if not self.session:
self.session = Session(self)
await self.session.init_fetch()

def save_session(self):
self.session_save_tag = True
self.session.generate_session_id()
get_logger().info("【登录】生成session_id结束")

def save_login_user(self, user: dict):
login_user = LoginUser(None)
login_user['username'] = user.get("username", "")
login_user['email'] = user.get("email", "")
login_user['phone'] = user.get("phone", "")
self.session[get_config().session['session_key_name']] = login_user
self.current_user = login_user
self.save_session()

login API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class LoginHandler(BaseHandler):
async def post(self):
try:
username = self.data['username']
password = self.data['password']
doc_user = await get_one_data(self.mongodb_manager, Config.mongodb_config['user_info'],
{'username': username})
print(doc_user)
if doc_user is not None and password == doc_user['password']:
self.save_login_user(doc_user)
get_logger().info("登录成功,用户信息已经保存")
except Exception as e:
error = traceback.format_exc()
get_logger().error("login fail[{}]:{}".format(e, error))
]]>
<p>tornado框架自身没有集成session鉴权,但是对cookie支持良好。由于现在前端vue部署在一个端口上,后端tornado分布式应用部署在多个端口上,这就会导致浏览器的跨域问题产生(url不同,即协议,域名,端口三者有不相同的地方)。</p> <p>在前后端分离的
微服务设计阅读学习 https://rebiocoder.github.io/2021/01/04/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E8%AE%BE%E8%AE%A1%E9%98%85%E8%AF%BB%E5%AD%A6%E4%B9%A0/ 2021-01-04T14:09:00.000Z 2021-01-04T14:11:26.166Z <![CDATA[

微服务的概念网上都有,各种博客都有解释。但是自己没有上手写代码,没有做工程项目,实际上是很难体会的。 微服务与SOA(面向服务的架构)的区别主要在于微服务中每个服务是一个独立的进程,它能够被独立的部署。若该服务宕机,并不影响整个系统中其他服务的正常运行。

在开始尝试做一个新的系统时,若常用微服务的方式,则需要使用限界上下文划分微服务边界。限界上下文指的是一个由显式边界限定的特定职责。如果你想要从一个限界上下文中获取信息,或者向其发起请求,需要使用模型和它的显式边界进行通信。实际上寻找限界上下文边界就是将整个业务逻辑进行一个粗粒度的划分。对于一个新系统而言,过早划分很容易错误判断服务之间的边界,按照书中内容所说,很多时候,将一个已有的代码库划分成微服务,要比从头开始构建微服务简单得多。

当你在思考组织内的限界上下文时,不应该从共享数据的角度来考虑,而应该从这些上下文能够提供的功能来考虑。服务与服务之间如果能够做到低耦合,高内聚,那么这就是一个好的微服务系统。实际上,要做到低耦合,高内聚还是很困难的,在工程项目中很可能遇到一份代码需要多次拷贝使用,当遇到这种情况时,就需要注意DRY(don’t repeat yourself)。应该考虑使用更好的设计模式来避免这种情况,因为很容易出现,一个地方被修改了,但是另一个地方忘记修改的情况。

服务集成是微服务相关技术中最重要的一个问题,按照作者的话来说:做得好的话,你的微服务可以保持自治性,你也可以独立地修改和发布它们;但做得不好的话会带来灾难。在选择集成方式时,需要注意以下几个原则:1.避免破坏性修改 2. 保证API的技术无关系3.使你的服务易于消费方使用4.隐藏内部实现细节。服务之间的通信可以分为同步和异步两种:如果使用同步通信,发起一个远程服务调用后,调用方会阻塞自己并等待整个操作的完成。如果使用异步通信,调用方不需要等待操作完成就可以返回,甚至可能不需要关心这个操作完成与否。这两种不同的通信模式有着各自的协作风格,即请求/响应或者基于事件。对于请求/响应来说,客户端发起一个请求,然后等待响应。这种模式能够与同步通信模式很好地匹配,但异步通信也可以使用这种模式。我可以发起一个请求,然后注册一个回调,当服务端操作结束之后,会调用该回调。对于使用基于事件的协作方式来说,情况会颠倒过来。客户端不是发起请求,而是发布一个事件,然后期待其他的协作者接收到该消息,并且知道该怎么做。

服务与服务之间常见有两种常见的架构风格:编排与协同。

upload successful

对于上图用户注册这样一个业务逻辑,如果用编排的风格来设计服务,我们可以得到如下的架构图:

upload successful
很显然,若编排的架构使用的是同步通信的机制,即请求/响应的协作方式,那么我们能够得到每一个服务返回的结果。当然,编排的架构也能使用基于回调的异步通信。编排架构的缺点在于,客户服务承担了太多的职责,而剩下的三个服务按作者的话来说:会成为贫血的CRUD服务。

如果采用协同的架构风格,我们可以得到下面这个架构图:

upload successful

可以仅仅从客户服务中使用异步的方式触发一个事件,该事件名可以叫作“客户创建”。电子邮件服务、邮政服务及积分账户可以简单地订阅这些事件并且做相应处理,如上图所示,这种方法能够显著地消除耦合。使用协同的方式组织服务,很显然必定是异步的。使用这种发布-订阅的模式,是当前非常流行的一种通信模式,典型的就是消息队列,比如RabbitMQ。这种模式能够很大程度的在服务之间进行解耦,但是它的一个问题在于无法检测服务是否成功执行,比如电子邮件服务出现问题,无法发送邮件,此时整个系统是否能够检测到该问题,并进行处理。一个常见的解决方案是构建一个与业务逻辑流程图相匹配的监控系统。实际的监控活动是针对每个服务的,但最终需要把监控的结果映射到业务流程中。针对请求响应的协同方式,常用有两种技术:RPC和REST(表征性状态转移)风格的HTTP。针对基于事件的协同方式,一般采用消息队列来实现。

微服务的部署相对于单体应用来说,也是一个较为复杂的问题。CI/CD(持续集成/持续部署)是当前非常重要的一种思想。按照书中所说,判断你的项目是否CI,可以通过回答一下几个问题来判断:1.你是否每天签入代码到主线?2.你是否有一组测试来验证修改?3.当构建失败后,团队是否把修复CI当作第一优先级的事情来做?我个人感觉CI/CD应该是用来解决开发和运维之间的矛盾,应该需要一个DevOps系统来实现这种思想。

]]>
<p>微服务的概念网上都有,各种博客都有解释。但是自己没有上手写代码,没有做工程项目,实际上是很难体会的。 微服务与SOA(面向服务的架构)的区别主要在于微服务中每个服务是一个独立的进程,它能够被独立的部署。若该服务宕机,并不影响整个系统中其他服务的正常运行。</p> <p>在开
unix环境高级编程:高级I/O理解 https://rebiocoder.github.io/2021/01/04/unix%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B%EF%BC%9A%E9%AB%98%E7%BA%A7I-O%E7%90%86%E8%A7%A3/ 2021-01-04T13:58:00.000Z 2021-01-04T14:02:25.397Z <![CDATA[

在网络通信中,数据的传输大部分基于socket(socket位于TCP/UDP与HTTP协议之间,当然socket也可以利用SOCK_RAW套接字提供一个数据报接口,用于直接访问下面的网络层,当使用SOCKT_RAW套接字时,应用程序应该构造自己的协议头部)。在unix系统中,将socket的操作抽象为文件的读,写,异常,即一个socket对象可以对应为一个文件描述符(fd)。所以网络I/O与磁盘读写O/I可以属于同一层次上要考虑的问题。

在考虑具体问题之前,首先要明确一个概念,阻塞与非阻塞是对于进程而言:当一个进程想要read一个文件描述符(fd)时,若fd的读缓冲区没有数据,此时进程将会阻塞,直到缓冲区中有数据可以使用,此时进程才会继续向下运行,这种情况便是阻塞I/O。对非阻塞I/O而言,当fd的读缓冲区中没有数据时,进程并不会阻塞在此处,而是立即返回错误,表示该操作如继续执行将会阻塞。下图是一个非阻塞I/O的例子(a.out)

   
upload successful

执行以下代码:

./a.out < /etc/services 2>stderr.out

可以得到如下结果:

upload successful

其中35对应的是EAGAIN信号,显然一次输出到终端的数据是有限的,当数据不能write到终端时,进程并没有阻塞,而是返回信号EAGAIN,表明当前写缓冲区已满, 以上代码便是一种轮询操作。

考虑如下一种场景:当该进程必须要从两个或多个fd中同时读取数据时,在这种情况下,我们不能将进程阻塞在任何一个fd的read方法上,原因是,另一个fd可能已经准备好read了,但进程阻塞,无法对其进行任何操作。为了能够使进程同时处理多个fd,我们的第一个想是fork子进程,或者用多线程同时阻塞处理。但这在实现上较为繁琐,且占用资源。另外,也可以用轮询的方法来遍历fd的状态。第三种方法是用异步I/O:进程告诉内核,当fd准备好可以进行I/O时,用一个信号通知进程,这种方法存在的问题在于:每个进程只能有一个信号,如果信号对多个fd都起作用,那么进程在收到该信号时,无法判断是哪个fd准备就绪。以上三种方法或多或少都有问题,我们主要考虑I/O多路转接技术。
I/O多路转接技术想要实现的作用是:将想监听的fd加入一个列表,用某个函数来监听这个列表,当列表中有fd准备就绪时,返回准备就绪的fd给进程使用。select函数和poll函数便是实现该功能。select函数如下:
upload successful

传给select的参数告诉内核:

我们所关心的描述符

对于每个描述符,我们所关心的条件

愿意等待多长时间

select将会返回:

已经准备好的描述符的总数量

对于读,写,异常,哪些描述符已经准备好了。

该函数返回的fd一定是准备就绪的,调用对应的read,write函数一定不会发生阻塞。select函数的具体实现过程:

upload successful

1
2
3
4
5
6
7
8
9
10
11

1. 从用户态copy fd_set到内核空间
2. 注册回调函数pollwait(将进程挂到每个socket的等待队列中,
当socket准备好后(执行mask状态码进行判断,再唤醒进程))
3. 内核遍历fd,调用每一个fd的poll方法,返回socket的mask状态掩码,即
现在准备好了没有,给fd_set赋值
4.当无可读写mask码时,select睡眠(On Linux, select() modifies
timeout to reflect the amount of time not slept),等睡眠时间到,
再次醒来轮询fd_set(内核态轮询)
5. 有值时返回fd_set,将其copy到用户空间
6. 用户进程变为运行态,循环fd_set,得到准备好的fd。

内核创建一个epoll对象,epoll_ctl函数向epoll对象中添加需要监听的fd,当fd准备就绪时,中断程序会操作epoll对象,而不是操作进程。当进程执行到epoll_wait时,如果就绪列表存在已经准备好的fd,进程将会被唤醒。

upload successful

epoll的作用是提升事件循环查询“io事件”的效率,他允许用户进程同时监听多个文件描述符的io事件即io多路复用。同时epoll在实现上是十分高效的,相对于poll/select,epoll使得进程可以监控更多的文件描述符。epoll采用注册机制,在内核中保存用户关注的文件描述符(红黑树保存),不像select一样每次都需要传入所有的文件描述符,效率更高(减少了从用户空间向内核空间的拷贝)。epoll不像select一样会在内核中轮询所有的文件描述符(低效的),epoll会在不同文件描述符对应的设备的等待队列中添加一个回调函数,在回掉函数中将对应的文件描述符添加至epoll的rdlist队列(高效的),当进程调用epoll_wait时,内核检查该队列是否为空,如果是空则阻塞进程,如果不为空则将该队列拷贝至用户空间,并返回该函数。epoll的两种触发模式:

1.水平触发:只要fd仍然可读或写,每次调用epoll_wait都会返回该文件描述符;

2.边缘触发:只fd仍然可读或写,每次调用epoll_wait只会返回一次该文件描述符;

注意异步I/O与非阻塞I/O的区别,异步I/O,需要用回调函数来处理,fd准备好之后的情况,比较复杂。而非阻塞则是在fd的read,write操作时,一定可以立即得到结果,不会阻塞进程。

]]>
<p>在网络通信中,数据的传输大部分基于socket(socket位于TCP/UDP与HTTP协议之间,当然socket也可以利用SOCK_RAW套接字提供一个数据报接口,用于直接访问下面的网络层,当使用SOCKT_RAW套接字时,应用程序应该构造自己的协议头部)。在unix系统
机器学习(1) https://rebiocoder.github.io/2020/06/01/%E5%AD%A6%E4%B9%A0%EF%BC%881%EF%BC%89/ 2020-06-01T02:27:00.000Z 2021-01-05T04:10:18.129Z <![CDATA[

1.监督学习

回归: 预测连续值

分类:预测离散值

2.无监督学习

聚类算法

鸡尾酒算法


梯度下降
![QOLJD78`A_BYQ_DIY_DGNWG.png](https://i.loli.net/2020/06/01/ugZyM5nDhRqdTUX.png)

正确更新方式是同时更新x1和x2

计算偏导数
3~O_PS3D6DF_JEAU5_NG6AY.png


logistics 函数
![C_CNR7D`BWNRDQI27~R_AZF.png](https://i.loli.net/2020/06/01/KG52sOlePuikzRM.png)

logistics代价函数
I2_EVOY__ZBQVK_ST_KM_XR.png

]]>
<p>1.监督学习</p> <p>回归: 预测连续值</p> <p>分类:预测离散值</p> <p>2.无监督学习</p> <p>聚类算法</p> <p>鸡尾酒算法</p> <hr> <p>梯度下降<br>![QOLJD78`A_BYQ_DIY_DGNWG.png](<a hre
二叉树中序遍历(栈) https://rebiocoder.github.io/2020/04/22/%E6%A0%91%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86/ 2020-04-22T08:09:00.000Z 2021-01-05T04:10:18.325Z <![CDATA[

leetcode94
问题:
栈的方式实现二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def middelTravel(self, r):
stack, ret = [], []
root = r
while stack or root:
if root:
stack.append(root)
root = root.left
else:
s = stack.pop()
ret.append(s.val)
root = s.right
return ret

关键点:
1.根节点先入栈,然后以左节点为根节点,直到左节点为空,将根节点出栈。 以右节点为根节点

]]>
<p>leetcode94<br>问题:<br>栈的方式实现二叉树</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><spa
二叉树子树 https://rebiocoder.github.io/2020/04/22/%E5%8F%89%E6%A0%91%E5%AD%90%E6%A0%91/ 2020-04-22T07:36:00.000Z 2021-01-05T04:10:18.177Z <![CDATA[

leetcode572
问题:判断t是否为s的子树

1
2
3
4
5
6
7
8
9
10
11
python解决方案
class TreeNode:
def __init__(self):
self.val = val
self.left = None
self.right = None
class Solution:
def isSubtitle(t,s):
def up(t):
return (t.val, up(t.left), up(t.right)) if t else None
return str(up(t)) in str(up(s))

注意点:

  • 元组中的元素位置不会发生变化
  • print(“a” in “ab”) 返回true
]]>
<p>leetcode572<br>问题:判断t是否为s的子树</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span
二叉树右视图 https://rebiocoder.github.io/2020/04/22/%E5%8F%89%E6%A0%91%E5%8F%B3%E8%A7%86%E5%9B%BE/ 2020-04-22T06:33:00.000Z 2021-01-05T04:10:18.277Z <![CDATA[

二叉树右视图(leetcode 199)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TreeNode:
def __init__(self,x):
self.val = x
self.left = None
self.right = None

class Solution:
def rightDideView(self, root: TreeNode) -> List[int]:
d = {}
def f(r,i):
if r:
d[i] = r.val
f(r.left, i+1)
f(r.right, i+1)
f(root,0)
return [*d.values()]

思路:

1
先序遍历,注意rightSideView(r->left, i+1)与rightSideView(r-right, i+1)中的i相同,由此可以知道,字典中存储的数据一定是每层的最后一个数据
]]>
<p>二叉树右视图(leetcode 199)</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="l
UML类图 https://rebiocoder.github.io/2020/04/16/ML%E7%B1%BB%E5%9B%BE/ 2020-04-16T03:39:18.000Z 2021-01-05T04:10:17.769Z <![CDATA[
  1. 类图基本属性
    1
    2
    3
    +表示public
    -表示private
    #表示protected
  2. 类与类的基本关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.依赖关系
定义:有两个元素x和y,如果修改x的定义可能会引起对y的定义的修改,则称y依赖与元素x,用带箭头的虚线表示依赖关系,箭头指向被依赖的对象。

2.泛化关系
泛化关系就是子类与父类之间的继承关系。用带空心箭头的实线表示,箭头指向父类

3.关联关系
关联关系可以理解为ER图中的实体关系,用实现表示,上方标识数字来进行确认。

4.聚合关系
聚合关系是一种特殊的关联关系,聚合关系表示的是类之间整体与部分的关系,用带有空心菱形的实线表示,空心菱形指向代表整体的类。

5.实现关系
定义接口,用户带空心箭头的虚线来表示
]]>
<ol> <li>类图基本属性<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span>
python导包时的一些细节问题 https://rebiocoder.github.io/2020/03/22/ython%E4%B8%80%E4%BA%9B%E7%BB%86%E8%8A%82/ 2020-03-22T02:21:00.000Z 2021-01-05T04:10:17.929Z <![CDATA[

python导包时的问题

1.可以使用sys.path,查看python导包的搜素路径;使用sys.modules来查看已经导入内存的模块.

一个细节点: import 模块 import只能导入模块(.py, .pyc,pyd),不能导入模块中的类和函数。如果需要导入模块中的函数或者对象,使用from 模块 import 对象

即为

1
说一个容易忽略的问题,import只能导入模块,不能导入模块中的对象(类、函数、变量等)。如一个模块A(A.py)中有个函数getName,另一个模块不能通过import A.getName将getName导入到本模块,只能用import A。如果想只导入特定的类、函数、变量则用from A import getName即可。

2.嵌套导包

(1)在A.py中导入import B, 在B.py中导入了import C,则在A中只能使用B而不能使用C,即时C已经存在于内存之中。也就是说必须显示导包

(2)第二种情况

1
2
3
4
5
6
7
8
9
[A.py] 
from B import C
class D:
pass

[B.py]
from A import D
class C:
pass

在A中执行C()时,将会报错, 而如果改成import B则可以导入,解释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
另外一种嵌套指,在模块A中import B,而在模块B中import A。这时会怎么样呢?这个在Python列表中由RobertChen给出了详细解释,抄录如下:

[A.py] from B import D class C:pass [B.py] from A import C class D:pass

为什么执行A的时候不能加载D呢?

如果将A.py改为:import B就可以了。

这是怎么回事呢?

RobertChen:这跟Python内部import的机制是有关的,具体到from B import D,Python内部会分成几个步骤:

在sys.modules中查找符号"B"
果符号B存在,则获得符号B对应的module对象。
从的__dict__中获得符号"D"对应的对象,如果"D"不存在,则抛出异常

如果符号B不存在,则创建一个新的module对象,注意,这时,module对象的__dict__为空。执行B.py中的表达式,填充的__dict__ 。

从的__dict__中获得"D"对应的对象,如果"D"不存在,则抛出异常。

所以,这个例子的执行顺序如下:

1、执行A.py中的from B import D

由于是执行的python A.py,所以在sys.modules中并没有存在,首先为B.py创建一个module对象(),注意,这时创建的这个module对象是空的,里边啥也没有,在Python内部创建了这个module对象之后,就会解析执行B.py,其目的是填充这个dict。

2、执行B.py中的from A import C

在执行B.py的过程中,会碰到这一句,首先检查sys.modules这个module缓存中是否已经存在了,由于这时缓存还没有缓存,所以类似的,Python内部会为A.py创建一个module对象(),然后,同样地,执行A.py中的语句。

3、再次执行A.py中的from B import D

这时,由于在第1步时,创建的对象已经缓存在了sys.modules中,所以直接就得到了,但是,注意,从整个过程来看,我们知道,这时还是一个空的对象,里面啥也没有,所以从这个module中获得符号"D"的操作就会抛出异常。如果这里只是import B,由于"B"这个符号在sys.modules中已经存在,所以是不会抛出异常的。

导入包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Package(包) Import

包(Package)可以看成模块的集合,只要一个文件夹下面有个__init__.py文件,那么这个文件夹就可以看做是一个包。包下面的文件夹还可以成为包(子包)。更进一步,多个较小的包可以聚合成一个较大的包,通过包这种结构,方便了类的管理和维护,也方便了用户的使用。比如SQLAlchemy等都是以包的形式发布给用户的。

包和模块其实是很类似的东西,如果查看包的类型import SQLAlchemy type(SQLAlchemy),可以看到其实也是。import包的时候查找的路径也是sys.path。

包导入的过程和模块的基本一致,只是导入包的时候会执行此包目录下的__init__.py而不是模块里面的语句了。另外,如果只是单纯的导入包,而包的__init__.py中又没有明确的其他初始化操作,那么此包下面的模块是不会自动导入的。如:

PA

--__init__.py

--wave.py

--PB1

??--__init__.py

??--pb1_m.py

--PB2

??--__init__.py

??--pb2_m.py

__init__.py都为空,如果有以下程序:

?

import?sys
import?PA.wave??#1
import?PA.PB1???#2
import?PA.PB1.pb1_m?as?m1??#3

import?PA.PB2.pb2_m?#4

PA.wave.getName()?#5

m1.getName()?#6

PA.PB2.pb2_m.getName()?#7
当执行#1后,sys.modules会同时存在PA、PA.wave两个模块,此时可以调用PA.wave的任何类或函数了。但不能调用PA.PB1(2)下的任何模块。当前Local中有了PA名字。

当执行#2后,只是将PA.PB1载入内存,sys.modules中会有PA、PA.wave、PA.PB1三个模块,但是PA.PB1下的任何模块都没有自动载入内存,此时如果直接执行PA.PB1.pb1_m.getName()则会出错,因为PA.PB1中并没有pb1_m。当前Local中还是只有PA名字,并没有PA.PB1名字。

当执行#3后,会将PA.PB1下的pb1_m载入内存,sys.modules中会有PA、PA.wave、PA.PB1、PA.PB1.pb1_m四个模块,此时可以执行PA.PB1.pb1_m.getName()了。由于使用了as,当前Local中除了PA名字,另外添加了m1作为 PA.PB1.pb1_m的别名。

当执行#4后,会将PA.PB2、PA.PB2.pb2_m载入内存,sys.modules中会有PA、PA.wave、PA.PB1、PA.PB1.pb1_m、PA.PB2、PA.PB2.pb2_m六个模块。当前Local中还是只有PA、m1。

下面的#5,#6,#7都是可以正确运行的。

注意的是:如果PA.PB2.pb2_m想导入PA.PB1.pb1_m、PA.wave是可以直接成功的。最好是采用明确的导入路径,对于./..相对导入路径还是不推荐用。
]]>
<p>python导包时的问题</p> <p>1.可以使用sys.path,查看python导包的搜素路径;使用sys.modules来查看已经导入内存的模块.</p> <p>一个细节点: <code>import 模块</code> import只能导入模块(.py, .pyc
[算法设计]动态规划求最长公共子序列 https://rebiocoder.github.io/2020/03/05/%E6%B3%95%E8%AE%BE%E8%AE%A1-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E6%B1%82%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97/ 2020-03-05T05:01:00.000Z 2021-01-05T04:17:24.829Z <![CDATA[

问题: 已知序列str1和序列str2,求出他们的最长公共子序列。

dp[i][j]的定义: str[0-i]str[0-j]所拥有的最长公共子序列。

法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def length_common_strength(str1, str2) -> int:
"""
递归法求解
:param str1:
:param str2:
:return:
"""
def dp(i, j):
if i == -1 or j == -1: return 0
if str1[i] == str2[j]:
return dp(i-1, j-1) + 1
else:
return max(dp(i-1, j), dp(i, j-1))
return dp(len(str1) - 1, len(str2) - 1)

法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def length_common_strength_dp(str1, str2) -> int:
"""
dp背包
:param str1:
:param str2:
:return:
"""
m, n = len(str1), len(str2)
dp = [[0] * (n + 1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if str1[i-1] == str2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
]]>
<p>问题: 已知序列str1和序列str2,求出他们的最长公共子序列。</p> <p><code>dp[i][j]</code>的定义: <code>str[0-i]</code>和<code>str[0-j]</code>所拥有的最长公共子序列。</p> <p>法一:</p>
[算法设计]求最长子序列问题 https://rebiocoder.github.io/2020/03/02/%E6%B3%95%E8%AE%BE%E8%AE%A1-%E6%B1%82%E6%9C%80%E9%95%BF%E5%AD%90%E5%BA%8F%E5%88%97%E9%97%AE%E9%A2%98/ 2020-03-02T10:00:00.000Z 2021-01-05T04:17:07.237Z <![CDATA[

问题: 求无序序列的最长子序列

方法: 动态规划法

关键点:

1
dp[i] : 表示数组中的第i位置的最长子序列的长度,初始默认所有位置的dp[i] = 1

思路: 假设已知Subsequence[i-1]位置的最长子序列为dp[i-1],则dp[i],显然如果subsequence[i]>subsequence[i-1],则dp[i] = dp[i-1] + 1

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <cstdio>
#include <algorithm>
using namespace std;
/*求最长子序列*/

int main() {
const int MaxN = 100;
int Subsequence[MaxN];
int dp[MaxN];
for (int j = 0; j < MaxN; ++j) {
dp[j] = 1;
}
int n;
scanf_s("%d", &n);
if (n != 0) {
for (int i = 0; i < n; ++i) {
scanf_s("%d", &Subsequence[i]); //子序
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (Subsequence[i] > Subsequence[j]) {
dp[i] = max(dp[j] + 1, dp[i]);
}
}
}
printf("输入结束!\n");
for (int j = 0; j < n; ++j) {
printf("%d", Subsequence[j]);
}
printf("\n");
for (int j = 0; j < n; ++j) {
printf("%d", dp[j]);
}
}
return 0;
}

]]>
<p>问题: 求无序序列的最长子序列</p> <p>方法: 动态规划法</p> <p>关键点:</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1<
[算法设计]斐波拉契优化与区间不相交问题 https://rebiocoder.github.io/2020/03/02/%E6%B3%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90-5/ 2020-03-02T03:11:00.000Z 2021-01-05T04:10:18.529Z <![CDATA[

斐波拉契数,最基本的定义用递归,现利用”自底向上”的思路求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <cstdio>
#include <ctime>

int BaseFib(int n) {//暴力递归斐波拉契数列
if (n == 1 || n == 2) return 1; //递归出口
return BaseFib(n - 1) + BaseFib(n - 2);
}

int DpTableFib(int n) {//dpTable,自底向上
int dp[1000] = {0}; //初始化为0
dp[1] = 1;
dp[2] = 1;
for (int i = 3; i <= n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}


int main() {
int n = 20;
int m = n;
printf("%d", BaseFib(n));
printf("%d", DpTableFib(m));
return 0;
}

不相交子区间问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
区间不相交问题
*/
#include <algorithm>
using namespace std;

const int MaxN = 10;
struct interval
{
int x, y; //开区间左右端点
} I[MaxN];

bool cmp(interval a, interval b) {
if (a.x != b.x) return a.x > b.x; //先按照左端点从大到小的顺序排序
return a.y < b.y; // 如果左端点相同, 则按照右端点从小到大的顺序排序
}


int main() {
int n;
while (scanf_s("%d", &n), n!=0){
for (int i = 0; i < n; ++i) {
scanf_s("%d%d", &I[i].x, &I[i].y);
}
sort(I, I + n, cmp);
int ans = 1, lastX = I[0].x;
for (int i = 1; i < n; ++i) {
if (I[i].y <= lastX) {
lastX = I[i].x;
ans++;
}
}
printf("%d",ans);
}
return 0;
}
]]>
<p>斐波拉契数,最基本的定义用递归,现利用”自底向上”的思路求解:</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><sp
[算法设计]全排列与n皇后问题 https://rebiocoder.github.io/2020/02/28/%E6%B3%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90-4/ 2020-02-28T10:37:00.000Z 2021-01-05T04:16:48.341Z <![CDATA[

全排列

1582886314_1_.png

递归算法求全排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
递归求全排
*/
const int MaxN = 10;
const int MAXSIZE = 1000;
int P[MaxN];
bool hashTable[MAXSIZE] = { false }; //空间换取时间
int n;
void generateP(int index) {
if (index == n + 1) {
for (int i = 1; i <= n; ++i) {
printf("%d", P[i]);
}
printf("\n");
return;
}
for (int j = 1; j <= n; ++j) {
if (hashTable[j] == false) {
P[index] = j;
hashTable[j] = true;
generateP(index + 1);
hashTable[j] = false;
}
}
}
int main() {
n = 5;
generateP(1);
}

n皇后问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

//n皇后问题,回溯法
int count = 0;
void queen(int index) {//第n列的皇后
if (index == n + 1) {//此时每一列都有一个皇后
count++;
printf("%d\n",count);
return;
}
for (int i = 1; i <= n; ++i) {
if (hashTable[i] == false) {//没有被使用过
bool flag = true; //表示当前序列与之前的皇后不冲突
for (int pre = 1; pre <= index; ++pre) {//回溯剪枝
//不能在同一对角线上
if (abs(pre - index) == abs(i - P[pre])) {
flag = false;
break;
}
}
if (flag) {
P[index] = i;
hashTable[i] = true;
queen(index + 1);
hashTable[i] = false;
}
}
}

}

int main() {
n = 8;
queen(1);
}
]]>
<p><strong>全排列</strong></p> <p><img src="https://i.loli.net/2020/02/28/brp17aE9OHwfouh.png" alt="1582886314_1_.png"></p> <p><strong>递归算法求全排<
[算法设计]hash https://rebiocoder.github.io/2020/02/27/%E6%B3%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90-3/ 2020-02-27T09:57:00.000Z 2021-01-05T04:10:18.429Z <![CDATA[

hash

基本问题
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdio>
#define MAXSIZE 1000
void HashInt(int a[], int b[]) {
bool c[MAXSIZE] = { false };
int m, n;
printf("a,b数组元素的个数:");
scanf("%d%d", &m, &n);
for (int i = 0; i < m; ++i) {
int tmp;
scanf("%d", &tmp);
a[i] = tmp;
c[tmp] = true;
}//空间换取时间
for (int j = 0; j < n; ++j) {
int tmp;
scanf("%d", &tmp);
b[j] = tmp;
if (c[tmp]) printf("%d在a中存在",tmp);
}

}

字符串hash

大写字符hash核心代码:

1
2
3
for(int i=0; i<len(s): ++i){
id = id*26 + (s[i]- 'A')
}

大小写字母均hash

1
2
3
4
5
6
7
for(int i=0; i<len(s): ++i){
if(s[i]>'A' && s[i]<'Z')
id = id*26 + (s[i]- 'A')
else(s[i]>'a' && s[i]<'z')
id = id*52 + (s[i]-'a') + 26
}

]]>
<p><strong>hash</strong></p> <p>基本问题<br><img src="https://i.loli.net/2020/02/27/AZh6EvzaSJUOTGI.png" alt="image.png"></p> <figure class="hig
[算法设计]基本排序 https://rebiocoder.github.io/2020/02/27/%E6%B3%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90-2/ 2020-02-27T09:02:00.000Z 2021-01-05T04:17:51.369Z <![CDATA[
基本排序算法

bubbleSort

关键点:一轮能够确定一个数,内层循环有:a[j]>a[j+1],保证a[j+1]不溢出,第一轮循环需要从i=1开始,内层循环从j=0开始,j<n-i

1
2
3
4
5
6
7
8
9
10
11
12
void BubbleSort(int a[], int n) {//冒泡排序,a为数据数组, n为数组元素个数
int tmp;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < n - i; ++j) {
if (a[j] > a[j + 1]) {//前一个元素大于后一个元素
tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
}
}
}
}

selectSort

关键点:与bubble刚好相反,它是从前往后排,所以关键在内层循环的int j=i开始点,而冒泡则是内层循环的j<n-i结束的点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void selectSort(int a[], int n) {//选择排序
for (int i = 0; i < n; ++i) {
int m = i;
for (int j = i; j < n; ++j ) {
if (a[m] > a[j]) {
m = j;
}
}
int tmp = a[i];
a[i] = a[m];
a[m] = tmp;
}
}

]]>
<h6 id="基本排序算法"><a href="#基本排序算法" class="headerlink" title="基本排序算法"></a>基本排序算法</h6><p><strong>bubbleSort</strong></p> <p><strong>关键点</strong
[算法设计]求幂集 https://rebiocoder.github.io/2020/02/25/%E6%B3%95%E5%88%86%E6%9E%90/ 2020-02-25T10:12:00.000Z 2021-01-05T04:18:03.253Z <![CDATA[
算法设计与分析(1)

求幂集问题(穷举法)

1
给定一个数n,求出从1-n的所有子集

法一:直接穷举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#define MaxN 100 //元素的最大值
#define MaxSize 100 //子集的最大值

typedef struct{
int n; //子集我的个数
int data[MaxSize][MaxN]; //元素
}PsetType;


void copy(int a[], int b[], int n){//将a数组的值复制给b数组
for(int i=0; i<=n; ++i)
b[i] = a[i];
}

void pset(int n, PsetType &p){
int a[MaxN];
//初始化
p.data[0][0] = 0;
p.n = 1;
//初始化结束
for(int i=1; i<=n; ++i){
int m = p.n; //原幂集中子集的个数
for(int j=0; j<m; ++j){
copy(p.data[j], a, p.data[j][0]);
a[0]++; //子集元素个数加1
a[a[0]] = i;
copy(a, p.data[p.n], a[0]);
p.n++;
}
}
}


void disp(PsetType p){
for(int i=0; i<p.n; ++i){
printf("{");
for(int j=1; j<=p.data[i][0];++j){
printf("%d",p.data[i][j]);
}
printf("}\n");
}
}

int main(){
PsetType p;
int n = 5;
pset(n, p);
disp(p);
return 0;
}

法二:增量穷举法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#define MaxN 100 //元素的最大值
#define MaxSize 100 //子集的最大值

typedef struct{
int n; //子集我的个数
int data[MaxSize][MaxN]; //元素
}PsetType;


void copy(int a[], int b[], int n){//将a数组的值复制给b数组
for(int i=0; i<=n; ++i)
b[i] = a[i];
}

void pset(int n, PsetType &p){
int a[MaxN];
//初始化
p.data[0][0] = 0;
p.n = 1;
//初始化结束
for(int i=1; i<=n; ++i){
int m = p.n; //原幂集中子集的个数
for(int j=0; j<m; ++j){
copy(p.data[j], a, p.data[j][0]);
a[0]++; //子集元素个数加1
a[a[0]] = i;
copy(a, p.data[p.n], a[0]);
p.n++;
}
}
}


void disp(PsetType p){
for(int i=0; i<p.n; ++i){
printf("{");
for(int j=1; j<=p.data[i][0];++j){
printf("%d",p.data[i][j]);
}
printf("}\n");
}
}

int main(){
PsetType p;
int n = 5;
pset(n, p);
disp(p);
return 0;
}

示例图:
1582625906_1_.png

]]>
<h6 id="算法设计与分析-1"><a href="#算法设计与分析-1" class="headerlink" title="算法设计与分析(1)"></a>算法设计与分析(1)</h6><p><strong>求幂集问题(穷举法)</strong></p> <figure
离散结构笔记(1) https://rebiocoder.github.io/2020/02/21/%E6%95%A3%E7%BB%93%E6%9E%84%E7%AC%94%E8%AE%B0-1/ 2020-02-21T05:37:39.000Z 2021-01-05T04:10:18.229Z <![CDATA[

命题: 能判断真假的陈述句称为命题

1
2
3
1. 感叹句,祈使句,疑问句都不是命题
2. 悖论不是命题
3.判断结果不能唯一确定的不是命题

简单命题: 由一个简单陈述句组成(原子命题,不能再分)

复合命题: 简单命题 + 联结词

五种常用的联结词:否定,合取,析取,蕴涵,等价

否定: 非,并非


合取

合取词: “且”

P 且 Q

当且仅当P和Q同时为真

1
2
3
4
5
P并且Q
不但P而且Q
既P又Q
尽管P还Q
虽然P但是Q

析取

析取词: 或

P或Q: 为假,当且仅当P和Q同时为假

分类: 相容或,排斥或


蕴涵词
如果.. 那么 ..

]]>
<p>命题: 能判断真假的陈述句称为命题</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line