javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题
2014-08-07 10:03
866 查看
赫夫曼树及其应用
赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用。
最优二叉树(Huffman树)
1 基本概念
① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径。
② 路径长度:结点路径上的分支数目称为路径长度。
③ 树的路径长度:从树根到每一个结点的路径长度之和。
以下图为例:
View Code
回溯法与树的遍历
在程序设计中,有相当一类求一组解,或求全部解或求最优解的问题,例如八皇后问题等,不是根据某种确定的计算法则,而是利用试探和回溯(backtracking)的搜索技术求解。回溯法也是设计递归过程的一种重要方法,它的求解过程实质上是一个选序遍历一棵“状态树”的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中。
求含n个元素的集合的幂集:
集合A的幂集是由集合A的所有子集所组成的集合。如:A = {1,2,3},则A的幂集p(A) = {{1,2,3}, {1,2}, {1,3}, {1}, {2,3}, {2}, {2}, 空集}
求幂集p(A)的元素的过程可看成是依次对集合A中元素进行“取”或“舍”的过程,并且可用一棵二叉树来表示过程中幂集元素的状态变化状况。
树中根结点表示幂集元素的初始状态(为空集);
叶子结点表示它的终结状态;
而第i(i=2,3,...,n-1)层的分支结点,则表示已对集合A中前i-1个元素进行了取/舍处理的当前状态(左分支表示“取”,右分支表示“舍”)。
如下图所示:
求八皇后问题的所有合法布局:
同理,我们可以用回溯法解决八皇后问题。
首先,我们把问题简化为四皇后问题。
在求解过程,棋盘的状态变化就像一棵四叉树,树上每个结点表示一个局部布局或一个完整布局。根结点表示棋盘的初始状态:棋盘上无任何棋子。每个(皇后)棋子都有4个可选择的位置,但在任何时刻,棋盘的合法布局都必须满足任何两个棋子都不占据棋盘上同一行,或者同一列,或者同一对角线。
求所有合法布局的过程即为上述约束条件下先根遍历状态树的过程。
遍历中访问结点的操作为,判别棋盘上是否已得到一个完整的布局(即棋盘上是否已有4个棋子)。
若是,则输出该布局;
否则依次先根遍历满足约束条件的各棵子树,即首先判断该子树根的布局是否合法;
若合法,则先根遍历该子树;
否则剪去该子树分支。
为了通用性,我将代码实现成n皇后问题:
赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用。
最优二叉树(Huffman树)
1 基本概念
① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径。
② 路径长度:结点路径上的分支数目称为路径长度。
③ 树的路径长度:从树根到每一个结点的路径长度之和。
以下图为例:
// 赫夫曼树和赫夫曼编码的存储结构 function HuffmanNode(weight, parent, leftChild, rightChild) { this.weight = weight || 0; this.parent = parent || 0; this.leftChild = leftChild || 0; this.rightChild = rightChild || 0; } function huffManCoding(weights) { var n = weights.length; if (n < 1) return; var huffmanTree = buildHuffmanTree(weights, n); // 从叶子到根逆向求每个字符的赫夫曼编码 var hc = calcHuffmanCode(huffmanTree, n); return [huffmanTree, hc]; } function calcHuffmanCode(huffmanTree, n) { // 从叶子到根逆向求每个字符的赫夫曼编码 var hc = []; var cd = []; for (var i = 0; i < n; i++) { var start = n - 1; for (var c = i, f = huffmanTree[i].parent; f != 0; c = f, f = huffmanTree[f].parent) { if (huffmanTree[f].leftChild == c) cd[--start] = '0'; else cd[--start] = '1'; } hc[i] = strCopy(cd, start); } return hc; } // 创建一棵叶子结点数为n的Huffman树 function buildHuffmanTree(weights, n) { n = n || weights.length; var m = 2 * n - 1; var huffmanTree = []; // 初始化 for (var i = 0; i < n; i++) huffmanTree[i] = new HuffmanNode(weights[i], 0, 0, 0); for (; i < m; i++) huffmanTree[i] = new HuffmanNode(0, 0, 0, 0); for (i = n; i < m; i++) { // 在HT[1..i-1]选择parent为0且weight最小的两个结点,返回其序号为[s1, s2] var ret = select(huffmanTree, i); var s1 = ret[0]; var s2 = ret[1]; huffmanTree[s1].parent = i; huffmanTree[s2].parent = i; huffmanTree[i].leftChild = s1; huffmanTree[i].rightChild = s2; huffmanTree[i].weight = huffmanTree[s1].weight + huffmanTree[s2].weight; } return huffmanTree; } function strCopy(str, start) { var s = ''; for (; str[start]; start++) { s += str[start]; } return s; } function select(huffmanTree, len) { var ret = []; for (var i = 0; i < len; i++) { var node = huffmanTree[i]; if (node.parent !== 0) continue; if (ret.length < 2) { ret.push(i); } else { var index = huffmanTree[ret[0]].weight > huffmanTree[ret[1]].weight ? 0 : 1; if (node.weight < huffmanTree[ret[index]].weight) ret[index] = i; } } if (ret[0] > ret[1]) { var temp = ret[0]; ret[0] = ret[1]; ret[1] = temp; } return ret; } console.log('-------huffman coding :------'); console.log(huffManCoding([5, 29, 7, 8, 14, 23, 3, 11]));
View Code
回溯法与树的遍历
在程序设计中,有相当一类求一组解,或求全部解或求最优解的问题,例如八皇后问题等,不是根据某种确定的计算法则,而是利用试探和回溯(backtracking)的搜索技术求解。回溯法也是设计递归过程的一种重要方法,它的求解过程实质上是一个选序遍历一棵“状态树”的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中。
求含n个元素的集合的幂集:
集合A的幂集是由集合A的所有子集所组成的集合。如:A = {1,2,3},则A的幂集p(A) = {{1,2,3}, {1,2}, {1,3}, {1}, {2,3}, {2}, {2}, 空集}
求幂集p(A)的元素的过程可看成是依次对集合A中元素进行“取”或“舍”的过程,并且可用一棵二叉树来表示过程中幂集元素的状态变化状况。
树中根结点表示幂集元素的初始状态(为空集);
叶子结点表示它的终结状态;
而第i(i=2,3,...,n-1)层的分支结点,则表示已对集合A中前i-1个元素进行了取/舍处理的当前状态(左分支表示“取”,右分支表示“舍”)。
如下图所示:
// 求含集合aList的幂集 // 进入函数时已对A中前i-1个元素做了取舍处理 function getPowerSet(i, aList) { var bList = []; void function recurse(i, aList) { if (i > aList.length - 1) console.log('set: ' + bList); else { var x = aList[i]; bList.push(x); recurse(i + 1, aList); bList.pop(); recurse(i + 1, aList); } }(i, aList); return bList; } console.log('getPowerSet:'); var list = [1, 2, 3]; console.log('list: ' + getPowerSet(0, list));
求八皇后问题的所有合法布局:
同理,我们可以用回溯法解决八皇后问题。
首先,我们把问题简化为四皇后问题。
在求解过程,棋盘的状态变化就像一棵四叉树,树上每个结点表示一个局部布局或一个完整布局。根结点表示棋盘的初始状态:棋盘上无任何棋子。每个(皇后)棋子都有4个可选择的位置,但在任何时刻,棋盘的合法布局都必须满足任何两个棋子都不占据棋盘上同一行,或者同一列,或者同一对角线。
求所有合法布局的过程即为上述约束条件下先根遍历状态树的过程。
遍历中访问结点的操作为,判别棋盘上是否已得到一个完整的布局(即棋盘上是否已有4个棋子)。
若是,则输出该布局;
否则依次先根遍历满足约束条件的各棵子树,即首先判断该子树根的布局是否合法;
若合法,则先根遍历该子树;
否则剪去该子树分支。
为了通用性,我将代码实现成n皇后问题:
// 求n皇后问题的所有合法布局 function Queen(n) { var board = []; var count = 0; this.init = function(){ for(var i = 0; i < n; i++){ board[i] = []; for(var j = 0; j < n; j++){ board[i][j] = 0; } } }; this.init(); this.getSolutionsCount = function(){ return count; }; this.printCurrentLayout = function () { ++count; console.log(board); }; this.addPoint = function (i, j) { if(board[i][j] === 0){ board[i][j] = 1; return true; } else { console.log('already occupated!'); return false; } }; this.isCurrentLayoutLegal = function (i, j) { return checkHorizontal(i, j) && checkVertical(i, j) && checkLeftTop2RightBottom(i, j) && checkRightTop2LeftBottom(i, j); }; function checkHorizontal(x, y){ for(var i = 0; i < y; i++){ if(board[x][i] === 1) return false; } for(i = y + 1; i < n; i++){ if(board[x][i] === 1) return false; } return true; } function checkVertical(x, y){ for(var i = 0; i < x; i++){ if(board[i][y] === 1) return false; } for(i = x + 1; i < n; i++){ if(board[i][y] === 1) return false; } return true; } function checkLeftTop2RightBottom(x, y){ for(var i = 1; x - i >= 0 && y - i >= 0; i++){ if(board[x - i][y - i] === 1) return false; } for(i = 1; x + i < n && y + i < n; i++){ if(board[x + i][y + i] === 1) return false; } return true; } function checkRightTop2LeftBottom(x, y){ for(var i = 1; x - i >= 0 && y + i < n; i++){ if(board[x - i][y + i] === 1) return false; } for(i = 1; x + i < n && y - i >= 0; i++){ if(board[x + i][y - i] === 1) return false; } return true; } this.removePoint = function (i, j) { if(board[i][j] === 1){ board[i][j] = 0; } }; var me = this; this.trial = function trial(i) { i = i || 0; if (i > n - 1) { me.printCurrentLayout(); } else { for (var j = 0; j < n; j++) { if(me.addPoint(i, j)){ if (me.isCurrentLayoutLegal(i, j)) trial(i + 1); me.removePoint(i, j); } } } }; } var test = new Queen(8); test.trial(); console.log('getSolutionsCount: ' + test.getSolutionsCount());
相关文章推荐
- javascript实现数据结构: 树和二叉树,二叉树的遍历和基本操作
- 数据结构二叉树的递归与非递归遍历之java,javascript,php实现可编译(1)java
- javascript实现数据结构: 树和二叉树,二叉树的遍历和基本操作
- 数据结构(二叉树系列)先序创建三种遍历和求深度(递归实现)
- 1291 数据结构上机测试4.1:二叉树的遍历与应用1
- 二叉树的全部遍历实现与应用
- 回溯法实现八皇后问题
- SDUT 1291数据结构上机测试4.1:二叉树的遍历与应用1
- 数据结构-----后序遍历二叉树非递归算法(利用堆栈实现)
- 数据结构--二叉树代码实现(建立,前序,中序,后续遍历,树的深度,销毁)
- 数据结构----二叉树(1)遍历,建立,应用
- 编程之美 3.10 分层遍历二叉树 扩展问题代码实现
- c++实现数据结构的二叉树及其遍历二叉树
- 数据结构二叉树的java实现,包括二叉树的创建、搜索、删除和遍历
- 马的遍历问题-回溯法应用-ACM
- 数据结构的应用——使用栈和递归实现Hanoi问题求解
- 数据结构上机测试4.1:二叉树的遍历与应用1
- 21-数据结构_链式二叉树-遍历实现
- 数据结构的C实现_二叉树的非递归遍历和层序遍历
- 数据结构(C实现)------- 遍历二叉树