多叉树+图实现简单业务流程
文章目录
- 场景
- 整体架构流程
- 业务界面
- 技术细节
- 小结
场景
这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念.
整体架构流程

方案管理、预案管理构成任务流程的基础条件,告警信息关联预案配置构成事件,也就是流程启动的入口信息.
业务界面




技术细节
其实也没有什么特殊的技术,也就用到了多叉树、图、最长路径计算(深搜)等.
- 多叉树
import lombok.Data;import java.util.ArrayList;
import java.util.List;/*** @author zwmac*/
@Data
public class MoreParentTreeNode<T> {/*** 节点的唯一标识符*/private Long id;/*** 节点的名称*/private String name;/*** 节点的索引*/private Integer index;/*** 子节点list*/private List<MoreParentTreeNode<T>> children;/*** 父节点list*/private List<MoreParentTreeNode<T>> parents;/*** 扩展信息(一般就存节点对应的原始数据)*/private T extendInfo;public MoreParentTreeNode(Long id, String name,Integer index) {this.id = id;this.name = name;this.index = index;this.children = new ArrayList<>();this.parents = new ArrayList<>();}public MoreParentTreeNode() {}/*** 添加子节点* @param child 子节点*/public void addChild(MoreParentTreeNode<T> child) {if (this.children.contains(child)) {return;}this.children.add(child);child.parents.add(this);}/*** 添加父节点* @param parent 父节点*/public void addParent(MoreParentTreeNode<T> parent) {if (this.parents.contains(parent)) {return;}this.parents.add(parent);parent.children.add(this);}public void traverse() {System.out.println(this.name); // 打印节点名称if (this.children.isEmpty()) {System.out.println("没有子节点");}for (MoreParentTreeNode<T> child : this.children) {child.traverse(); // 递归遍历子节点}}public static void main(String[] args) {MoreParentTreeNode root = new MoreParentTreeNode(1L, "root",1);MoreParentTreeNode node1 = new MoreParentTreeNode(2L, "node1",2);MoreParentTreeNode node2 = new MoreParentTreeNode(3L, "node2",3);MoreParentTreeNode node3 = new MoreParentTreeNode(4L, "node3",4);MoreParentTreeNode node4 = new MoreParentTreeNode(5L, "node4",5);MoreParentTreeNode node5 = new MoreParentTreeNode(5L, "node5",6);MoreParentTreeNode node6 = new MoreParentTreeNode(6L, "node6",7);root.addChild(node1);root.addChild(node2);node1.addChild(node6);node3.addParent(node2);node4.addParent(node2);node4.addParent(node1);node5.addParent(node4);root.traverse();}
}
- 图对象与计算基本方法
import lombok.Data;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** 流程图对象,用于计算最长执行时间* @author zwmac*/
@Data
public class ProcessGraph {/*** 顶点个数*/private int numVertices;/*** 邻接表*/private List<List<Integer>> adjList;/*** 顶点执行时间(边长)*/private Double[] executionTimes;/*** 构造函数* @param numVertices 顶点个数* @param executionTimes 顶点执行时间(边长)*/public ProcessGraph(int numVertices, Double[] executionTimes) {this.numVertices = numVertices;this.executionTimes = executionTimes;adjList = new ArrayList<>(numVertices);for (int i = 0; i < numVertices; i++) {adjList.add(new ArrayList<>());}}/*** 添加边* @param src 源顶点* @param dest 目标顶点*/public void addEdge(int src, int dest) {adjList.get(src).add(dest);}/*** 计算最长执行时间* @return 最长执行时间*/public Double findMaxExecutionTime() {Double[] maxExecutionTimes = new Double[numVertices];Arrays.fill(maxExecutionTimes, -1d);Double maxExecutionTime = 0d;for (int i = 0; i < numVertices; i++) {Double currentExecutionTime = dfs(i, maxExecutionTimes);if (currentExecutionTime > maxExecutionTime) {maxExecutionTime = currentExecutionTime;}}return maxExecutionTime;}/*** 深度优先搜索* @param vertex 顶点* @param maxExecutionTimes 最长执行时间数组* @return 最长执行时间*/private Double dfs(int vertex, Double[] maxExecutionTimes) {if (maxExecutionTimes[vertex] != -1) {return maxExecutionTimes[vertex];}Double maxExecutionTime = executionTimes[vertex];for (int neighbor : adjList.get(vertex)) {Double neighborExecutionTime = dfs(neighbor, maxExecutionTimes);if (neighborExecutionTime + executionTimes[vertex] > maxExecutionTime) {maxExecutionTime = neighborExecutionTime + executionTimes[vertex];}}maxExecutionTimes[vertex] = maxExecutionTime;return maxExecutionTime;}public static void main(String[] args) {Double[] executionTimes = {0d,3d, 4d, 5d, 2d, 3d,0d};ProcessGraph graph = new ProcessGraph(7, executionTimes);graph.addEdge(0, 1);graph.addEdge(0, 2);graph.addEdge(1, 3);graph.addEdge(1, 4);graph.addEdge(2, 3);graph.addEdge(3, 5);graph.addEdge(3, 5);//graph.addEdge(3, 5);Double maxExecutionTime = graph.findMaxExecutionTime();System.out.println("Max execution time: " + maxExecutionTime);}
}
- 多叉树工具类
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.smartPark.business.emergency.event.entity.EmergencyEventTask;
import com.smartPark.business.emergency.event.entity.dto.EmergencyEventTaskDTO;
import org.apache.commons.lang3.StringUtils;import java.util.*;/*** @author zwmac*/
public class EventTaskTreeUtil {/*** 转换流程树* @param eventTaskList 任务列表* @return 流程树根节点*/public static MoreParentTreeNode<?> convertTreeDto(List<EmergencyEventTaskDTO> eventTaskList) {MoreParentTreeNode<?> root = new MoreParentTreeNode<>(0L,"开始",0);EmergencyEventTaskDTO rootEventTaskDto = new EmergencyEventTaskDTO();rootEventTaskDto.setTaskDuration(0d);Map<Long, MoreParentTreeNode<?>> nodeMap = new HashMap<>();for (int i = 0;i < eventTaskList.size();i++) {EmergencyEventTaskDTO eventTask = eventTaskList.get(i);Long id = eventTask.getId();String name = eventTask.getTaskName();MoreParentTreeNode<EmergencyEventTaskDTO> node = new MoreParentTreeNode<>(id,name,(i+1));node.setExtendInfo(eventTask);nodeMap.put(eventTask.getId(), node);}//处理父子关系(正)for (EmergencyEventTaskDTO eventTask : eventTaskList) {transNodeRef(eventTask, nodeMap, root);}//处理父子关系(反)for (int i = eventTaskList.size() - 1;i > -1; i--){EmergencyEventTaskDTO eventTask = eventTaskList.get(i);transNodeRef(eventTask, nodeMap, root);}return root;}/*** 转换流程树* @param eventTaskList 任务列表* @return 流程树根节点*/public static MoreParentTreeNode<?> convertTree(List<EmergencyEventTask> eventTaskList) {MoreParentTreeNode<?> root = new MoreParentTreeNode<>(0L,"开始",0);EmergencyEventTask rootEventTask = new EmergencyEventTask();rootEventTask.setTaskDuration(0d);Map<Long, MoreParentTreeNode<?>> nodeMap = new HashMap<>();for (int i = 0;i < eventTaskList.size();i++) {EmergencyEventTask eventTask = eventTaskList.get(i);Long id = eventTask.getId();String name = eventTask.getTaskName();MoreParentTreeNode<EmergencyEventTask> node = new MoreParentTreeNode<>(id,name,(i+1));node.setExtendInfo(eventTask);nodeMap.put(eventTask.getId(), node);}//处理父子关系(正)for (EmergencyEventTask eventTask : eventTaskList) {EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();BeanUtil.copyProperties(eventTask, eventTaskDTO);transNodeRef(eventTaskDTO, nodeMap, root);}//处理父子关系(反)for (int i = eventTaskList.size() - 1;i > -1; i--){EmergencyEventTask eventTask = eventTaskList.get(i);EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();BeanUtil.copyProperties(eventTask,eventTaskDTO);transNodeRef(eventTaskDTO, nodeMap, root);}return root;}/*** 转换节点关系* @param eventTask 父级任务* @param nodeMap 节点map* @param root 根节点*/private static void transNodeRef(EmergencyEventTaskDTO eventTask, Map<Long, MoreParentTreeNode<?>> nodeMap, MoreParentTreeNode<?> root) {Long id = eventTask.getId();MoreParentTreeNode<?> node = nodeMap.get(id);if (node != null){EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();Object eventTaskObj = node.getExtendInfo();if(eventTaskObj instanceof EmergencyEventTask) {EmergencyEventTask nodeEventTask = (EmergencyEventTask) eventTaskObj;BeanUtil.copyProperties(nodeEventTask, eventTaskDTO);}String preIds = eventTaskDTO.getBeforeTaskIds();if (StringUtils.isEmpty(preIds)){node.addParent(root);root.addChild(node);}else {String[] preIdsArr = preIds.split(",");if (ArrayUtil.isNotEmpty(preIdsArr)){for (String preId : preIdsArr) {MoreParentTreeNode<?> preNode = nodeMap.get(Long.valueOf(preId));//前置任务不为空if (preNode != null){preNode.addChild(node);}}}}nodeMap.put(id, node);}}/*** 获取节点的子节点列表* @param treeNode 节点* @param eventTaskId 事件任务id* @return 子节点列表*/public static List<? extends MoreParentTreeNode<?>> getNodeChildrenList(MoreParentTreeNode<?> treeNode, Long eventTaskId) {List<? extends MoreParentTreeNode<?>> childrenList = itGetNodeChildrenList(treeNode, eventTaskId);if (CollectionUtil.isNotEmpty(childrenList)) {return childrenList;}childrenList = itGetNodeParentList(treeNode, eventTaskId);return childrenList;}/*** 迭代获取节点的父节点列表* @param moreParentTreeNode 父节点* @param eventTaskId 事件任务id* @return 父节点列表*/private static List<? extends MoreParentTreeNode<?>> itGetNodeParentList(MoreParentTreeNode<?> moreParentTreeNode, Long eventTaskId) {List<? extends MoreParentTreeNode<?>> children = null;if (moreParentTreeNode.getId().equals(eventTaskId)){children = moreParentTreeNode.getChildren();}else{List<? extends MoreParentTreeNode<?>> parentTreeNodes = moreParentTreeNode.getParents();if(CollectionUtil.isNotEmpty(parentTreeNodes)) {for (MoreParentTreeNode<?> parentTreeNode : parentTreeNodes) {children = itGetNodeParentList(parentTreeNode, eventTaskId);if (CollectionUtil.isNotEmpty(children)) {break;}}}}return children;}/*** 迭代获取节点的子节点列表* @param moreParentTreeNode 子节点* @param eventTaskId 事件任务id* @return 子节点列表*/private static List<? extends MoreParentTreeNode<?>> itGetNodeChildrenList(MoreParentTreeNode<?> moreParentTreeNode, Long eventTaskId) {List<? extends MoreParentTreeNode<?>> children = null;if (moreParentTreeNode.getId().equals(eventTaskId)){children = moreParentTreeNode.getChildren();}else{List<? extends MoreParentTreeNode<?>> childrenChild = moreParentTreeNode.getChildren();if(CollectionUtil.isNotEmpty(childrenChild)) {for (MoreParentTreeNode<?> parentTreeNode : childrenChild) {children = itGetNodeChildrenList(parentTreeNode, eventTaskId);if (CollectionUtil.isNotEmpty(children)) {break;}}}}return children;}public static void main(String[] args) {List<EmergencyEventTaskDTO> eventTaskList = new ArrayList<>();initTestData(eventTaskList,9);System.out.println(JSONUtil.toJsonStr(eventTaskList));MoreParentTreeNode<?> treeNode = convertTreeDto(eventTaskList);List<?> childrenList = getNodeChildrenList(treeNode, 3L);treeNode.traverse();}private static void initTestData(List<EmergencyEventTaskDTO> eventTaskList, int num) {for (int i = 0;i < num; i++){EmergencyEventTaskDTO eventTask = new EmergencyEventTaskDTO();eventTask.setId(Long.valueOf(i));eventTask.setTaskName("任务-" + (i + 1));eventTask.setTaskDuration(RandomUtil.randomDouble(1,10));if (i > 1){StringJoiner sj = new StringJoiner(",");int n = RandomUtil.randomInt(0,i);for(int j = 0;j < i - 1 - n;j++){sj.add(String.valueOf(j+1));}eventTask.setBeforeTaskIds(sj.toString());}eventTaskList.add(eventTask);}}}
使用思路也特别简单,实际就是将配置任务时只选择了节点上级任务的所有任务构成一个多叉树,然后跟这个树每个节点设置唯一编号,然后转化为图,计算最长路径作为事件的预计完成时间,最后就是各个任务节点的流转了.
/*** 计算预计完成时间* @param event 事件* @return 预计完成时间*/private Date calPlanEndTime(EmergencyEvent event) {Date planStartTime = event.getPlanStartTime();Date finalNow = planStartTime;//先查询预案任务QueryWrapper<EmergencyEventPlan> eventPlanQw = new QueryWrapper<>();eventPlanQw.eq("event_id_", event.getId());List<EmergencyEventPlan> eventPlanList = eventPlanService.list(eventPlanQw);if (CollectionUtil.isNotEmpty(eventPlanList)){//最终预计完成时间//再查询预案任务for (int i = 0;i < eventPlanList.size();i++){QueryWrapper<EmergencyEventTask> eventTaskQw = new QueryWrapper<>();eventTaskQw.eq("event_id_", event.getId());eventTaskQw.eq("event_plan_id_", eventPlanList.get(i).getId());List<EmergencyEventTask> eventTaskList = eventPlanTaskService.list(eventTaskQw);if (CollectionUtil.isNotEmpty(eventTaskList)){//再组织流程树MoreParentTreeNode<?> processTree = EventTaskTreeUtil.convertTree(eventTaskList);//最后计算时间Date planTaskEndTime = calEndTime(processTree, planStartTime, eventTaskList);if (planTaskEndTime.after(finalNow)){finalNow = planTaskEndTime;}}}}return finalNow;}
小结
- 后悔数据结构没有学好
- 算法用的少,也有很大工具包直接提供,但是还是很有学的必要的
就随便写写,希望能帮到大家,uping!
相关文章:
多叉树+图实现简单业务流程
文章目录 场景整体架构流程业务界面技术细节小结 场景 这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念. 整体架构流程 方案管理、预案管理构成任务流程的基础条…...
Word | 简单可操作的快捷公式编号、右对齐和引用方法
1. 问题描述 在理工科论文的写作中,涉及到大量的公式输入,我们希望能够按照章节为公式进行编号,并且实现公式居中,编号右对齐的效果。网上有各种各样的方法来实现,操作繁琐和简单的混在一起,让没有接触过公…...
leetCode 123.买卖股票的最佳时机 III 动态规划 + 状态压缩
123. 买卖股票的最佳时机 III - 力扣(LeetCode) 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意:你不能同时参与多笔交易࿰…...
JavaScript计算两个时间相差多少个小时的封装函数
js中计算两个时间相差小时数 在JavaScript中,你可以使用Date对象来处理日期和时间。下面是一个函数,它接受两个时间字符串作为参数,并返回两者之间的时间差(以小时为单位): function calculateHours(time…...
Qt 画自定义饼图统计的例子
先给出结果图,这个例子是将各种事件分类然后统计的其比例,然后画饼图显示出来 这个是我仿照官方给的例子,让后自己理解后,修改的,要生成饼图,需要QT的 charts 支持,安装QT 没有选择这个的&#…...
【数据结构】链表与LinkedList
作者主页:paper jie 的博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精…...
Flink RoaringBitmap去重
1、RoaringBitmap的依赖 <!-- 去重大哥--> <dependency><groupId>org.roaringbitmap</groupId><artifactId>RoaringBitmap</artifactId><version>0.9.21</version> </dependency> 2、Demo去重 package com.gwm.driver…...
Elasticsearch—(MacOs)
1⃣️环境准备 准备 Java 环境:终端输入 java -version 命令来确认版本是否符合 Elasticsearch 要求下载并解压 Elasticsearch:前往(https://www.elastic.co/downloads/elasticsearch)选择适合你的 Mac 系统的 Elasticsearch 版本…...
插入排序与希尔排序
个人主页:Lei宝啊 愿所有美好如期而遇 前言: 这两个排序在思路上有些相似,所以有人觉得插入排序和希尔排序差别不大,事实上,他们之间的差别不小,插入排序只是希尔排序的最后一步。 目录 前言:…...
C# OpenCvSharp 基于直线检测的文本图像倾斜校正
效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp;namespace OpenCvSharp_基于直线检测的文…...
“智慧时代的引领者:探索人工智能的无限可能性“
目录 一.背景 二.应用 2.1金融领域 2.2医疗领域 2.3教育领域 三.发展 四.总结: 一.背景 人工智能(Artificial Intelligence,简称AI),是指通过计算机程序模拟人类智能的一种技术。它是计算机科学、工程学、语言学、哲学等多…...
PMSM——转子位置估算基于QPLL
文章目录 前言仿真模型观测器速度观测位置观测转矩波形电流波形 前言 今后是电机控制方向的研究生的啦,期待有同行互相交流。 仿真模型 观测器 速度观测 位置观测 转矩波形 电流波形...
Android Studio之Gradle和Gradle插件的区别
解释的很详细 Android Studio之Gradle和Gradle插件的区别...
DataExcel控件读取和保存excel xlsx 格式文件
需要引用NPOI库 https://github.com/dotnetcore/NPOI 调用Read 函数将excel读取到dataexcel控件 调用Save 函数将dataexcel控件文件保存为excel文件 using NPOI.HSSF.UserModel; using NPOI.HSSF.Util; using NPOI.SS.UserModel; using NPOI.SS.Util; using System; using …...
【JavaEE】CAS(Compare And Swap)操作
文章目录 什么是 CASCAS 的应用如何使用 CAS 操作实现自旋锁CAS 的 ABA 问题CAS 相关面试题 什么是 CAS CAS(Compare and Swap)是一种原子操作,用于在无锁情况下保证数据一致性的问题。它包含三个操作数——内存位置、预期原值及更新值。在执…...
第三章:最新版零基础学习 PYTHON 教程(第三节 - Python 运算符—Python 中的关系运算符)
关系运算符用于比较值。它根据条件返回 True 或 False。这些运算符也称为比较运算符。 操作员描述 句法> 大于:如果左操作数大于右操作数,则为 Truex > y...
【GDB】使用 GDB 自动画红黑树
阅读本文前需要的基础知识 用 python 扩展 gdb python 绘制 graphviz 使用 GDB 画红黑树 前面几节中介绍了 gdb 的 python 扩展,参考 用 python 扩展 gdb 并且 python 有 graphviz 模块,那么可以用 gdb 调用 python,在 python 中使用 grap…...
使用Vue3+elementPlus的Tree组件实现一个拖拽文件夹管理
文章目录 1、前言2、分析3、实现4、踩坑4.1、拖拽辅助线的坑4.2、数据的坑4.3、限制拖拽4.4、样式调整 1、前言 最近在做一个文件夹管理的功能,要实现一个树状的文件夹面板。里面包含两种元素,文件夹以及文件。交互要求如下: 创建、删除&am…...
小谈设计模式(7)—装饰模式
小谈设计模式(7)—装饰模式 专栏介绍专栏地址专栏介绍 装饰模式装饰模式角色Component(抽象组件)ConcreteComponent(具体组件)Decorator(抽象装饰器)ConcreteDecorator(具…...
nginx 多层代理 + k8s ingress 后端服务获取客户真实ip 配置
1.nginx http 七层代理 修改命令空间: namespace: nginx-ingress : configmap:nginx-configuration kubectl get cm nginx-configuration -n ingress-nginx -o yaml添加如上配置 compute-full-forwarded-for: “true” forwarded-for-header: X-Forwa…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
