赫夫曼(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);
}
}