《啊哈算法》的Java实现 |第八章:更多精彩算法

11 篇文章 3 订阅
4 篇文章 0 订阅

啊哈算法ch8

《啊哈算法》的Java现实 | 第一章:排序.
《啊哈算法》的Java现实 | 第二章:栈、队列、链表.
《啊哈算法》的Java现实 | 第三章:枚举!很暴力.
《啊哈算法》的java实现 | 第四章:万能的搜索.
《啊哈算法》的Java实现| 第五章:图.
《啊哈算法》的Java实现 | 第六章 :最短路径及最短路径算法的对比分析.
《啊哈算法》的Java实现 | 第七章:神奇的树.
《啊哈算法》的Java实现 |第八章:更多精彩算法.

图的最小生成树

package ch8;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/*
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
19

Process finished with exit code 0
*
 */
public class TUTest01 {
    static Scanner scanner = new Scanner(System.in);
    static int n = scanner.nextInt();
    static int m = scanner.nextInt();
    static int sum = 0;
    static int count =0;
    static int[] f = new int[n+1];
    static edge[] edges = new edge[m+1];
    public static void main(String[] args) {
      for (int i =1;i<=m;i++){
          int u = scanner.nextInt();
          int v = scanner.nextInt();
          int w = scanner.nextInt();
          edges[i] = new edge(u,v,w);
      }
      scanner.close();
      quicksort(1,m);
      for (int i =1;i<=n;i++){
          f[i]  =i;//初始化并查集
      }
      for (int i =1;i<=m;i++){
          if (merge(edges[i].u,edges[i].v)){//如果没有连通则选择这条边
              count++;
              sum = sum + edges[i].w;
          }
          if (count==n-1){
              break;
          }
      }
        System.out.println(sum);

    }
    public static void quicksort(int left,int right){
        if (left>right)return;
        int i = left;
        int j = right;
        while(i!=j){
            while(edges[j].w >= edges[left].w && i<j){
                j--;//从右边开始找
            }
            while (edges[i].w <= edges[right].w && i<j){
                i++;//从左边开始找
            }
            if (i<j){
                edge t = edges[i];
                edges[i] = edges[j];
                edges[j] = t;
            }
        }
        edge t = edges[left];
        edges[left] = edges[i];
        edges[i] = t;

        quicksort(left,i-1);
        quicksort(i+1,right);

    }
    //压缩路径,寻找是否在同一个集合中
    public static int getf(int v){
        if (f[v] == v) return v;
        else {
            f[v] = getf(f[v]);
            return f[v];
        }
    }
    //判断这两个点是否在同一个集合中
    public static boolean merge(int v,int u){
        int t1 = getf(v);
        int t2 = getf(u);
        if(t1!=t2){
            f[t2] = t1;
            return true;//如果不在同一个集合中就连接这两个点
        }
        return false;//如果在同一个集合中,就证明这两个点已经被连接在一起了
    }

}
//用来存储边的关系
class edge{
    int u;
    int v;
    int w;

    public edge(int u, int v, int w) {
        this.u = u;
        this.v = v;
        this.w = w;
    }

    public edge() {
    }
}

时间复杂度为O(MlogM)

再谈最小生成树

package ch8;

import java.util.Scanner;

public class TUTest02 {
    static Scanner scanner = new Scanner(System.in);
    static int inf = 99999;
    static int count = 0, sum = 0;
    static int n = scanner.nextInt();
    static int m = scanner.nextInt();
    static int[][] e = new int[n+1][n+1];
    static int[] dis = new int[n+1];
    static int[] book = new int[n+1];

    public static void main(String[] args) {
        for (int i =1;i<=n;i++){
            for (int j =1;j<=n;j++){
                if (i==j) e[i][j] = 0;
                else e[i][j] = inf;
            }
        }
        for (int i = 1; i<=m; i++){
            int t1 = scanner.nextInt();
            int t2 = scanner.nextInt();
            int t3 = scanner.nextInt();
            e[t1][t2] = t3;
            e[t2][t1] = t3; //这里是无向图,两个方向的都要写
        }
        for (int i =1;i<=n;i++){
            dis[i] = e[1][i];//初始化dis数组,这里的生成树目前只有一号,所有要初始化一号顶点到各个顶点的距离
        }
        //初始化book,这里的book用来记录哪些点加入了生成树
        for (int i =1;i<=n;i++){
            book[i] = 0;
        }
        book[1] = 1;
        int j = 0;
        count ++;//用来记录生成树中的定点数,现在+1为1个
        while(count<n){
            int min = inf;
            for (int i = 1;i<=n;i++){
                if (book[i] == 0 && dis[i] < min){//判断没有加入生成树,并且距离小于无穷远
                    min = dis[i];
                    j = i;
                }

            }
            //判断完一个点之后标记
            book[j] = 1;
            count ++;
            sum = sum + dis[j];
            //扫描当前j所有的边,再以j为中间点更新生成树到每一个非树顶点的距离
            for (int k = 1;k<=n;k++){
                if (book[k] == 0 && dis[k] > e[j][k])
                    dis[k] = e[j][k];
            }
        }

        System.out.println(sum);
    }
}

时间复杂度为O(N2

改进算法

package ch8;

import java.util.Scanner;

public class TUTest03  {
    static Scanner scanner = new Scanner(System.in);
    static int n = scanner.nextInt();
    static int m = scanner.nextInt();
    static int[] dis = new int[n+1];
    static int[] book = new int[n+1];
    static int[] h = new int[n+1];//用来保存堆
    static int[] pos  = new int[n+1];//用来保存每个顶点在堆中位置
    static int size;

    /**
     * 交换x,y位置的堆中的元素 并更新x,y在pos中的位置
     * @param x
     * @param y
     */
    public static void swap(int x,int y){
        int t = h[x];
        h[x] = h[y];
        h[y] = t;

        //同步更新pos
        t = pos[h[x]];
        pos[h[x]] = pos[h[y]];
        pos[h[y]] = t;
    }

    /**
     * 向下调整,小的在上面
     * @param i
     */
    public static void siftdown(int i){//传入下一个需要向下调整的结点
        int t,flag = 0;//flage用来标记是否需要继续向下调整
        while(i*2<=size && flag == 0){//首先i下面还需要有子结点,并且需要被调整
            if (dis[h[i]] > dis[h[i*2]]) t = i*2;
            else t = i;
            //右边的子结点
            if (i*2+1 <= size){
                if (dis[h[t]]>dis[h[i*2+1]]) t= i*2+1;
            }
            //如果发现最小结点编号不是自己的,说明子结点中有比父节点更小的
            if (t!=i){
                swap(t,i);//交换堆t和i位置的元素
                i = t;//更新i为刚与它交换子结点的的编号,继续执行
            }else flag = 1;
        }

    }

    /**
     * 向上调整
     * @param i
     */
    public static void siftup(int i){
        int flag = 0;
        if (i == 1)return;//如果已经是堆的顶点就不需要调整了
        while(i!=1 && flag == 0){
            //判断是否比父节点小
            if (dis[h[i]] < dis[h[i/2]]) swap(i,i/2);//交换位置
            else flag=1;//表示以及不需要进行调整了
            i= i/2;//更新i的表换为父结点,便于下一次的调整
        }
    }

    /**
     * 从堆顶取出元素
     * @return
     */
    public static int pop(){
        int t;
        t = h[1];//用一个临时变量来记录堆顶
        h[1] = h[size];//将堆最后一个值复制到堆顶
        pos[h[1]] = 1;
        size --;
        siftdown(1);//每次都把最后一个点赋值到顶点,然后向下调整,并且整个堆的大小-1,剔除掉顶部元素
        return t;

    }

    public static void main(String[] args) {
        int[] u = new int[2*m+1];
        int[] v = new int[2*m+1];
        int[] w = new int[2*m+1];
        int[] first = new int[n+1];
        int[] next = new int[2*m+1];
        int inf = 999999;
        int count = 0;
        int sum = 0;

        //输入边
        for (int i =1; i<=m;i++){
            u[i] = scanner.nextInt();
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
        }
        scanner.close();
        //因为是无向图,所以需要将所有的边在反向存储一次
        for (int i =m+1; i<=m*2;i++){
            u[i] = v[i-m];
            v[i] = u[i-m];
            w[i] = w[i-m];
        }
        //开始使用邻接表存储边,初始化邻接表
        for (int i =1;i<=n;i++){
            first[i] = -1;//保存顶点为u[i]的第一条边 next保存编号为i的边的下一条边的编号
        }
        //存入值
        for (int i =1; i<= 2*m;i++){
            next[i] = first[u[i]];
            first[u[i]] = i;
        }
        for (int i =1;i<=n;i++){
            book[i] = 0;
        }

        //将1号顶点加入生成树
        book[1] = 1;
        count ++;
        //初始化dis数组
        dis[1] = 0;
        for (int i =2; i<=n;i++){
            dis[i] = inf;
        }
        int k = first[1];
        //初始化dis数组,更新为生成树中只有顶点1时,生成树到各个顶点的距离
        while(k!=-1){
            dis[v[k]] = w[k];
            k = next[k];
        }
        //初始化堆
        size = n;
        for (int i =1;i<=n;i++){
            h[i] = i;
            pos[i] = i;
        }
        for (int i = size/2; i>=1;i--)
            siftdown(i);//调整好整个数
        pop();//弹出栈顶元素,这个元素就是距离生成树最近的元素,这个栈顶元素为1号元素

        while(count < n){
            int j = pop();//弹出一个栈顶元素
            book[j] = 1;//标记 这个元素进入了生成树
            count ++;
            sum  =sum + dis[j];
            //扫描当前顶点j的所有边,进行松弛
            k = first[j];
            while(k != -1){
                if (book[v[k]] == 0&& dis[v[k]] > w[k]){
                    dis[v[k]] = w[k];//更新距离
                    siftup(pos[v[k]]);//进行向上调整
                    //pos[v[k]]存储的是顶点v[k]在堆中的位置
                }
                k = next[k];
            }
        }
        System.out.println(sum);
    }

}

时间复杂度为O(MlogN)

重要城市-图的割点

package ch8;

import java.util.Scanner;
/*
6 7
1 4
1 3
4 2
3 2
2 5
2 6
5 6
 */
public class TUTest04 {
    static Scanner scanner = new Scanner(System.in);
    static int n = scanner.nextInt();
    static int m = scanner.nextInt();
    static int[][] e= new int[n+1][n+1];
    static int root;
    static int[] num = new int[n+1];
    static int[] low = new int[n+1];
    static int[] flag = new int[n+1];
    static int index;//index 用来进行时间戳的递增

    public static int min(int a, int b){
        return a< b ? a :b;
    }
    public static void dfs(int cur,int father){//当前顶点编号和父顶点编号
        int childnum = 0;//用来记录当前cur结点的子节点数
        index ++;
        num[cur] = index;//当前顶点的时间戳
        low[cur] = index;//当前结点能够访问到最早顶点的时间戳,刚开始就是自己
        for(int i =1; i<=n;i++){//枚举与当前顶点cur相连的顶点i
            if (e[cur][i] == 1){
                if (num[i] == 0) {//如果顶点i的时间戳为0,说明顶点还没有被访问过
                    childnum ++;//为子结点
                    dfs(i,cur);//继续往下深度优先遍历
                    //更新当前顶点cur最早能够访问到的顶点的时间戳
                    low[cur] = min(low[cur],low[i]);
                    //如果当前顶点不是根节点并且满足low[i]>=num[cur],则当前顶点为割点
                    if (cur != root && low[i] >= num[cur]){
                        flag[cur] = 1;
                    }
                    //如果当前顶点时根节点,在生成书中根节点必须有两个儿子,那么这个根结点才是割点
                    if (cur == root && childnum == 2){
                        flag[cur] =1;
                    }

                }else if(i != father){
                    //否则如果顶点i被访问过,并且这个顶点不是当前顶点的father,说明此时的i为cur的祖先。因此
                    //需要更新当前结点cur能访问到的最早顶点的时间戳
                    low[cur] = min(low[cur],num[i]);
                }
            }
        }
    }

    public static void main(String[] args) {
        for (int i =1; i<=n;i++){
            for (int j=1; j<= n ;j++){
                e[i][j] = 0;
            }
        }
        for (int i =1; i<=m ;i++){
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            e[x][y] = 1;
            e[y][x] = 1;
        }
        scanner.close();
        root =1;
        dfs(1,root);//从一号顶点进行dfs遍历

        for (int i =1;i<=n;i++){
            if (flag[i] == 1){
                System.out.println(i);
            }
        }
    }
}

时间复杂度为O(N2

关键道路-用Tarjan算法求图的割边

package ch8;

import java.util.Scanner;
/*
6 6
1 4
1 3
4 2
3 2
2 5
5 6
5 - 6
2 - 5

Process finished with exit code 0

 */
public class TUTest05 {
    static Scanner scanner = new Scanner(System.in);
    static int n = scanner.nextInt();
    static int m = scanner.nextInt();
    static int[][] e= new int[n+1][n+1];
    static int root;
    static int[] num = new int[n+1];
    static int[] low = new int[n+1];
    static int[] flag = new int[n+1];
    static int index;//index 用来进行时间戳的递增

    public static int min(int a, int b){
        return a< b ? a :b;
    }
    public static void dfs(int cur,int father){//当前顶点编号和父顶点编号

        index ++;
        num[cur] = index;//当前顶点的时间戳
        low[cur] = index;//当前结点能够访问到最早顶点的时间戳,刚开始就是自己
        for(int i =1; i<=n;i++){//枚举与当前顶点cur相连的顶点i
            if (e[cur][i] == 1){
                if (num[i] == 0) {//如果顶点i的时间戳为0,说明顶点还没有被访问过
                    dfs(i,cur);//继续往下深度优先遍历
                    //更新当前顶点cur最早能够访问到的顶点的时间戳
                    low[cur] = min(low[cur],low[i]);
                    //如果当前顶点不是根节点并且满足low[i]>=num[cur],则当前顶点为割边
                    if (low[i] > num[cur]){
                        System.out.println(cur + " - " + i);
                    }
                }else if(i != father){
                    //否则如果顶点i被访问过,并且这个顶点不是当前顶点的father,说明此时的i为cur的祖先。因此
                    //需要更新当前结点cur能访问到的最早顶点的时间戳
                    low[cur] = min(low[cur],num[i]);
                }
            }
        }
    }

    public static void main(String[] args) {
        for (int i =1; i<=n;i++){
            for (int j=1; j<= n ;j++){
                e[i][j] = 0;
            }
        }
        for (int i =1; i<=m ;i++){
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            e[x][y] = 1;
            e[y][x] = 1;
        }
        scanner.close();
        root =1;
        dfs(1,root);//从一号顶点进行dfs遍历

        for (int i =1;i<=n;i++){
            if (flag[i] == 1){
                System.out.println(i);
            }
        }
    }
}

我要做月老-二分图最大匹配

package ch8;

import java.util.Scanner;
/*
3 5
1 1
1 2
2 2
2 3
3 1
 */
public class TUTest06 {
    static Scanner scanner = new Scanner(System.in);
    static int n = scanner.nextInt();
    static int m = scanner.nextInt();
    static int[][] e = new int[n+1][n+1];
    static int[] book = new int[n+1];
    static int[] match = new int[n+1];
    public static boolean dfs(int u){
        for (int i =1;i<=n;i++){
            if (book[i] == 0 && e[u][i] == 1) {//u i 可以链接在一起
                book[i] = 1;
                if (match[i] == 0 || dfs(match[i])) { //i还没有配对或者找到了新的配对
                    //更新配对表
                    match[i] = u;
                    return true;//配对成功
                }
            }
        }
        return false;//全部遍历完都没有配对成功返回false
    }

    public static void main(String[] args) {
        int sum = 0;
        for (int i =1;i<=m;i++){
            int t1 = scanner.nextInt();
            int t2 = scanner.nextInt();
            e[t1][t2] = 1;
        }
        scanner.close();
        for (int i =1;i<=n;i++){
            for (int j =1; j<=n;j++){
                book[j] = 0;//清空上次搜索的标记
            }
            if (dfs(i)) sum++;//找到一条增广路,配对数+1
        }
        System.out.println(sum);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小七rrrrr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值