物联网数据安全区块链服务

物联网数据安全区块链服务

下面是一个专为物联网数据安全设计的区块链服务实现,使用Python编写并封装为RESTful API。该服务确保物联网设备数据的不可篡改性、可追溯性和安全性。

import hashlib
import json
import time
from datetime import datetime
from uuid import uuid4
from flask import Flask, jsonify, request, Response
import requests
from urllib.parse import urlparse
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
import sqlite3
import threading# --------------------- 区块链核心实现 ---------------------
class IoTBlockchain:def __init__(self):self.chain = []self.pending_transactions = []self.nodes = set()self.device_keys = {}  # 存储设备公钥 {device_id: public_key}# 创建创世区块self.create_genesis_block()# 创建数据库连接self.db_conn = sqlite3.connect('iot_blockchain.db', check_same_thread=False)self.init_db()# 自动清理线程self.cleanup_thread = threading.Thread(target=self.periodic_cleanup, daemon=True)self.cleanup_thread.start()def init_db(self):"""初始化数据库"""cursor = self.db_conn.cursor()cursor.execute('''CREATE TABLE IF NOT EXISTS device_data (data_id TEXT PRIMARY KEY,device_id TEXT,data_hash TEXT,timestamp REAL,block_index INTEGER,public_key TEXT)''')cursor.execute('''CREATE TABLE IF NOT EXISTS revoked_tokens (token TEXT PRIMARY KEY,revocation_time REAL)''')self.db_conn.commit()def periodic_cleanup(self):"""定期清理旧数据"""while True:time.sleep(3600)  # 每小时清理一次try:cursor = self.db_conn.cursor()# 保留最近48小时的数据cutoff = time.time() - 48 * 3600cursor.execute("DELETE FROM revoked_tokens WHERE revocation_time < ?", (cutoff,))self.db_conn.commit()except Exception as e:print(f"清理错误: {e}")def create_genesis_block(self):"""创建创世区块"""genesis_block = {'index': 0,'timestamp': time.time(),'transactions': [],'proof': 100,'previous_hash': '0','merkle_root': '0'}self.chain.append(genesis_block)def register_device(self, device_id, public_key):"""注册物联网设备"""if device_id in self.device_keys:return Falseself.device_keys[device_id] = public_keyreturn Truedef verify_signature(self, device_id, data, signature):"""验证设备签名"""if device_id not in self.device_keys:return Falsepublic_key = self.device_keys[device_id]try:# 在实际应用中应使用更安全的验证方法# 这里简化实现pub_key = serialization.load_pem_public_key(public_key.encode(),backend=default_backend())# 在实际应用中应使用pub_key.verify()方法# 这里简化验证过程calculated_hash = self.hash_data(data)return calculated_hash == signatureexcept:return Falsedef hash_data(self, data):"""计算数据的哈希值"""if isinstance(data, dict):data_str = json.dumps(data, sort_keys=True)else:data_str = str(data)return hashlib.sha256(data_str.encode()).hexdigest()def create_merkle_root(self, transactions):"""创建Merkle树根哈希"""if not transactions:return "0"# 计算所有交易的哈希hashes = [self.hash_data(tx) for tx in transactions]while len(hashes) > 1:new_hashes = []# 两两配对计算哈希for i in range(0, len(hashes), 2):if i + 1 < len(hashes):combined = hashes[i] + hashes[i + 1]else:combined = hashes[i] + hashes[i]  # 奇数个时复制最后一个new_hashes.append(hashlib.sha256(combined.encode()).hexdigest())hashes = new_hashesreturn hashes[0]def new_transaction(self, device_id, data, signature):"""创建新的物联网数据交易"""# 验证签名if not self.verify_signature(device_id, data, signature):return None# 生成唯一数据IDdata_id = str(uuid4())# 存储到数据库(在实际应用中应使用更安全的存储)cursor = self.db_conn.cursor()cursor.execute("INSERT INTO device_data (data_id, device_id, data_hash, timestamp, public_key) VALUES (?, ?, ?, ?, ?)",(data_id, device_id, self.hash_data(data), time.time(), self.device_keys[device_id])self.db_conn.commit()# 添加到待处理交易transaction = {'data_id': data_id,'device_id': device_id,'data_hash': self.hash_data(data),'timestamp': time.time(),'signature': signature}self.pending_transactions.append(transaction)return data_iddef mine(self):"""挖矿创建新区块"""if not self.pending_transactions:return Nonelast_block = self.last_blocklast_proof = last_block['proof']proof = self.proof_of_work(last_proof)# 创建Merkle根merkle_root = self.create_merkle_root(self.pending_transactions)# 创建新区块block = {'index': len(self.chain),'timestamp': time.time(),'transactions': self.pending_transactions,'proof': proof,'previous_hash': self.hash_block(last_block),'merkle_root': merkle_root}# 重置待处理交易self.pending_transactions = []# 添加到区块链self.chain.append(block)return blockdef proof_of_work(self, last_proof):"""简单的工作量证明算法"""proof = 0while not self.valid_proof(last_proof, proof):proof += 1return proofdef valid_proof(self, last_proof, proof):"""验证工作量证明"""guess = f'{last_proof}{proof}'.encode()guess_hash = hashlib.sha256(guess).hexdigest()# 调整难度:要求前3位为0(可根据需求调整)return guess_hash[:3] == "000"def hash_block(self, block):"""计算区块的哈希值"""block_string = json.dumps(block, sort_keys=True).encode()return hashlib.sha256(block_string).hexdigest()@propertydef last_block(self):"""获取最后一个区块"""return self.chain[-1]def validate_chain(self):"""验证区块链的完整性"""for i in range(1, len(self.chain)):current_block = self.chain[i]previous_block = self.chain[i-1]# 检查区块哈希是否正确if current_block['previous_hash'] != self.hash_block(previous_block):return False# 检查工作量证明if not self.valid_proof(previous_block['proof'], current_block['proof']):return False# 验证Merkle根calculated_merkle = self.create_merkle_root(current_block['transactions'])if calculated_merkle != current_block['merkle_root']:return False# 验证交易签名for tx in current_block['transactions']:if tx['device_id'] not in self.device_keys:return False# 在实际应用中应使用公钥验证签名# 这里简化验证过程stored_data = self.get_data_by_id(tx['data_id'])if not stored_data or stored_data['data_hash'] != tx['data_hash']:return Falsereturn Truedef get_data_by_id(self, data_id):"""根据ID获取数据记录"""cursor = self.db_conn.cursor()cursor.execute("SELECT * FROM device_data WHERE data_id = ?", (data_id,))row = cursor.fetchone()if row:return {'data_id': row[0],'device_id': row[1],'data_hash': row[2],'timestamp': row[3],'block_index': row[4],'public_key': row[5]}return Nonedef revoke_token(self, token):"""撤销JWT令牌"""cursor = self.db_conn.cursor()cursor.execute("INSERT OR REPLACE INTO revoked_tokens (token, revocation_time) VALUES (?, ?)",(token, time.time()))self.db_conn.commit()return Truedef is_token_revoked(self, token):"""检查令牌是否已被撤销"""cursor = self.db_conn.cursor()cursor.execute("SELECT token FROM revoked_tokens WHERE token = ?", (token,))return cursor.fetchone() is not None# --------------------- Flask应用和API ---------------------
app = Flask(__name__)
blockchain = IoTBlockchain()# 生成RSA密钥对用于API认证
private_key = rsa.generate_private_key(public_exponent=65537,key_size=2048,backend=default_backend()
)
public_key = private_key.public_key()# 序列化密钥
private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8,encryption_algorithm=serialization.NoEncryption()
).decode()public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode()# API密钥(仅用于演示)
API_KEYS = {"admin": "supersecretkey123","device_manager": "devicekey456"
}# JWT认证装饰器
def jwt_required(f):def decorated_function(*args, **kwargs):token = Noneif 'Authorization' in request.headers:token = request.headers['Authorization'].split(" ")[1]if not token:return jsonify({'message': 'Token is missing'}), 401if blockchain.is_token_revoked(token):return jsonify({'message': 'Token has been revoked'}), 401try:# 在实际应用中应验证签名# 这里简化验证过程data = jwt.decode(token, algorithms=["HS256"], options={"verify_signature": False})request.current_user = data['sub']except:return jsonify({'message': 'Token is invalid'}), 401return f(*args, **kwargs)return decorated_function@app.route('/api/auth', methods=['POST'])
def authenticate():"""获取JWT令牌"""auth_data = request.get_json()if not auth_data or 'api_key' not in auth_data:return jsonify({'message': 'Missing API key'}), 400api_key = auth_data['api_key']if api_key not in API_KEYS.values():return jsonify({'message': 'Invalid API key'}), 401# 确定用户角色role = "user"for k, v in API_KEYS.items():if v == api_key:role = kbreak# 生成JWT令牌(在实际应用中应设置合理过期时间)token = jwt.encode({'sub': role,'iat': datetime.utcnow(),# 'exp': datetime.utcnow() + timedelta(hours=1)  # 设置过期时间}, 'secret', algorithm='HS256')  # 在实际应用中应使用更安全的密钥return jsonify({'token': token})@app.route('/api/devices/register', methods=['POST'])
@jwt_required
def register_device():"""注册物联网设备"""if request.current_user != 'admin':return jsonify({'message': 'Unauthorized'}), 403data = request.get_json()if not data or 'device_id' not in data or 'public_key' not in data:return jsonify({'message': 'Missing device ID or public key'}), 400device_id = data['device_id']public_key = data['public_key']if blockchain.register_device(device_id, public_key):return jsonify({'message': f'Device {device_id} registered successfully'}), 201else:return jsonify({'message': f'Device {device_id} already registered'}), 400@app.route('/api/data/submit', methods=['POST'])
@jwt_required
def submit_data():"""提交物联网数据"""data = request.get_json()if not data or 'device_id' not in data or 'data' not in data or 'signature' not in data:return jsonify({'message': 'Missing required fields'}), 400device_id = data['device_id']sensor_data = data['data']signature = data['signature']data_id = blockchain.new_transaction(device_id, sensor_data, signature)if data_id:return jsonify({'message': 'Data submitted successfully','data_id': data_id}), 201else:return jsonify({'message': 'Invalid device signature'}), 400@app.route('/api/chain/mine', methods=['POST'])
@jwt_required
def mine_block():"""挖矿创建新区块"""if request.current_user != 'admin':return jsonify({'message': 'Unauthorized'}), 403block = blockchain.mine()if block:return jsonify({'message': 'New block mined','block': block}), 201else:return jsonify({'message': 'No transactions to mine'}), 400@app.route('/api/chain', methods=['GET'])
@jwt_required
def get_full_chain():"""获取完整区块链"""return jsonify({'chain': blockchain.chain,'length': len(blockchain.chain)}), 200@app.route('/api/data/<data_id>', methods=['GET'])
@jwt_required
def get_data(data_id):"""根据ID获取数据记录"""data_record = blockchain.get_data_by_id(data_id)if data_record:# 在实际应用中应返回更多信息return jsonify({'data_id': data_record['data_id'],'device_id': data_record['device_id'],'timestamp': data_record['timestamp'],'block_index': data_record['block_index']}), 200else:return jsonify({'message': 'Data not found'}), 404@app.route('/api/validate', methods=['GET'])
@jwt_required
def validate_chain():"""验证区块链完整性"""is_valid = blockchain.validate_chain()if is_valid:return jsonify({'message': 'Blockchain is valid'}), 200else:return jsonify({'message': 'Blockchain is invalid'}), 400@app.route('/api/auth/revoke', methods=['POST'])
@jwt_required
def revoke_token():"""撤销当前令牌"""token = request.headers['Authorization'].split(" ")[1]if blockchain.revoke_token(token):return jsonify({'message': 'Token revoked successfully'}), 200else:return jsonify({'message': 'Failed to revoke token'}), 400@app.route('/api/public_key', methods=['GET'])
def get_public_key():"""获取API公钥(用于客户端验证)"""return Response(public_pem, mimetype='text/plain')# --------------------- 设备模拟器 ---------------------
class IoTDeviceSimulator:def __init__(self, device_id):self.device_id = device_id# 生成设备密钥对self.private_key = rsa.generate_private_key(public_exponent=65537,key_size=2048,backend=default_backend())self.public_key = self.private_key.public_key().public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo).decode()def sign_data(self, data):"""签名数据(简化实现)"""# 在实际应用中应使用私钥签名# 这里返回数据的哈希作为简化签名if isinstance(data, dict):data_str = json.dumps(data, sort_keys=True)else:data_str = str(data)return hashlib.sha256(data_str.encode()).hexdigest()def generate_data(self):"""生成模拟传感器数据"""return {'temperature': round(20 + 10 * (time.time() % 1), 2),'humidity': round(40 + 30 * ((time.time() + 0.3) % 1), 2),'pressure': round(980 + 40 * ((time.time() + 0.7) % 1), 2),'timestamp': time.time()}# --------------------- 主程序 ---------------------
if __name__ == '__main__':import argparseparser = argparse.ArgumentParser(description='IoT Blockchain API Server')parser.add_argument('-p', '--port', type=int, default=5000, help='Port to run the server on')parser.add_argument('-d', '--debug', action='store_true', help='Run in debug mode')args = parser.parse_args()# 注册一个模拟设备sim_device = IoTDeviceSimulator("sensor-001")blockchain.register_device(sim_device.device_id, sim_device.public_key)# 启动Flask应用app.run(host='0.0.0.0', port=args.port, debug=args.debug)

功能说明

1. 核心区块链功能

  • 设备注册:物联网设备使用公钥注册到系统
  • 数据提交:设备提交带签名的传感器数据
  • 区块挖矿:将待处理数据打包进新区块
  • Merkle树:用于高效验证区块中的交易
  • 工作量证明:简单的PoW共识机制

2. 安全特性

  • 设备身份验证:每个设备使用公私钥对进行身份验证
  • 数据签名:设备对提交的数据进行签名
  • API认证:使用JWT令牌保护API端点
  • 令牌撤销:支持撤销已发放的JWT令牌

3. 数据管理

  • SQLite数据库:存储设备数据和撤销令牌
  • 数据检索:通过数据ID查询数据记录
  • 自动清理:定期清理旧数据

4. RESTful API 端点

端点方法描述认证
/api/authPOST获取JWT令牌API密钥
/api/devices/registerPOST注册新设备admin令牌
/api/data/submitPOST提交传感器数据有效JWT
/api/chain/minePOST挖矿创建新区块admin令牌
/api/chainGET获取完整区块链有效JWT
/api/data/<data_id>GET获取数据记录有效JWT
/api/validateGET验证区块链完整性有效JWT
/api/auth/revokePOST撤销当前令牌有效JWT
/api/public_keyGET获取API公钥

部署和使用指南

1. 启动服务

python iot_blockchain.py

2. 获取API令牌

curl -X POST http://localhost:5000/api/auth \-H "Content-Type: application/json" \-d '{"api_key": "supersecretkey123"}'

3. 注册物联网设备

curl -X POST http://localhost:5000/api/devices/register \-H "Authorization: Bearer <JWT_TOKEN>" \-H "Content-Type: application/json" \-d '{"device_id": "sensor-002", "public_key": "-----BEGIN PUBLIC KEY-----\n..."}'

4. 提交传感器数据

curl -X POST http://localhost:5000/api/data/submit \-H "Authorization: Bearer <JWT_TOKEN>" \-H "Content-Type: application/json" \-d '{"device_id": "sensor-001","data": {"temperature": 25.5, "humidity": 60},"signature": "<DATA_SIGNATURE>"}'

5. 挖矿创建新区块

curl -X POST http://localhost:5000/api/chain/mine \-H "Authorization: Bearer <JWT_TOKEN>"

6. 查看区块链

curl http://localhost:5000/api/chain \-H "Authorization: Bearer <JWT_TOKEN>"

系统架构图

加密数据
物联网设备
区块链网关
区块链API
区块链核心
区块数据
设备注册
交易池
数据库
设备数据
撤销令牌
认证服务

实际应用建议

  1. 增强安全性

    • 使用硬件安全模块(HSM)管理密钥
    • 实现真正的数字签名验证
    • 添加传输层加密(HTTPS)
  2. 性能优化

    • 使用更高效的共识算法(如PoS)
    • 实现分片技术处理大量设备
    • 使用分布式数据库(如Cassandra)
  3. 扩展功能

    • 添加设备管理面板
    • 实现数据分析和告警功能
    • 支持设备固件验证
  4. 存储优化

    • 使用IPFS存储大型传感器数据
    • 实现数据压缩和聚合
    • 添加时间序列数据库支持
  5. 隐私保护

    • 实现零知识证明验证
    • 添加数据脱敏功能
    • 支持差分隐私

这个区块链服务为物联网数据提供了强大的安全保障,确保数据的完整性和不可篡改性,同时通过API接口提供了灵活的集成方式。

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

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

相关文章

数据集-目标检测系列- 卡车 数据集 truck >> DataBall

数据集-目标检测系列- 卡车 数据集 truck &#xff1e;&#xff1e; DataBall贵在坚持&#xff01;* 相关项目1&#xff09;数据集可视化项目&#xff1a;gitcode: https://gitcode.com/DataBall/DataBall-detections-100s/overview2&#xff09;数据集训练、推理相关项目&…

vue/微信小程序/h5 实现react的boundary

ErrorBoundary react的boundary实现核心逻辑无法处理的情况包含函数详细介绍getDerivedStateFromError和componentDidCatch作用为什么分开调用 代码实现&#xff08;补充其他异常捕捉&#xff09;函数组件与useErrorBoundary&#xff08;需自定义Hook&#xff09; vue的boundar…

Day113 切换Node.js版本、多数据源配置

切换Node.js版本 1.nvm简介nvm(Node Version Manager)&#xff0c;在Windows上管理Node.js版本&#xff0c;可以在同一台电脑上轻松管理和切换多个Node.js版本 nvm下载地址&#xff1a;https://github.com/coreybutler/nvm-windows/2.配置nvm安装之后检查nvm是否已经安装好了&a…

应急响应靶机-linux2-知攻善防实验室

题目&#xff1a; 1.提交攻击者IP2.提交攻击者修改的管理员密码(明文)3.提交第一次Webshell的连接URL(http://xxx.xxx.xxx.xx/abcdefg?abcdefg只需要提交abcdefg?abcdefg)4.提交Webshell连接密码5.提交数据包的flag16.提交攻击者使用的后续上传的木马文件名称7.提交攻击者隐藏…

新手前端使用Git(常用命令和规范)

发一篇文章来说一下前端在开发项目的时候常用的一些git命令 注&#xff1a;这篇文章只说最常用的&#xff0c;最下面有全面的 一&#xff1a;从git仓库拉取项目到本地 1&#xff1a;新建文件夹存放项目代码 2&#xff1a;在git上复制一下项目路径&#xff08;看那个顺眼复制…

【面试题】常用Git命令

【面试题】常用Git命令1. 常用Git命令1. 常用Git命令 1.git clone git clone https://gitee.com/Blue_Pepsi_Cola/straw.git 2.使用-v选项&#xff0c;可以参看远程主机的网址 git remote -v origin https://ccc.ddd.com/1-java/a-admin-api.git (fetch) origin https://ccc.…

Webpack构建工具

构建工具系列 Gulp构建工具Grunt构建工具Webpack构建工具Vite构建工具 Webpack构建工具 构建工具系列前言一、安装打包配置webpack安装样式加载器devtoolwebpack devtool 配置详解常见 devtool 值及适用场景选择建议性能影响注意事项 module处理流程module.rulesmodule.usemod…

重学前端002 --响应式网页设计 CSS

文章目录 css 样式特殊说明 根据在这里 Freecodecamp 实践&#xff0c;调整顺序后做的总结。 css 样式 body {background-color: red; # 跟background-image 不同时使用background-image: url(https://cdn.freecodecamp.org/curriculum/css-cafe/beans.jpg);font-family: san…

RabbitMQ简单消息监听和确认

如何监听RabbitMQ队列 简单代码实现RabbitMQ消息监听 需要的依赖 <!--rabbitmq--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId><version>x.x.x</version>&l…

Docker学习笔记:Docker网络

本文是自己的学习笔记 1、Linux中的namespace1.1、创建namespace1.2、两个namespace互相通信2、Docker中的namespace2.1 容器中的默认Bridge3、容器的三种网络模式1、Linux中的namespace Docker中使用了虚拟网络技术&#xff0c;让各个容器的网络隔离。好像每个容器从网卡到端…

用自定义注解解决excel动态表头导出的问题

导入的excel有固定表头动态表头如何解决 自定义注解&#xff1a; import java.lang.annotation.*;/*** 自定义注解&#xff0c;用于动态生成excel表头*/ Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface FieldLabel {// 字段中文String label(…

Android-EDLA 解决 GtsMediaRouterTestCases 存在 fail

问题描述&#xff1a;[原因]R10套件新增模块&#xff0c;getRemoteDevice获取远程蓝牙设备时&#xff0c;蓝牙MAC为空 [对策]实际蓝牙MAC非空;测试时绕过处理 1.release/ebsw_skg/skg/frameworks/base/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManage…

双涡轮增压器结构设计cad【5张】+设计说明书

摘要 随着汽车制造商和消费者对动力性能的要求不断增加&#xff0c;发动机需要在更宽的转速范围内提供更大的功率和扭矩。双涡轮增压器可以帮助实现这一目标&#xff0c;通过在不同转速下调整涡轮的工作状态来提供更平顺的动力输出。单一涡轮增压器可能存在涡轮滞后和增压延迟…

大数据轻量化流批一体架构探索实践(一)

最近学习了解到一种轻量化&#xff0c;维护门槛较低的流批一体化的架构方式&#xff0c;虽然目前还是不太成熟&#xff0c;自己也在探索学习中。 dolphinschedulerdinkystarrocksflinkzookeper 后面我会逐步发一下这个整体架构的特点&#xff0c;以及各个组件作用&#xff0c;和…

【2025/07/04】GitHub 今日热门项目

GitHub 今日热门项目 &#x1f680; 每日精选优质开源项目 | 发现优质开源项目&#xff0c;跟上技术发展趋势 &#x1f4cb; 报告概览 &#x1f4ca; 统计项&#x1f4c8; 数值&#x1f4dd; 说明&#x1f4c5; 报告日期2025-07-04 (周五)GitHub Trending 每日快照&#x1f55…

HarmonyOS学习记录3

HarmonyOS学习记录3 本文为个人学习记录&#xff0c;仅供参考&#xff0c;如有错误请指出。本文主要记录ArkTS基础语法&#xff0c;仅记录了部分我觉得与其他语言不太类似的地方&#xff0c;具体规范请参考官方文档。 参考官方文档&#xff1a;https://developer.huawei.com/co…

HKS201-M24 大师版 8K60Hz USB 3.0 适用于 2 台 PC 1台显示器 无缝切换 KVM 切换器

HKS201-M24 8K60Hz HDMI 2.1 2x1 KVM 切换器&#xff0c;适用于 2 台 PC&#xff0c;带 EDID 仿真、千兆 LAN、双充电和 USB 3.2 Gen 1 HKS201-M24 产品概述 TESmart 重新定义智能工作空间&#xff0c;无缝双PC控制。 真正的 8K60Hz 亮度&#xff0c;具有 EDID 稳定性和超快速…

stm32f103vct6的DAC口的输出电压达不到3.3V

问题&#xff1a;调试时发现自己设置的DAC在最大时达不到3.3V&#xff0c;总结了原因&#xff0c;记录下。 原因&#xff1a;使用时&#xff0c;注意有没有其他负载&#xff0c;有的话最好给负载独立供电&#xff0c;不要只用STM32f103的板凑活着供电&#xff0c;我的就是这个…

java8 Collectors.mapping 使用 例子 学习

java8 Collectors.mapping 使用 例子 学习 Map<String, List<String>> colorApple appleList.stream().collect(Collectors.groupingBy(Apple::getColor, Collectors.mapping(Apple::getVariety, toList()))); colorApple.forEach((k, v) -> {System.out.prin…

动态规划-P1216 [IOI 1994] 数字三角形 Number Triangles

P1216 [IOI 1994] 数字三角形 Number Triangles 题目来源-洛谷题库 思路 如果用贪心只是找当前的到达该点的路径最大值&#xff0c;可能结果无法做到最优最值问题试着看能否将大问题分解成若干个小问题 走到a[i] [j ]这个点的最值来源于上一步a[i-1 ] [j]和a[i-1] [j-1]的最…