本质特征(区分dp、分治)
只选择一个子问题
特点
1.快
2.不能保证获得最优解,可以作为近似解,一般,在特殊情况下可保证最优解。
3.适合于组合优化问题
设计关键
贪心选择策略(分解方案)
程序写法
S = s0,C = c0; // 部分解S,候选集C
while(!complete(s)){
x = select(c); // 贪心选择主体
if(flexible(x)){ // 可行函数,判断贪心选择结果是否满足约束条件
S = S ∪ {x} // 解扩展
}
C = C - {x}; //C中参数重新计算以及候选集调整
}
例子
1、Dijkstra算法——求单源最短路径(有向无负权图)
注意:实现时dist[] 的初始值若源点和i之间无边,则一定要为无穷大(例如XX.MAX_VALUE等),若为0,则后面会更新不了,导致结果出错。
实现一:邻接矩阵实现
public class Greedy {
float[][] arr; // 表示边的权,若没有连接的边则用最大值表示
boolean[] s; // 表示已找到最短路径的点集合
int[] pre; // 最短路径中某个点对应的前驱节点
float[] dist; // 从源到所有其他顶点的最短路径
int m; // 图中边的条数
int n; // 图中的点的个数
int v; // 源
public static void main(String[] args){
new Greedy().run();
}
public void run(){
Scanner scanner = new Scanner(System.in);
v = 0;
n = scanner.nextInt();
m = scanner.nextInt();
arr = new float[n][n];
s = new boolean[n];
pre = new int[n];
dist = new float[n];
for(int i = 0;i < n;i++){
for(int j = 0;j < n;j++){
arr[i][j] = Float.MAX_VALUE;
}
}
for(int i = 0;i < m;i++){
int a = scanner.nextInt()-1;
int b = scanner.nextInt()-1;
float c = scanner.nextFloat();
arr[a][b] = c;
// arr[b][a] = c; 若为无向图则加上
}
dijkstra();
System.out.println(Arrays.toString(dist));
}
public void dijkstra(){
// 初始化
for(int i = 0;i < n;i++){
dist[i] = arr[v][i];
if(dist[i] == Float.MAX_VALUE){
pre[i] = -1;
}else {
pre[i] = v;
}
}
dist[v] = 0;
s[v] = true;
for(int i = 0;i < n;i++){
float min = Float.MAX_VALUE;
int u = v;
// 找出未访问过的最短路径的点
for(int j = 0;j < n;j++){
if(!s[j] && dist[j] < min){
min = dist[j];
u = j;
}
}
// 加入到已找到最短路径的点集合S中
s[u] = true;
// 若存在以j为中间节点,到源点v的最短距离变小的未找到最短距离的点,则更新距离
for(int j = 0;j < n;j++){
if(!s[j] && arr[u][j] < Float.MAX_VALUE){
float newDis = dist[u] + arr[u][j];
if(newDis < dist[j]){
dist[j] = newDis;
pre[j] = u;
}
}
}
}
}
}
实现二:邻接矩阵,优先队列实现(不知道这个方法有没有错就是了...)
public class Greedy2 {
public class Edge{
int end;
float cost;
public Edge(int end,float cost){
this.end = end;
this.cost = cost;
}
}
public class Node implements Comparable<Node>{
int index;
float dis;
public Node(int index,float dis){
this.index = index;
this.dis = dis;
}
@Override
public int compareTo(Node o) {
if(this.dis < o.dis){
return -1;
}
if(this.dis == o.dis){
return 0;
}
return 1;
}
}
PriorityQueue<Node> queue;
LinkedList<Edge>[] list; // 表示边的权,若没有连接的边则用最大值表示
boolean[] s; // 表示已找到最短路径的点集合
int[] pre; // 最短路径中某个点对应的前驱节点
float[] dist; // 从源到所有其他顶点的最短路径
int m; // 图中边的条数
int n; // 图中的点的个数
int v; // 源
public static void main(String[] args){
new Greedy2().run();
}
public void run(){
Scanner scanner = new Scanner(System.in);
v = 0;
n = scanner.nextInt();
m = scanner.nextInt();
queue = new PriorityQueue<>();
s = new boolean[n];
pre = new int[n];
dist = new float[n];
list = new LinkedList[m];
for(int i = 0;i < n;i++){
list[i] = new LinkedList<>();
dist[i] = Float.MAX_VALUE;
pre[i] = -1;
}
for(int i = 0;i < m;i++){
int a = scanner.nextInt()-1;
int b = scanner.nextInt()-1;
float c = scanner.nextFloat();
list[a].add(new Edge(b,c));
// list[b].add(new Edge(a,c)); 若为无向图则加上
}
queue.add(new Node(v,0));
dijkstra();
System.out.println(Arrays.toString(dist));
}
public void dijkstra(){
// 初始化
LinkedList curList = list[v];
for(int i = 0;i < curList.size();i++){
Edge temp = (Edge) curList.get(i);
dist[temp.end] = temp.cost;
pre[temp.end] = v;
}
dist[v] = 0;
s[v] = true;
while (!queue.isEmpty()){
// 选取最小值
Node curNode = queue.poll();
s[curNode.index] = true;
int u = curNode.index;
// 更新
LinkedList<Edge> uList = list[u];
for(int i = 0;i < uList.size();i++){
Edge curEdge = uList.get(i);
float newDis = dist[u] + curEdge.cost;
if(!s[curEdge.end] && newDis <= dist[curEdge.end]){
dist[curEdge.end] = newDis;
pre[curEdge.end] = u;
queue.add(new Node(curEdge.end,newDis));
}
}
}
}
}
2、最小生成树
描述:求无向连通带权图的耗费最小的生成树(生成树中总权值最小)
1.Kruskal算法(最短边策略)
/**
* Kruskal算法+并查集
*
*/
public class MinTree {
/**
* i的父亲father[i]
*/
int[] father;
/**
* 寻找x的父亲
*/
public int find(int x){
if(father[x] == x){
return x;
}
return father[x] = find(father[x]);
}
/**
* 将x和y合并在一个集合
* @param x
* @param y
*/
public void union(int x,int y){
int fatherX = find(x);
int fatherY = find(y);
if(fatherX > fatherY){
father[fatherX] = fatherY;
}else{
father[fatherY] = fatherX;
}
}
public class Edge implements Comparable<Edge>{
int start;
int end;
int cost;
public Edge(int start,int end,int cost){
this.start = start;
this.end = end;
this.cost = cost;
}
@Override
public int compareTo(Edge o) {
return this.cost - o.cost;
}
}
int n;
ArrayList<Edge> list;
/**
* 存储结果的集合
*/
ArrayList<Edge> res;
public static void main(String[] args){
new MinTree().run();
}
public void run(){
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
int m = scanner.nextInt();
list = new ArrayList<>();
father = new int[n];
res = new ArrayList<>(n);
for(int i = 0;i < n;i++){
father[i] = i;
}
for(int i = 0;i < m;i++){
int a = scanner.nextInt()-1;
int b = scanner.nextInt()-1;
int c = scanner.nextInt();
Edge edge = new Edge(a,b,c);
list.add(edge);
}
list.sort(new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
if(o1.cost == o2.cost){
return o1.start - o2.start;
}
return o1.cost - o2.cost;
}
});
kruskal();
for(int i = 0;i < res.size();i++){
System.out.println(res.get(i).start + " " + res.get(i).end + " ");
}
}
/**
* Kruskal主体
*/
public void kruskal(){
for(int i = 0;i < list.size();i++){
Edge curEdge = list.get(i);
// 如果这个节点已经在一个集合,就不能再连
if(find(curEdge.start) == find(curEdge.end)){
continue;
}
// 把该边加入选择的边集
res.add(curEdge);
union(curEdge.start,curEdge.end);
}
}
}
2、Prim算法
public class MinTree2 {
int[][] graph;
int[] lowcost;
int[] pre;
StringBuilder res;
int n;
public static void main(String[] args){
new MinTree2().run();
}
public void run(){
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
int m = scanner.nextInt();
graph = new int[n][n];
lowcost = new int[n];
pre = new int[n];
res = new StringBuilder();
for(int i = 0;i < n;i++){
for(int j = 0;j < n;j++){
graph[i][j] = Integer.MAX_VALUE;
}
}
for(int i = 0;i < m;i++){
int a = scanner.nextInt()-1;
int b = scanner.nextInt()-1;
int c = scanner.nextInt();
graph[a][b] = c;
}
prim();
System.out.println(res.toString());
}
public void prim(){
// 初始化
for(int i = 1;i < n;i++){
lowcost[i] = graph[0][i];
pre[i] = 0;
}
lowcost[0] = 0;
for(int i = 1;i < n;i++){
int minP = 0;
int minCost = Integer.MAX_VALUE;
// 找出距离已找出最短边的点集最小的点
for(int j = 1;j < n;j++){
if(lowcost[j] != 0 && lowcost[j] < minCost){
minCost = lowcost[j];
minP = j;
}
}
if(minP == 0){
return;
}
// 加入已访问的集合
lowcost[minP] = 0;
res.append(minCost + "\n");
// 更新未找出最小边点的集合
for(int j = 1;j < n;j++){
if(lowcost[j] != 0 && lowcost[j] > graph[minP][j]){
lowcost[j] = graph[minP][j];
pre[j] = minP;
}
}
}
}
}
贪心算法是一种解决问题的策略,它通过每次选择局部最优解来逼近全局最优解。在本文中,我们探讨了贪心算法的本质特征,如与DP和分治的区别,以及它的快速但不保证最优解的特点。内容涵盖了贪心算法的设计关键,以及如何在程序中实现。具体实例包括Dijkstra算法用于求解有向无负权图的单源最短路径,以及两种最小生成树的构造方法:Kruskal和Prim算法。
375

被折叠的 条评论
为什么被折叠?



