【Go基础】加密算法和数据结构

news2024/10/6 12:32:29

文章目录

  • 一、加密算法
    • 1. 对称加密
    • 2. 非对称加密
    • 3. 哈希算法
  • 二、数据结构与算法
    • 1. 链表
    • 2. 栈
    • 3. 堆
    • 4. Trie树

一、加密算法

1. 对称加密

在这里插入图片描述

加密过程的每一步都是可逆的
加密和解密用的是同一组密钥
异或是最简单的对称加密算法

// XOR 异或运算,要求plain和key的长度相同
func XOR(plain string, key []byte) string {
	bPlain := []byte(plain)
	bCipher := make([]byte, len(key))
	for i, k := range key {
		bCipher[i] = k ^ bPlain[i]
	}
	cipher := string(bCipher)
	return cipher
}

DES(Data Encryption Standard)数据加密标准,是目前最为流行的加密算法之一
对原始数据(明文)进行分组,每组64位,最后一组不足64位时按一定规则填充,每一组上单独施加DES算法
DES子密钥生成
初始密钥64位,实际有效位56位,每隔7位有一个校验位,根据初始密钥生成16个48位的子密钥
在这里插入图片描述
在这里插入图片描述

N取值从1到16,N和x有固定的映射表

DES加密过程
在这里插入图片描述
在这里插入图片描述

L1, R1 = f(L0, R0, K1),依此循环,得到L16和R16
S盒替换,输入48位,输出32位,各分为8组,输入每组6位,输出每组4位,分别在每组上施加S盒替换,一共8个S盒

在这里插入图片描述

CBC加密过程
在这里插入图片描述

分组模式,CBC(Cipher Block Chaining )密文分组链接模式,将当前明文分组与前一个密文分组进行异或运算,然后再进行加密
其他分组模式还有ECB, CTR, CFR, OFB

func DesEncryptCBC(text string, key []byte) (string, error) {
	src := []byte(text)
	block, err := des.NewCipher(key) // 用des创建一个加密器cipher
	if err != nil {
		return "", err
	}
	blockSize := block.BlockSize() // 分组的大小,blockSize=8
	src = common.ZeroPadding(src, blockSize) // 填充

	out := make([]byte, len(src)) // 密文和明文的长度一致
	encrypter := cipher.NewCBCEncrypter(block, key) // CBC分组模式加密
	encrypter.CryptBlocks(out, src)
	return hex.EncodeToString(out), nil
}

func DesDecryptCBC(text string, key []byte) (string, error) {
	src, err := hex.DecodeString(text) // 转成[]byte
	if err != nil {
		return "", err
	}
	block, err := des.NewCipher(key)
	if err != nil {
		return "", err
	}
 
	out := make([]byte, len(src)) // 密文和明文的长度一致
	encrypter := cipher.NewCBCDecrypter(block, key) // CBC分组模式解密
	encrypter.CryptBlocks(out, src)
	out = common.ZeroUnPadding(out) // 反填充
	return string(out), nil
}

AES(Advanced Encryption Standard)高级加密标准,旨在取代DES

2. 非对称加密

  • 使用公钥加密,使用私钥解密
  • 公钥和私钥不同
  • 公钥可以公布给所有人
  • 私钥只有自己保存
  • 相比于对称加密,运算速度非常慢

在这里插入图片描述

对称加密和非对称加密结合使用的案例:
小明要给小红传输机密文件,他俩先交换各自的公钥,然后:

  • 小明生成一个随机的AES口令,然后用小红的公钥通过RSA加密这个口令,并发给小红
  • 小红用自己的RSA私钥解密得到AES口令
  • 双方使用这个共享的AES口令用AES加密通信

在这里插入图片描述

RSA是三个发明人名字的缩写:Ron Rivest,Adi Shamir,Leonard Adleman,密钥越长,越难破解,目前768位的密钥还无法破解(至少没人公开宣布),因此可以认为1024位的RSA密钥基本安全,2048位的密钥极其安全,RSA的算法原理主要用到了数论
RSA加密过程:

  • 随机选择两个不相等的质数p和q:p=61, q=53
  • 计算p和q的乘积n:n=3233
  • 计算n的欧拉函数φ(n) = (p-1)(q-1): φ(n) =3120
  • 随机选择一个整数e,使得1< e < φ(n),且e与φ(n) 互质:e=17
  • 计算e对于φ(n)的模反元素d,即求解e*d+ φ(n)*y=1:d=2753, y=-15
  • 将n和e封装成公钥,n和d封装成私钥:公钥=(3233,17),公钥=(3233,2753)
// RSA加密
func RsaEncrypt(origData []byte) ([]byte, error) {
	// 解密pem格式的公钥
	block, _ := pem.Decode(publicKey)
	if block == nil {
		return nil, errors.New("public key error")
	}
	// 解析公钥
	pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) // 目前的数字证书一般都是基于ITU(国际电信联盟)制定的X.509标准
	if err != nil {
		return nil, err
	}
	// 类型断言
	pub := pubInterface.(*rsa.PublicKey)
	// 加密
	return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}

// RSA解密
func RsaDecrypt(ciphertext []byte) ([]byte, error) {
	// 解密
	block, _ := pem.Decode(privateKey)
	if block == nil {
		return nil, errors.New("private key error!")
	}
	// 解析PKCS1格式的私钥
	priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	// 解密
	return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}

ECC(Elliptic Curve Cryptography)椭圆曲线加密算法,相比RSA,ECC可以使用更短的密钥,来实现与RSA相当或更高的安全
定义了椭圆曲线上的加法和二倍运算,椭圆曲线依赖的数学难题是:k为正整数,P是椭圆曲线上的点(称为基点), k*P=Q , 已知Q和P,很难计算出k

func genPrivateKey() (*ecies.PrivateKey, error) {
	pubkeyCurve := elliptic.P256() // 初始化椭圆曲线
	// 随机挑选基点,生成私钥
	p, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader) // 用golang标准库生成公私钥对
	if err != nil {
		return nil, err
	} else {
		return ecies.ImportECDSA(p), nil // 转换成以太坊的公私钥对
	}
}

// ECCEncrypt 椭圆曲线加密
func ECCEncrypt(plain string, pubKey *ecies.PublicKey) ([]byte, error) {
	src := []byte(plain)
	return ecies.Encrypt(rand.Reader, pubKey, src, nil, nil)
}

// ECCDecrypt 椭圆曲线解密
func ECCDecrypt(cipher []byte, prvKey *ecies.PrivateKey) (string, error) {
	if src, err := prvKey.Decrypt(cipher, nil, nil); err != nil {
		return "", err
	} else {
		return string(src), nil
	}
}

3. 哈希算法

哈希函数的基本特征:

  • 输入可以是任意长度
  • 输出是固定长度
  • 根据输入很容易计算出输出
  • 根据输出很难计算出输入(几乎不可能)
  • 两个不同的输入几乎不可能得到相同的输出

SHA(Secure Hash Algorithm) 安全散列算法,是一系列密码散列函数,有多个不同安全等级的版本:SHA-1,SHA-224,SHA-256,SHA-384,SHA-512,其作用是防伪装,防窜扰,保证信息的合法性和完整性
sha-1算法大致过程:

  • 填充,使得数据长度对512求余的结果为448
  • 在信息摘要后面附加64bit,表示原始信息摘要的长度
  • 初始化h0到h4,每个h都是32位
  • h0到h4历经80轮复杂的变换
  • 把h0到h4拼接起来,构成160位,返回
func Sha1(data string) string {
	sha1 := sha1.New()
	sha1.Write([]byte(data))
	return hex.EncodeToString(sha1.Sum(nil))
}

MD5(Message-Digest Algorithm 5)信息-摘要算法5,算法流程跟SHA-1大体相似,MD5的输出是128位,比SHA-1短了32位,MD5相对易受密码分析的攻击,但运算速度比SHA-1快

func Md5(data string) string {
	md5 := md5.New()
	md5.Write([]byte(data))
	return hex.EncodeToString(md5.Sum(nil))
}

哈希函数的应用场景

  • 用户密码的存储
  • 文件上传/下载完整性校验
  • mysql大字段的快速对比

数字签名

在这里插入图片描述
比特币中验证交易记录的真实性用的就是数字签名,先hash再用私钥加密的原因是:非对称加密计算量比较大,先hash可以把原始数据转一条很短的信息,提高计算效率

二、数据结构与算法

1. 链表

在这里插入图片描述

链表的一个应用案例,LRU(Least Recently Used,最近最少使用)缓存淘汰的总体思路:缓存的key放到链表中,头部的元素表示最近刚使用

  • 如果命中缓存,从链表中找到对应的key,移到链表头部
  • 如果没命中缓存:
    • 如果缓存容量没超,放入缓存,并把key放到链表头部
    • 如果超出缓存容量,删除链表尾部元素,再把key放到链表头部

在这里插入图片描述

ring的应用:基于滑动窗口的统计,比如最近100次接口调用的平均耗时、最近10笔订单的平均值、最近30个交易日股票的最高点;ring的容量即为滑动窗口的大小,把待观察变量按时间顺序不停地写入ring即可

package main

import (
	"container/ring"
	"fmt"
)

func TraverseRing(ring *ring.Ring) {
	// 通过Do()来遍历ring,内部实际上调用了Next()而非Prev()
	ring.Do(func(i interface{}) {
		fmt.Printf("%v ", i)
	})
	fmt.Println()
}

func main() {
	ring := ring.New(5) // 必须指定长度,各元素被初始化为nil
	ring2 := ring.Prev()
	for i := 0; i < 3; i++ {
		ring.Value = i
		ring = ring.Next()
	}
	for i := 0; i < 3; i++ {
		ring2.Value = i
		ring2 = ring2.Prev()
	}
	TraverseRing(ring)
	TraverseRing(ring2) // ring和ring2当前所在的指针位置不同,所以遍历出来的顺序也不同
}

2. 栈

栈是一种先进后出的数据结构,push把元素压入栈底,pop弹出栈顶的元素,编程语言的编译系统也用到了栈的思想

在这里插入图片描述

go自带的List已经包含了栈的功能,这里实现一个线程安全的栈

type (
	node struct {
		value interface{}
		prev  *node
	}
	MyStack struct {
		top    *node
		length int
		lock   *sync.RWMutex
	}
)

func NewMyStack() *MyStack {
	return &MyStack{nil, 0, &sync.RWMutex{}}
}

func (stack *MyStack) Push(value interface{}) {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	n := &node{value, stack.top}
	stack.top = n
	stack.length++
}

func (stack *MyStack) Pop() interface{} {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	if stack.length == 0 {
		return nil
	}
	n := stack.top
	stack.top = n.prev
	stack.length--
	return n.value
}

func (stack *MyStack) Peak() interface{} {
	stack.lock.RLock()
	defer stack.lock.RUnlock()
	if stack.length == 0 {
		return nil
	}
	return stack.top.value
}

func (stack *MyStack) Len() int {
	return stack.length
}

func (stack *MyStack) Empty() bool {
	return stack.Len() == 0
}

3. 堆

堆是一棵二叉树,大根堆即任意节点的值都大于等于其子节点,反之为小根堆
用数组来表示堆,下标为 i 的结点的父结点下标为(i-1)/2,其左右子结点分别为 (2i + 1)、(2i + 2)

在这里插入图片描述

构建堆

在这里插入图片描述

package main

import "fmt"

// AdjustTraingle 如果只是修改slice里的元素,不需要传slice的指针;如果要往slice里append或让slice指向新的子切片,则需要传slice指针
func AdjustTraingle(arr []int, parent int) {
	left := 2 * parent + 1
	if left >= len(arr) {
		return
	}

	right := 2 * parent + 2
	minIndex := parent
	minValue := arr[minIndex]
	if arr[left] < minValue {
		minValue = arr[left]
		minIndex = left
	}
	if right < len(arr) {
		if arr[right] < minValue {
			minValue = arr[right]
			minIndex = right
		}
	}
	if minIndex != parent {
		arr[minIndex], arr[parent] = arr[parent], arr[minIndex]
		// 递归,每当有元素调整下来时,要对以它为父节点的三角形区域进行调整
		AdjustTraingle(arr, minIndex)
	}
}

func ReverseAdjust(arr []int) {
	n := len(arr)
	if n <= 1 {
		return
	}
	lastIndex := n / 2 * 2
	fmt.Println(lastIndex)
	// 逆序检查每一个三角形区域
	for i := lastIndex; i > 0; i -= 2 {
		right := i
		parent := (right - 1) / 2
		fmt.Println(parent)
		AdjustTraingle(arr, parent)
	}
}

func buildHeap() {
	arr := []int{62, 40, 20, 30, 15, 10, 49}
	ReverseAdjust(arr)
	fmt.Println(arr)
}

每当有元素调整下来时,要对以它为父节点的三角形区域进行调整

插入元素

在这里插入图片描述

删除堆顶

在这里插入图片描述

下面讲几个堆的应用
堆排序:

  • 构建堆O(N)
  • 不断地删除堆顶O(NlogN)

求集合中最大的K个元素:

  • 用集合的前K个元素构建小根堆
  • 逐一遍历集合的其他元素,如果比堆顶小直接丢弃;否则替换掉堆顶,然后向下调整堆

把超时的元素从缓存中删除:

  • 按key的到期时间把key插入小根堆中
  • 周期扫描堆顶元素,如果它的到期时间早于当前时刻,则从堆和缓存中删除,然后向下调整堆

Golang中的container/heap实现了小根堆,但需要自己定义一个类,实现以下接口:

  • Len() int
  • Less(i, j int) bool
  • Swap(i, j int)
  • Push(x interface{})
  • Pop() x interface{}
type Item struct {
	Value    string
	priority int // 优先级,数字越大,优先级越高
}

type PriorityQueue []*Item

func (pq PriorityQueue) Len() int {
	return len(pq)
}

func (pq PriorityQueue) Less(i, j int) bool {
	// Golang默认提供的是小根堆,而优先队列是大根堆,所以这里要反着定义Less
	return pq[i].priority > pq[j].priority
}

func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
}

// 往slice里append,需要传slice指针
func (pq *PriorityQueue) Push(x interface{}) {
	item := x.(*Item)
	*pq = append(*pq, item)
}

// 让slice指向新的子切片,需要传slice指针
func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1] // 数组最后一个元素
	*pq = old[0 : n-1] // 去掉最一个元素
	return item
}

4. Trie树

Trie树又叫字典权
现有term集合:{分散,分散精力,分散投资,分布式,工程,工程师},把它们放到Trie树里如下图:

在这里插入图片描述

Trie树的根节点是总入口,不存储字符;对于英文,第个节点有26个子节点,子节点可以存到数组里;中文由于汉字很多,用数组存子节点太浪费内存,可以用map存子节点;从根节点到叶节点的完整路径是一个term,从根节点到某个中间节点也可能是一个term,即一个term可能是另一个term的前缀;上图中红圈表示从根节点到本节点是一个完整的term

package main

import "fmt"

type TrieNode struct {
	Word     rune // 当前节点存储的字符。byte只能表示英文字符,rune可以表示任意字符
	Children map[rune]*TrieNode // 孩子节点,用一个map存储
	Term     string
}

type TrieTree struct {
	root *TrieNode
}

// add 把words[beginIndex:]插入到Trie树中
func (node *TrieNode) add(words []rune, term string, beginIndex int) {
	// words已经遍历完了
	if beginIndex >= len(words) {
		node.Term = term
		return
	}

	if node.Children == nil {
		node.Children = make(map[rune]*TrieNode)
	}

	word := words[beginIndex] // 把这个word放到node的子节点中
	if child, exists := node.Children[word]; !exists {
		newNode := &TrieNode{Word: word}
		node.Children[word] = newNode
		newNode.add(words, term, beginIndex+1) // 递归
	} else {
		child.add(words, term, beginIndex+1) // 递归
	}
}

// walk words[0]就是当前节点上存储的字符,按照words的指引顺着树往下走,最终返回words最后一个字符对应的节点
func (node *TrieNode) walk(words []rune, beginIndex int) *TrieNode {
	if beginIndex == len(words)-1 {
		return node
	}
	beginIndex += 1
	word := words[beginIndex]
	if child, exists := node.Children[word]; exists {
		return child.walk(words, beginIndex)
	} else {
		return nil
	}
}

// traverseTerms 遍历一个node下面所有的term,注意要传数组的指针,才能真正修改这个数组
func (node *TrieNode) traverseTerms(terms *[]string) {
	if len(node.Term) > 0 {
		*terms = append(*terms, node.Term)
	}
	for _, child := range node.Children {
		child.traverseTerms(terms)
	}
}

func (tree *TrieTree) AddTerm(term string) {
	if len(term) <= 1 {
		return
	}
	words := []rune(term)

	if tree.root == nil {
		tree.root = new(TrieNode)
	}

	tree.root.add(words, term, 0)
}

func (tree *TrieTree) Retrieve(prefix string) []string {
	if tree.root == nil || len(tree.root.Children) == 0 {
		return nil
	}
	words := []rune(prefix)
	firstWord := words[0]
	if child, exists := tree.root.Children[firstWord]; exists {
		end := child.walk(words, 0)
		if end == nil {
			return nil
		} else {
			terms := make([]string, 0, 100)
			end.traverseTerms(&terms)
			return terms
		}
	} else {
		return nil
	}
}

func main() {
	tree := new(TrieTree)
	tree.AddTerm("分散")
	tree.AddTerm("分散精力")
	tree.AddTerm("分散投资")
	tree.AddTerm("分布式")
	tree.AddTerm("工程")
	tree.AddTerm("工程师")

	terms := tree.Retrieve("分散")
	fmt.Println(terms)
	terms = tree.Retrieve("人工")
	fmt.Println(terms)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/179701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP实现URL长连接转短连接方法总结

依据第二种算法&#xff0c;URL长连接转短连接实现方法如下&#xff1a;语言&#xff1a;PHP5.6服务器环境&#xff1a;LNMP假设&#xff1a;长连接地址&#xff1a;http://www.test.com/index.php短连接地址&#xff1a;http://t.test.com/六位code码第一步&#xff1a;利用sh…

Jupyter使用详解

Jupyter使用详解 本篇文章我们主要介绍Jupyter的使用与配置&#xff0c;本篇文章的主要内容如下&#xff1a; 什么是Jupyter notebookJupyter notebook的安装使用Jupyter notebook 什么是Jupyter notebook&#xff1f; Jupyter Notebook是一个Web应用程序&#xff0c;允许您…

在甲骨文云容器实例(Container Instances)上部署Oracle Linux 8 Desktop加强版(包括Minio,ssh登录等)

甲骨文云推出了容器实例&#xff0c;这是一项无服务器计算服务&#xff0c;可以即时运行容器&#xff0c;而无需管理任何服务器。 今天我们尝试一下通过容器实例部署Oracle Linux 8 Desktop加强版。 加强版里包括&#xff0c;Minio&#xff0c;ssh登录&#xff0c;OCI CLI命令行…

linux基本功系列之-rpm命令实战

文章目录前言&#x1f680;&#x1f680;&#x1f680;一. rpm命令介绍1.1 RPM包介绍1.2 rpm包的优缺点1.3 rpm包获取方式二. 语法格式及常用选项2.1 RPM安装常用参数2.2 rpm格式介绍三. 应用案例3.1 从本地安装软件包3.2 查询lrzsz的包有没有安装3.3 查询命令是哪个包安装的3.…

3.1(完结)Linux扫盲笔记

1. Linux环境下&#xff0c;输入密码&#xff0c;不回回显(*)。 2.普通用户的密码一定不要和root一样&#xff0c;root一定要安全级别更高。具体的添加账户和修改密码的操作&#xff0c;见蛋哥Linux训练营&#xff0c;第2课&#xff0c;30分钟处。 3.在最高权限(root)&#x…

java基础学习 day37 (集合)

集合与数组的区别 长度&#xff1a;数组长度固定&#xff0c;一旦创建完成&#xff0c;就不能改变。集合长度可变&#xff0c;根据添加和删除元素&#xff0c;自动扩容或自动收缩&#xff0c;&#xff08;添加几个元素就扩容多少&#xff0c;删除几个元素就收缩多少&#xff0…

JMeter测试redis性能

JMeter测试redis性能前言插件使用说明前言 针对Redis的性能测试需求本身就比较小众&#xff0c;因为Redis的性能指标在官网已经给出了详细的数据。但是有时候我们仍然需要对redis进行性能测试&#xff0c;例如资源配置需求&#xff0c;参数调优对比&#xff0c;程序优化等场景…

树型结构——二叉数

之前就说过我们的数据结构分为两种&#xff0c;分别是线性结构和非线性结构&#xff0c;我们今天要学的第一种线性结构就是树型结构。 1. 树型结构 树型结构并非我们熟悉的重点&#xff0c;所以在这里只做了解。 概念&#xff1a; 树是一种非线性的数据结构&#xff0c;它是…

【人工智能原理自学】循环:序列依赖问题

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文讲解循环&#xff1a;序列依赖问题&#xff0c;一起卷起来叭&#xff01; 目录一、“序列”二、代码实现一、“序列” 数据除了在空间上可能出现关联性外&#xff0c;也可…

nodejs在线教学网上授课系统vue367

目 录 摘 要 I Abstracts II 目 录 III 第1章 绪论 1 1.1课题背景 1 1.2研究意义 1 1.3研究内容 2 第2章 技术介绍 1 2.1 相关技术 1 1、 node_modules文件夹(有npn install产生) 这文件夹就是在创建完项目后&#xff0c;cd到项目目录执行np…

基于nodejs+vue驾校预约网站管理系统

系统分为用户和管理员&#xff0c;教练三个角色 目 录 第1章 绪论 1 1.1课题背景 1 1.2 背景意义 1 1.3 研究的内容 2 第2章 相关技术 3 第3章 系统分析 5 3.1可行性分析 5 3.2系统性能分析 6 3.3系统流程分析 6 3.3.1操作流程 6 3.3.2信息添加…

Cadence PCB仿真使用Allegro PCB SI生成电源地噪声报告SSN Report及报告导读图文教程

🏡《Cadence 开发合集目录》   🏡《Cadence PCB 仿真宝典目录》 目录 1,概述2,生成报告3,报告导读4,总结1,概述 SSN报告等效的电源和地噪声源报告。本文简单介绍使用Allegro PCB SI生成SSN报告的方法,及其要点导读。 2,生成报告 第1步,选择需要生成报告的网络,…

【绝密】大厂笔试题

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前是C语言学习者 ✈️专栏&#xff1a;C语言刷题 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&…

微信支付账户更换实名认证微信钱包零钱余额还在吗?怎么更换微信钱包实名认证?

原文来源&#xff1a;https://www.caochai.com/article-4119.html 微信支付账户更换实名认证微信钱包零钱余额还在吗&#xff1f; 微信支付账户更换实名认证微信钱包的零钱余额将清空&#xff0c;因为更换微信钱包实名认证的前提条件是微信钱包零钱余额不能大于0元。所以&…

一周学习总结(2022.1.25)

文章目录前言本周任务完成情况1.《Vue.js的设计与实现》2.《计算机网络&#xff1a;自顶向下方法》3.组件库4.青训营笔记5.刷题总结前言 年前给自己定下了一组学习计划&#xff0c;安排了每天需要完成的事情。这里主要记录一下每周任务的完成情况。本周定制的任务主要围绕着《V…

DP初入门

目录 一、前言 二、DP概念 1、最少硬币问题 2、DP的两个特征 三、0/1背包&#xff08;最经典的DP问题&#xff09; 1、小明的背包1&#xff08;lanqiaoOJ题号1174&#xff09; 2、空间优化&#xff1a;滚动数组 1&#xff09;交替滚动 2&#xff09;自我滚动 一、前言…

C语言函数调用详解

所谓函数调用&#xff08;Function Call&#xff09;&#xff0c;就是使用已经定义好的函数。函数调用的一般形式为&#xff1a;functionName(param1, param2, param3 ...);functionName 是函数名称&#xff0c;param1, param2, param3 ...是实参列表。实参可以是常数、变量、表…

【Java开发】Spring Cloud 10 :Stream消息驱动

官方定义Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。它为一些供应商的消息中间件产品提供了个性化的自动化配置实现&#xff0c;Spirng Cloud Stream 本质上就是整合了 Spring Boot 和 Spring Integration&#xff0c;实现一套轻量级的消息驱动的微服…

Python内置包Tkinter的重要控件(下)

本文将接着介绍剩下的五个重要的控件&#xff0c;包括Canvas&#xff0c;Messagebox&#xff0c;Listbox&#xff0c;Checkbutton&#xff0c;Radiobutton。 目录 前言 控件 1. Canvas 2. Messagebox 3. Listbox 4. Radiobutton 5. Checkbutton 总结 前言 包括但不…

VBA提高篇_08 数据源类型判断 / 四舍五入

文章目录数据类型操作1. 数据类型判断2.数据类型转换2.1转换函数2.2 关于小数数据类型的四舍五入2.2.1 银行家舍入法2.2.2 Round()函数2.2.3 Int()函数数据类型操作 1. 数据类型判断 IsDate() 是否是日期类型 IsNumeric() 是否是数值类型 TypeName(x) 返回x 的数据类型的名称…