所謂遞歸算法,是將問題轉化為規模縮小的同類問題的子問題,每一個子問題都用一個同樣的算法去解決。一般來說,一個遞歸算法就是函數調用自身去解決它的子問題。
一、節點數據集合
- var nodes = [{
- "id": "EQ06-9006C8BBE68A475BAC04858F60E0249D",
- "name": "題目1",
- "type": "解答題",
- "children": [{
- "id": "EQ06-CC80CFD8702F47E4A42AF63B0BA10569",
- "name": "題目1.1",
- "type": "解答題",
- "score": 5
- }, {
- "id": "EQ06-06F7EBA807D445EB937B920ED2250016",
- "name": "題目1.2",
- "type": "解答題",
- "score": 6
- }],
- "score": 10
- }, {
- "id": "EQ06-0F0A8DA4B5834D1BB96504546EA47DA9",
- "name": "題目2",
- "type": "解答題",
- "children": [{
- "id": "EQ06-660D9E79B5054EA296D15EFA6D1F5462",
- "name": "題目2.1",
- "type": "解答題",
- "score": 10
- }, {
- "id": "EQ06-C5D5191018B94E8C8D1E9AB3AD5E4C61",
- "name": "題目2.2",
- "type": "解答題",
- "score": 20,
- "children": [{
- "id": "EQ06-037CBCDFD897434A839619D4AF07E030",
- "name": "題目2.2.1",
- "type": "解答題",
- "score": 5
- }, {
- "id": "EQ06-FC7CAE767F4E48D6B3E0B78D5E4BC473",
- "name": "題目2.2.2",
- "type": "解答題",
- "score": 6
- }, {
- "id": "EQ06-FC7CAE767F4E48D6B3E0B78D5E4BC474",
- "name": "題目2.2.3",
- "type": "解答題",
- "score": 7
- }]
- }],
- "score": 40
- }];
起初朋友給我發了一段數據,輸出的是題目節點信息,其中包含題目的分數值:score,原始數據已經過樹形結構處理了。
他的需求是:題目的分數值 score 是創建題目時預估定義的一個分數值,如20分;實際情況是在某個大題的下面還可以創建小題,小題也會定義一個分數值 score,這樣一來,某個大題原先預估定義的分數值 score 可能與它下面所有小題的分數值 score 加起來的和並不相等,這樣大題的分數值 score 就顯得不準確,希望我在前端能幫忙處理一下,使得大題的分數值等於彙總了其下所有小題的分數值之和,以符合題目分數在實際展現時的準確無誤。
接到朋友的這個 issue,我想了一下,大致要求就是針對於某個節點的屬性(這裡是 score),彙總累加其下所有子節點的這個屬性(score)的數值,然後將這一過程中累加得到的數值賦到父節點的這個屬性(score)上。
為了實現樹型結構數據的遍歷與處理,免不了要想到使用遞歸函數來實現。我們先來看一下定義,所謂遞歸算法,是將問題轉化為規模縮小的同類問題的子問題,每一個子問題都用一個同樣的算法去解決。一般來說,一個遞歸算法就是函數調用自身去解決它的子問題。
遞歸算法的特點:
在函數過程中調用自身。
在遞歸過程中,必須有一個明確的條件判斷遞歸的結束,既遞歸出口。
遞歸算法簡潔但效率低,通常不作為推薦算法。
上面這是原理性的解釋,講的也是十分明確,下面我們結合實例來細細琢磨。
二、編寫一個遞歸算法,實現彙總累加節點的分數值
- var getScoreSum = function (data, options){
- //默認參數配置
- var options = {
- scoreField: options.scoreField || "score", //分數屬性名
- childrenField: options.childrenField || "children", //子節點集合屬性名
- spread: options.spread || false
- };
- var scoreSum = 0; //定義父級節點彙總分數的局部變量
- if(data && data.length > 0){
- for(var i = 0, item; i < data.length; i++){
- item = data[i];
- var itemScore = item[options.scoreField] || 0, itemChildren = item[options.childrenField] || []; //定義本次循環中的局部變量
- var tempSum = 0; //定義當前條目彙總分數的臨時變量
- //是否有子節點
- if(itemChildren && itemChildren.length > 0){
- var tempCldSum = getScoreSum(itemChildren, options) || 0; //遞歸調用 getScoreSum() 方法,獲取當前節點下的子節點集合的彙總分數 tempCldSum
- tempSum += tempCldSum; //子節點集合的彙總分數 tempCldSum,追加到當前節點彙總分數 tempSum 上
- }else{
- tempSum += itemScore; //沒有子節點,以當前節點的分數值追加到當前節點彙總分數 tempSum 上
- delete item[options.childrenField]; //刪除屬性
- }
- item["scoreSum"] = tempSum; //已有或一個新的節點屬性:scoreSum,並使用當前節點彙總分數 tempSum 賦值
- item["name"] = item.name + '(' + (item.scoreSum || item.score) + ' 分)' //其他屬性設置
- item["spread"] = options.spread;
- scoreSum += tempSum; //當前節點彙總分數追加到父級節點彙總分數上
- }
- }
- return scoreSum;
- }
在方法中用到了調用方法自身的執行語句,目的是遍歷所有子節點,做相同的累加處理。
三、調用遞歸函數,並測試題目的分數值在實際數據展現時是否準確無誤
- //layui 模塊化組件引用
- layui.use(['jquery', 'layer', 'tree'], function () {
- var $ = layui.$, layer = layui.layer, tree = layui.tree;
- //分數彙總處理
- getScoreSum(nodes, {scoreField: "score", childrenField: "children", spread: true});
- //console.log(nodes);
- //構建樹節點
- var buildTreeNodes = function (options) {
- options = options || {};
- var treeNodes = [{ name: "全部分組", spread: true, children: nodes }];
- $("#treeGroup").empty(); //先清空DOM
- layui.tree({
- elem: '#treeGroup',
- nodes: treeNodes,
- click: function (node) {
- var self = $(this.elem);
- var nodeLinks = self.find("li>a");
- nodeLinks.removeClass("active");
- nodeLinks.filter(function (index) {
- return $('cite', this).text() == node.name;
- }).addClass('active');
- nodeLinks.splice(0, 1); //刪除數組第一項:全部分組
- console.log(node); //node即為當前點擊的節點數據
- document.getElementById("nodeContent").innerHTML = JSON.stringify(node, null, 2); //使用JSON.stringify() 格式化輸出JSON字符串
- }
- });
- if (options.selectedNodeName) {
- var elem = $("#treeGroup");
- var nodeLinks = elem.find("ul>li>a");
- nodeLinks.removeClass("active");
- nodeLinks.filter(function (index) {
- return $('cite', this).text() == options.selectedNodeName;
- }).trigger('click');
- }
- }
- buildTreeNodes();
- });
在這裡我用的是 layui 框架中的 tree 組件來展現遞歸處理後的數據。新建一個 scoreSum.html 和 scoreSum.js 文件,在 scoreSum.html 中放一個 id="tree" 的元素,用來加載遞歸處理彙總累加分數值之後的樹形節點數據。
scoreSum.html 頁面的代碼如下:
- <title>使用遞歸彙總累加題目分數值/<title>
- <link>
- <style>
- body{
- margin: 12px;
- }
- .layui-tree li a.active{
- border-color: lightblue;
- background-color: #b1deff;
- }
- <fieldset>
- <legend>題目索引/<legend>
大題下面有小題,大題的分數是彙總其下各小題的分數
- 正在加載...
- <fieldset>
- <legend>題目信息/<legend>
題目的分數值以“scoreSum”為準,“score”是從數據庫中讀出來的,僅作參考。
點擊左邊節點,以加載節點信息……
scoreSum.js 存放的就是上面幾段腳本:
- //節點數據集合
- var nodes = [{
- ……
- }];
- //使用遞歸算法,實現彙總累加節點的分數值
- var getScoreSum = function (data, options){
- ……
- }
- //layui 模塊化組件引用
- layui.use(['jquery', 'layer', 'tree'], function () {
- var $ = layui.$, layer = layui.layer, tree = layui.tree;
- ……
- buildTreeNodes();
- });
四、效果截圖
閱讀更多 班馬文章BMWZ 的文章