赫夫曼(Huffman)树,又称最优树,是一类带权路径长度长度最短的树。
赫夫曼树,最优二叉树,从树的一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称路径长度。树的路径长度是从树根到每一个结点的路径长度纸盒。结点的带权路径长度为从该结点到树根之间的路径长度与结点上的权的乘积。
树的带权路径长度为树中所有叶子结点的带权路径长度之和WPL = (W1*L1+W2*L2+W3*L3+…+Wn*Ln)
构造赫夫曼树的算法:
1.根据给定的n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根节点,其左右子树为空。
2.在F中选取两棵根结点的权值最小的树作为左右子树构成一棵新的二叉树,且置新的二叉树的根结点的权值为左、右子树上根结点的权值之和。
3.在F中删除这两棵树,同时将新得到的二叉树加入F中。
4.重复2和3,直到F只含有一棵树为止。这棵树便是赫夫曼树。
下面给出赫夫曼树的Java实现:
结点类:
package com.lintcode.example;
import java.util.LinkedList;
public class HuffmanTree {
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
TreeNode r1 = new TreeNode(5);
TreeNode r2 = new TreeNode(10);
TreeNode r3 = new TreeNode(0);
r1.left = r2;
root.left = r1;
root.right = r3;
System.out.println("原始树的先序遍历结果:");
pretraverseR(root);
System.out.println();
TreeNode huffRoot = BinaryHuffmanTree(root);
System.out.println("赫夫曼的先序遍历结果:");
pretraverseR(huffRoot);
}
public static class TreeNode{
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value) {
this.value = value;
this.left = this.right = null;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public TreeNode getLeft() {
return left;
}
public void setLeft(TreeNode left) {
this.left = left;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
}
//先序遍历(用于打印测试结点)
public static void pretraverseR(TreeNode root){
if(root == null)return;
System.out.print(root.getValue()+" ");
if(root.left != null)pretraverseR(root.left);
if(root.right != null)pretraverseR(root.right);
}
//采用先序遍历将结点存入LinkedList中
public static LinkedList<TreeNode> treeToList(TreeNode node, LinkedList<TreeNode> list){
if(node == null)return list;
list.add(node);
if(node.left != null)
{
treeToList(node.left,list);
}
if(node.right != null)
{
treeToList(node.right,list);
}
return list;
}
/**
* 将一个树转换为赫夫曼树
* @param node 该节点为一个树的根节点
* @return 赫夫曼树的根节点
*/
public static TreeNode BinaryHuffmanTree(TreeNode root){
//首先需要遍历树,将树的每一个节点存入一个LinkedList集合中
LinkedList<TreeNode> list = new LinkedList<TreeNode>();
list = treeToList(root, list);
return buildHuffmanTree(list);
}
/**
* 依据list中每一个结点构造赫夫曼树
* @param list
* @return 赫夫曼树的根节点
*/
private static TreeNode buildHuffmanTree(LinkedList<TreeNode> list) {
//1.首先需要将list中每一个结点的左右子树变为空
for(TreeNode node : list)
{
if(node.left != null)
{
//将该节点的左结点置为空
node.setLeft(null);
}
if(node.right != null)
{
node.setRight(null);
}
}
//2.进行循环构造
while(list.size() > 1)
{
//最小值(作为右节点)
int min1 = findMinNode(list);
TreeNode rightNode = list.remove(min1);
//次小值(作为左结点)
int min2 = findMinNode(list);
TreeNode leftNode = list.remove(min2);
//同时生成这两个结点的父节点
TreeNode parent = new TreeNode(rightNode.getValue()+leftNode.getValue());
parent.setLeft(leftNode);
parent.setRight(rightNode);
list.add(parent);
}
return list.get(0);
}
/**
* 找出list集合中最小的值
* @param list
* @return 最小值在list中的位置
*/
private static int findMinNode(LinkedList<TreeNode> list) {
int min = list.get(0).getValue();
int index = 0;
for(int i=0;i<list.size();i++)
{
if(list.get(i).getValue() < min)
{
min = list.get(i).getValue();
index = i;
}
}
return index;
}
}
KMP算法
1.暴力匹配算法:
主串S(i)和模式串T(j)进行匹配,如果S匹配到i位置,模式串匹配到j位置
1.如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;
2.如果当前字符匹配不成功(即S[i] != P[j]),则i=i-(j-1),j=0,相当与i回溯,j置为IE
2.KMP算法:
假设文本串S匹配到i位置,模式串T匹配到j位置
如果j=-1或者当前字符匹配成功,都令i++,j++,继续匹配下一个字符;
如果j!=-1或者当前字符匹配失败(即S[I] != T[j]),则让i不变,j=next[j].此举意味着当匹配失败时,模式串T向右移动了j-next[j]个位置;
换言之,当匹配失败时,模式串向右移动的位数:失配字符所在位置-失配字符对应的next值。
next[j]值的计算:
寻找最长前缀和后缀
如果给定的模式串为“ABCDABD”,从左到右遍历整个字符串,得到表格如下:
模式串中的各个字串 | 前缀 | 后缀 | 最大公共长度 |
A | 空 | 空 | 0 |
AB | A | B | 0 |
ABC | A,AB | C,BC | 0 |
ABCD | A,AB,ABC | D,CD,BCD | 0 |
ABCDA | A,AB,ABC,ABCD | A,DA,CDA,BCDA | 1 |
ABCDAB | A,AB,ABC,ABCD,ABCDA | B,AB,DAB,CDAB,BCDAB | 2 |
ABCDABD | A,AB,ABC,ABCD,ABCDA,ABCDAB | D,BD,ABD,DABD,CDABD,BCDABD | 0 |
首先求得每个字符的最大公共长度,得到0,0,0,0,1,2,0
然后计算每个字符对应的next[j]的值,只需要将所得的最大公共长度向右移动一位得到模式串的next表格
模式串 | A | B | C | D | A | B | D |
next | -1 | 0 | 0 | 0 | 0 | 1 | 2 |
上面就是求得模式串的next值的方法,下面给出next值优化
为什么会出现优化了,问题出现在P[j]=P[next[j]],为什么了,当P[j] != S[i],按着next方法,下面匹配的必须为P[next[j]]与S[i]的匹配,y因为P[j]=P[next[j]],两个值相同,再次比较必然是不匹配的,所以就不能让P[j]=P[next[j]],如果出现了P[j]=P[next[j]],则让其再次递归,next[j]=next[next[j]]
给出主串为“abacababc”,模式串为abab,利用上面给出的方法,首先求出模式串的next值为-1,0,0,1
采用优化过的next数组求值,得表格如下:
模式串 | a | b | a | b |
最大长度值 | 0 | 0 | 1 | 2 |
未优化的next数组 | next[0]=-1 | next[1]=0 | next[2]=0 | next[3]=1 |
索引值 | p0 | p1 | p2 | p3 |
优化理由 | 初值不变 | p[1] != p[next[1]] | 因p[j] != p[next[j]],即p[2]不能等于p[next[]2] | 因p[j] != p[next[j]],即p[3]不能等于p[next[3]] |
措施 | 无需处理 | 无需处理 | next[2]=next[next[2]]=-1 | next[3]=next[next[3]]=0 |
优化的next数组 | -1 | 0 | -1 | 0 |
即只要出现p[next[j]]=p[j]的时候,就需要将next[j]再次递归。
图的深度优先遍历和广度优先遍历:
此处采用数组存储结构,使用两个数组分别存储图的点和边的信息。
下面给出示例代码:
package com.niuke.example;
import java.util.LinkedList;
import java.util.Queue;
//掌握图的深度优先搜索遍历和图的广度优先搜索遍历
//采用数组表示法,使用数组分别存储点的信息,和边的信息
public class Graph {
//存储结点信息
private Object[] vertices;
//存储边的信息
private int[][] arcs;
//当前图总顶点数
private int vexnum;
//记录第i个结点是否已经被访问了
private boolean[] visited;
public static void main(String[] args) {
//首先创建一个副图
Graph g = new Graph(8);
//结点权值
Character[] vertices = { '0', '1', '2', '3', '4', '5', '6', '7' };
g.addVertex(vertices);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 3);
g.addEdge(1, 4);
g.addEdge(3, 7);
g.addEdge(4, 7);
g.addEdge(2, 5);
g.addEdge(2, 6);
//0,1,3,7,4,2,5,6
System.out.println("深度优先遍历:");
g.depthTraverse();
System.out.println();
System.out.println("广度优先遍历:");
g.broadTraverse();
}
//深度优先搜索遍历
public void depthTraverse(){
//1.首先将标志位置为false
for(int i=0;i<vexnum;i++)
{
visited[i] = false;
}
for(int i=0;i<vexnum;i++)
{
if(!visited[i])
{
//如果没有访问到,则进行遍历
traverse(i);
}
}
}
public void traverse(int i){
visited[i] = true;
System.out.print(vertices[i]+" ");
for(int j=this.firstAdjVex(i);j > 0;j = this.nextAdjVex(i, j))
{
if(!visited[j])
{
this.traverse(j);
}
}
}
//广度优先搜索遍历
public void broadTraverse(){
Queue<Integer> q = new LinkedList<Integer>();
//1.将访问标识位置为false
for(int i=0;i<vexnum;i++)
{
visited[i] = false;
}
for(int i=0;i<vexnum;i++)
{
if(!visited[i])
{
q.add(i);
visited[i] = true;
System.out.print(vertices[i]+" ");
while(!q.isEmpty())
{
int j = q.remove().intValue();
for(int k=this.firstAdjVex(j);k >= 0;k = this.nextAdjVex(j, k))
{
boolean flag = visited[k];
if(!flag)
{
q.add(k);
visited[k] = true;
System.out.print(vertices[k]+" ");
}
}
}
}
}
}
public Graph(){};
public Graph(int n)
{
vexnum = n;//设置顶点数
vertices = new Object[n];//new出一个权值数组
arcs = new int[n][n];//new出一个n*n的二维矩阵,用于存储边的信息
visited = new boolean[n];//new出一个访问标志位
for(int i=0;i<vexnum;i++)
{
for(int j=0;j<vexnum;j++)
{
arcs[i][j] = 0;
}
}
}
//添加结点信息
public void addVertex(Object[] obj)
{
this.vertices = obj;
}
//添加边
public void addEdge(int i, int j)
{
if(i == j)return;
arcs[i][j] = 1;
//无向图 此处不需要,有向图需要添加
// arcs[j][i] = 1;
}
//求以i为起点,求出第一个跟i相通的路径
public int firstAdjVex(int i)
{
for(int j=0;j<vexnum;j++)
{
if(arcs[i][j] > 0)
{
return j;
}
}
return -1;
}
//求下一条与i相通的路径
public int nextAdjVex(int i, int k)
{
for(int j=k+1;j<vexnum;j++)
{
if(arcs[i][j] > 0)
{
return j;
}
}
return -1;
}
}
迪杰斯特拉算法求最短路径(从某个源点到其余各顶点的最短路径)
算法描述:
1.假定用带权的邻接矩阵arcs来表示带权有向图,arcs[i][j]表示弧<vi,vj>上的权值。若<vi,vj>不存在,则置arcs[i][j]为∞(在计算机上可用允许的最大值代替)。S为已找到从v出发的最短路径的终点的集合,它的初始状态为空集。那么,从v出发到图上其余各顶点(终点)vi可能达到的最短路径长度的初值为:
D[i] = arcs[Locate Vex(G,v)[i] vi属于V
2.选择vj,使得
D[j] = Min{D[i] | i属于V-S}
vj就是当前求得的一条从v出发的最短路径的终点。令
S = S∪{j}
3.修改从v出发到集合V-S 上任一顶点vk可达的最短路径长度。如果
D[j] + arcs[j][k] < D[k]
则修改D[k]为
D[k] = D[j] + arcs[j][k]
4.重复(2)(3)共n-1次,由此求得从v到图上其余各顶点的最短路径是依路径长度递增的序列。
给定带权有向图G和源点v.求从v到G中其余各顶点的最短路径。
Java实现代码:
package com.sjjg.example;
public class Dijkstra {
//给出极大值,表示顶点之间不可达
private static int max = Integer.MAX_VALUE;
//存储最短路径长度的数组
private static int dist[] = new int[6];
//存储当前顶点的前驱顶点
private static int prve[] = new int[6];
//给定测试的邻接矩阵表
private static int a[][]={
{0,max,10,max,30,100},
{max,0,5,max,max,max},
{max,max,0,50,max,max},
{max,max,max,20,max,10},
{max,max,max,max,0,60},
{max,max,max,max,max,0}
};
// D.dijkstra(0, a, dist, prve);
public void dijkstra(int v,int [][]a,int dist[],int prve[]){
int n = dist.length - 1;
//s[]:存储已经找到最短路径的顶点,false为未求得
boolean[] s = new boolean[n+1];
//将第一行的距离值初始化放入dist数组中
for(int i=1;i<=n;i++)
{
//初始化dist数组
dist[i] = a[v][i];
s[i] = false;
/*
* prve[]数组存储源点到顶点vi之间的最短路径上该顶点的前驱顶点,
* 若从源点到顶点vi之间无法到达,则前驱顶点为-1
*/
if(dist[i] < Integer.MAX_VALUE)
{
prve[i] = v;
}
else
{
prve[i] = -1;
}
}
dist[v] = 0;//初始化v0源点属于s集
s[v] = true;//表示v0源点已经找到最短路径
for(int i=1;i<=n;i++)
{
int temp = Integer.MAX_VALUE;//temp暂存v0源点到vi顶点的最短路径
int u = v;
for(int j=1;j<=n;j++)
{
//顶点vi不属于s集当前顶点不属于s集(未求得最短路径)并且距离v0更近
if(!s[j] && dist[j]<temp)
{
//更新当前源点,当前vi作为下一个路径的源点
u = j;
//更新当前最短路径
temp = dist[j];
}
}
s[u]=true; //顶点vi进s集,离顶点v0最近的v加入S集
for(int j=0;j<=n;j++)
{
//当前顶点不属于s集(未求得最短路径)并且当前顶点有前驱顶点
if(!s[j] && a[u][j] < Integer.MAX_VALUE)
{
//累加更新最短路径
int newdist = dist[u] + a[u][j];
if(newdist < dist[j])
{
//更新后的最短路径
dist[j] = newdist;
//当前顶点加入前驱顶点集
prve[j] = u;
}
}
}
}
}
/*
* m:源点
* []p:更新结果后的前驱顶点集
* []d:更新结果后的最短路径集
*/
public void outPath(int m,int []p,int []d){
for(int i=0;i<dist.length;i++)
{
//当前顶点已求得最短路径并且当前顶点不等于源点
if(d[i] < Integer.MAX_VALUE && i!=m)
{
System.out.print("v"+i+"<--");
int next=p[i]; //设置当前顶点的前驱顶点
while(next!=m){ //若前驱顶点不为一个,循环求得剩余前驱顶点
System.out.print("v"+next+"<--");
next=p[next];
}
System.out.println("v"+m+":"+d[i]);
}
//当前顶点未求得最短路径的处理方法
else
{
if(i!=m)
{
System.out.println("v"+i+"<--"+"v"+m+":no path");
}
}
}
}
public static void main(String[] args) {
Dijkstra D=new Dijkstra();
D.dijkstra(0, a, dist, prve);
D.outPath(0, prve, dist);
}
}