算法实现之贪心法2

哈夫曼编码

原理

1.统计输入文本中各个字符的频率。

2.根据字符频率构建哈夫曼树:创建一个优先队列(最小堆),每个节点都包含字符的频率,将每个字符视为一个独立的树节点,将它们插入到优先队列中,然后从优先队列中取出频率最小的两个节点(两棵树),将它们合并成一棵新树,新树的根节点频率为两个子树的频率之和,将新树插入回优先队列,重复上述步骤,直到队列中只剩下一个节点。

3.根据哈夫曼树生成编码:从根节点开始遍历哈夫曼树,路径中的左子树标记为0,右子树标记为1,当到达叶子节点时,记录路径上的0和1,即为该叶子节点对应的字符的哈夫曼编码。

4.使用生成的编码将原始数据进行编码压缩:将输入文本中的每个字符替换为其对应的哈夫曼编码。

Java实现:

import java.util.*;

class HuffmanNode implements Comparable<HuffmanNode> 
{
    int frequency;
    char data;
    HuffmanNode left, right;

    public HuffmanNode(int frequency, char data) 
    {
        this.frequency = frequency;
        this.data = data;
        left = right = null;
    }

    public int compareTo(HuffmanNode node) 
    {
        return frequency - node.frequency;
    }
}

class Huffman 
{
    public static void printCodes(HuffmanNode root, String code) 
    {
        if (root == null)
            return;

        if (root.data != '\0')
        {
            System.out.println(root.data + ": " + code);
        }

        printCodes(root.left, code + "0");
        printCodes(root.right, code + "1");
    }

    public static void buildHuffmanTree(String text) 
    {
        HashMap<Character, Integer> frequencyMap = new HashMap<>();

        for (int i = 0; i < text.length(); i++) 
        {
            char c = text.charAt(i);
            frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1);
        }

        PriorityQueue<HuffmanNode> priorityQueue = new PriorityQueue<>();
        for (char key : frequencyMap.keySet()) 
        {
            priorityQueue.offer(new HuffmanNode(frequencyMap.get(key), key));
        }

        while (priorityQueue.size() > 1) 
        {
            HuffmanNode leftNode = priorityQueue.poll();
            HuffmanNode rightNode = priorityQueue.poll();

            HuffmanNode newNode = new HuffmanNode(leftNode.frequency + rightNode.frequency, '\0');
            newNode.left = leftNode;
            newNode.right = rightNode;
            priorityQueue.offer(newNode);
        }

        HuffmanNode root = priorityQueue.poll();
        printCodes(root, "");
    }

   
}

最小生成树——Prim算法

原理:

1.将连通网中的所有顶点分为两类(假设为 A 类和 B 类)。 初始状态下,所有顶点位于 B 类;

2.选择任意一个顶点,将其从 B 类移动到 A 类;

3.从 B 类的所有顶点出发,找出一条连接着 A 类中的某个顶点且权值最小的边,将此边连接着的 A 类中的顶点移动到 B 类;

重复执行第 3 步,直至 B 类中的所有顶点全部移动到 A 类,恰好可以找到 N-1 条边,这些边就构成了图的最小支撑树。

Java实现:

import java.util.*;


class Edge implements Comparable<Edge>
{
    int source;
    int destination;
    int weight;

    public Edge(int source, int destination, int weight)
    {
        this.source = source;
        this.destination = destination;
        this.weight = weight;
    }


    public int compareTo(Edge edge)
    {
        return this.weight - edge.weight;
    }
}

class Graph
{
    private final int vertices;
    private final List<List<Edge>> adjacencyList;

    public Graph(int vertices)
    {
        this.vertices = vertices;
        adjacencyList = new ArrayList<>(vertices);
        for (int i = 0; i < vertices; i++) {
            adjacencyList.add(new ArrayList<>());
        }
    }

    public void addEdge(int source, int destination, int weight)
    {
        Edge edge = new Edge(source, destination, weight);
        adjacencyList.get(source).add(edge);
        Edge reverseEdge = new Edge(destination, source, weight);
        adjacencyList.get(destination).add(reverseEdge);
    }

    public void primMST()
    {
        boolean[] visited = new boolean[vertices];
        int[] parent = new int[vertices];
        int[] key = new int[vertices];

        for (int i = 0; i < vertices; i++)
        {
            key[i] = Integer.MAX_VALUE;
            visited[i] = false;
        }

        
        key[0] = 0;
        parent[0] = -1;

        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>();
        priorityQueue.add(new Edge(0, 0, 0));

        while (!priorityQueue.isEmpty())
        {
            int u = priorityQueue.poll().destination;
            visited[u] = true;

            for (Edge edge : adjacencyList.get(u))
            {
                int v = edge.destination;
                int weight = edge.weight;

                if (!visited[v] && weight < key[v])
                {
                    priorityQueue.remove(new Edge(v, v, key[v]));
                    key[v] = weight;
                    priorityQueue.add(new Edge(v, v, key[v]));
                    parent[v] = u;
                }
            }
        }

        
        System.out.println("Edge \tWeight");
        for (int i = 1; i < vertices; i++)
        {
            System.out.println(parent[i] + " - " + i + "\t" + key[i]);
        }
    }
}

 class Main
 {
    public static void main(String[] args)
    {
        int vertices = 5;
        Graph graph = new Graph(vertices);

        graph.addEdge(0, 1, 5);
        graph.addEdge(0, 3, 2);
        graph.addEdge(1, 2, 5);
        graph.addEdge(1, 3, 8);
        graph.addEdge(1, 4, 5);
        graph.addEdge(2, 4, 7);
        graph.addEdge(3, 4, 9);

        graph.primMST();
    }
}

最小生成树——Kruskal算法

  1. 将每个顶点都看作一个独立的树,即共有 V 个树,其中 V 是图中顶点的数量。
  2. 将图中的所有边按照权值从小到大进行排序。
  3. 按照排序后的顺序依次遍历边。
  4. 对于当前遍历到的边,检查它所连接的两个顶点是否在同一个树中。如果不在同一个树中,说明加入这条边不会形成环路。
  5. 如果边的两个顶点不在同一个树中,将这条边加入最小生成树,同时合并这两个顶点所在的树。
  6. 重复步骤3和步骤4,直到最小生成树中的边数达到 V-1,其中 V 是图中顶点的数量。

Java实现:

import java.util.*;

 class KruskalMST
 {
    static class Edge implements Comparable<Edge>
    {
        int src, dest, weight;
        public int compareTo(Edge compareEdge) 
        {
            return this.weight - compareEdge.weight;
        }
    }

    static class Subset 
    {
        int parent, rank;
    }

    int V, E;
    Edge[] edge;

    KruskalMST(int v, int e)
    {
        V = v;
        E = e;
        edge = new Edge[E];
        for (int i = 0; i < e; ++i)
            edge[i] = new Edge();
    }

    int find(Subset[] subsets, int i) 
    {
        if (subsets[i].parent != i)
            subsets[i].parent = find(subsets, subsets[i].parent);
        return subsets[i].parent;
    }

    void Union(Subset[] subsets, int x, int y)
    {
        int xroot = find(subsets, x);
        int yroot = find(subsets, y);

        if (subsets[xroot].rank < subsets[yroot].rank)
            subsets[xroot].parent = yroot;
        else if (subsets[xroot].rank > subsets[yroot].rank)
            subsets[yroot].parent = xroot;
        else 
        {
            subsets[yroot].parent = xroot;
            subsets[xroot].rank++;
        }
    }

    void KMST()
    {
        Edge[] result = new Edge[V];
        int e = 0;
        int i;
        for (i = 0; i < V; ++i)
            result[i] = new Edge();

        Arrays.sort(edge);

        Subset[] subsets = new Subset[V];
        for (i = 0; i < V; ++i)
            subsets[i] = new Subset();

        for (int v = 0; v < V; ++v) 
        {
            subsets[v].parent = v;
            subsets[v].rank = 0;
        }

        i = 0;

        while (e < V - 1) 
        {
            Edge next_edge ;
            next_edge = edge[i++];

            int x = find(subsets, next_edge.src);
            int y = find(subsets, next_edge.dest);

            if (x != y) 
            {
                result[e++] = next_edge;
                Union(subsets, x, y);
            }
        }

        
        for (i = 0; i < e; ++i)
            System.out.println(result[i].src + " -- " + result[i].dest + " == " + result[i].weight);
    }

    public static void main(String[] args)
    {
        int V = 4; // 顶点个数
        int E = 5; // 边的个数
        KruskalMST graph = new KruskalMST(V, E);

        // 添加边
        graph.edge[0].src = 0;
        graph.edge[0].dest = 1;
        graph.edge[0].weight = 10;

        graph.edge[1].src = 0;
        graph.edge[1].dest = 2;
        graph.edge[1].weight = 6;

        graph.edge[2].src = 0;
        graph.edge[2].dest = 3;
        graph.edge[2].weight = 5;

        graph.edge[3].src = 1;
        graph.edge[3].dest = 3;
        graph.edge[3].weight = 15;

        graph.edge[4].src = 2;
        graph.edge[4].dest = 3;
        graph.edge[4].weight = 4;

        graph.KMST();
    }
}

最短路径——Djakarta算法

原理:

1.初始时令 S={V0},T={其余顶点},T 中顶点对应的距离值。若存在,d(V0,Vi)为弧上的权值;若不存在<V0,Vi>,d(V0,Vi)为 MAXINT;

2.从 T 中选取一个其距离值为最小的顶点 W 且不在 S 中,加入 S;

3.对其余 T 中顶点的距离值进行修改:若加进 W 作中间顶点,从 V0 到 Vi 的距离值缩短,则修改此距离值;

4.重复上述步骤 2、3,直到 S 中包含所有顶点,即 W=Vi 为止

Java实现:

 class Dijkstra
 {
    private static final int MAXSIZE = 11;
    private static final int MAXINT = Integer.MAX_VALUE;

    public static void dijkstra(Graph graph, int v, int v1) 
    {
        int[] dist = new int[MAXSIZE];
        int[] path = new int[MAXSIZE];
        int[] s = new int[MAXSIZE];
        String[] vexsC = {"", "", "", "", "", "", "", "", "", "", ""};

        for (int i = 1; i <= graph.n; i++) 
        {
            s[i] = 0;
            dist[i] = graph.edges[v][i];
            path[i] = v;
        }
        s[v] = 1;
        dist[v] = 0;

        for (int i = 1; i <= graph.n; i++) 
        {
            int k = -1;
            int min = MAXINT;
            for (int j = 1; j <= graph.n; j++) 
            {
                if (s[j] == 0 && dist[j] < min) 
                {
                    min = dist[j];
                    k = j;
                }
            }
            if (k == -1 || min == MAXINT) 
            {
                break;
            }
            s[k] = 1;
            for (int j = 1; j <= graph.n; j++) 
            {
                int newD = dist[k] + graph.edges[k][j];
                if (s[j] == 0 && dist[j] > newD) 
                {
                    dist[j] = newD;
                    path[j] = k;
                }
            }
        }
        for (int i = 1; i <= graph.n; i++) 
        {
            if (v1 == i) 
            {
                System.out.printf("从 %d(%s)到 %d(%s)的短经过路线为:\n", v, vexsC[v - 1], i, vexsC[i - 1]);
                System.out.printf("%d(%s)", i, vexsC[i - 1]);
                int pre = path[i];
                while (pre != v) {
                    System.out.printf("<- %d(%s)", pre, vexsC[pre - 1]);
                    pre = path[pre];
                }
                System.out.printf("<- %d(%s)", v, vexsC[v - 1]);

                System.out.printf("   路线距离:%d\n\n", 100 * dist[i]);
            }
        }
    }
}

附:Graph

 class Graph 
 {
    private static final int MAXSIZE = Integer.MAX_VALUE; 

    private final int[] vexs; 
    private final int[][] edges; 
    private final int n; 
    private final int e; 

    
    public Graph() 
    {
        vexs = new int[MAXSIZE];
        edges = new int[MAXSIZE][MAXSIZE];
        n = 0;
        e = 0;
    }

    
    public int[] getVexs() 
    {
        return vexs;
    }

    public int[][] getEdges() 
    {
        return edges;
    }

    public int getN() 
    {
        return n;
    }

    public int getE() 
    {
        return e;
    }
}

最短路径——Floyd算法

原理:

1.定义n×n的方阵序列D-1, D0 , … Dn-1,

2.初始化: D-1=C D-1 [i] [j]=边<i,j>的长度,表示初始的从i到j的最短路径长度,即它是从i到j的中间不经过其他中间点的最短路径。

3.迭代:设Dk-1已求出,如何得到Dk(0≤k≤n-1)? Dk-1 [i] [j]表示从i到j的中间点不大于k-1的最短路径p:i…j, 考虑将顶点k加入路径p得到顶点序列q:i…k…j, ...

4.因为q的两条子路径i…k和k…j皆是中间点不大于k-1的最短路径,所以从i到j中间点不大于k的最短路径长度为: Dk [i] [j]=min { Dk-1 [i] [j], Dk-1 [i] [k] +Dk-1 [k] [j] }

Java实现:

Graph代码同上。

public static void floyd(Graph graph, int[][] P, int[][] D, int n1, int n2) 
     {
         int v, w, k;
         // 初始化floyd算法矩阵
         for (v = 1; v < graph.getN(); v++) 
         {
             for (w = 1; w < graph.getN(); w++) 
             {
                 D[v][w] = graph.getEdges()[v][w];
                 P[v][w] = w;
             }
         }

         // 设k为中间点
         for (k = 1; k < graph.getN(); k++) 
         {
             // v为起点
             for (v = 1; v < graph.getN(); v++) 
             {
                 // w为终点
                 for (w = 1; w < graph.getN(); w++) 
                 {
                     if (D[v][w] > (D[v][k] + D[k][w])) 
                     {
                         D[v][w] = D[v][k] + D[k][w]; // 更新最小路径
                         P[v][w] = P[v][k]; // 更新最小路径中间顶点
                     }
                 }
             }
         }

         String[] name1 = {"", "", "", "", "", "", "", "", "", "", ""};

         System.out.printf("\n%d (%s) -> %d(%s) 的最小路径为:%d\n", n1, name1[n1], n2, name1[n2], D[n1][n2]*100);
         k = P[n1][n2];
         System.out.printf("路线是: %d ( %s )", n1, name1[n1]); // 打印起点
         while (k != n2) 
         {
             System.out.printf("-> %d ( %s )", k, name1[k]); // 打印中间点
             k = P[k][n2];
         }
         System.out.printf("-> %d( %s )\n", n2, name1[n2]); // 打印终点
     }

本文仅为学习记录,如有错误欢迎指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值