输油管道或水井挖水渠中路径和最短问题

1、问题描述

修建输油管道或者挖水渠,方向都是指向正南正北或者正东正西。然后有多个油井或者水井需要向管道或者水渠修建小分支管道或者水渠,求输油管道或者水渠的位置,使得所有的油管小分支长度总和最小或者所有的水渠小分支长度总和最小。

例题:

某石油公司计划建造一条由东向西的主输油管道。该管道要穿过一个有n口油井的油田。从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连。如果给定n口油井的位置, 即它们的x坐标(东西向)和y坐标(南北向), 应如何确定主管道的最优位置, 即使各油井到主管道之间的输油管道长度总和最小的位置。示意图如下:

这里写图片描述

2、分析

(1)问题本质是求一条直线y = yk,使得所有给定点到此直线的距离和最短。又因为直线y=y0平行于x轴,所以可以不用考虑横坐标,降维化简成一维问题:有n个数值y1,y2,y3.......yn,找一个数值yk,使得n个数值与yk的距离和最小。

(2)图例分析:n取值可以为奇数和偶数,两种情况有些差异,此处分开讨论

n为奇数时:

当yk位于a2处时,距离和D=a3-a1,当yk偏离a2,比如位于a1-a2间时,距离和D=(yk-a1)+(a2-yk)+(a3-yk) =a3-a1+(a2-yk),因为a2-yk >0,所以可知只有直线位于中位数a2位置时,距离和最短,中位数就是我们要找的数值。 

这里写图片描述

n为偶数时:

设有4个值a1、a2、a3、a4,当yk位于a2-a3之间时,距离和D=(a3-a2)+(a4-a1),当yk处于别的数值区间时,

比如在a1-a2时,距离和D=(yk-a1)+(a2-yk)+(a3-yk)+(a4-yk),

其中a3-yk=(a3-a2)+(a2-yk),(a4-yk) = (a4-a2)+(a2-yk),带入上式可得到:

D=(yk-a1)+(a2-yk)+(a3-a2)+(a2-yk)+(a4-a2)+(a2-yk) =(a3-a2)+(a4-a1)+2(a2-yk),其中a2-yk > 0,记2(a2-yk)为d,最终化简为

D=(a3-a2)+(a4-a1)+d,其中d>0。我们会发现只有yk属于[a2,a3]时,距离和才会最小,而且在[a2,a3]任意位置,距离和都是固定的。所以yk可以取(a2,a3)区间的任意整数,也可以取a2或者a3,当取a2或者a3时,也就是取n个数值的中位数,与上述n为奇数时讨论的情况相同,都是找n个数的中位数问题。所以综合上述对于n为奇和偶的讨论,此问题本质是求n个数的中位数。
这里写图片描述

 

3、求中位数算法(分治)

(1)写个random_partition(A,p,r)方法,随机选择一个主元,通过多次比较和交换找到主元在数组A中的最终位置i使得A[p]到A[p+i-1]的元素都小于等于主元,而A[p+i]到A[r]的元素都大于主元,最后把主元交换到位置i,同时返回主元的下标i;

(2)分治算法循环调用random_partition(A,p,r),如果返回值i==中位数的下标位置m,则直接返回;如果i小于m则递归调用右侧部分数组;如果i大于m则递归调用左侧部分数组。

4、代码

import java.util.*;

public class Main {
    public static void main(String[] args) {
        int [] arr = {1,8,2,9,8,0,2,9,1,8,7};
        int len = arr.length;
        if(len == 1){
            System.out.println(arr[0]);
            return;
        }
        if(len % 2 == 0){
            System.out.println(getMiddle(arr,0,len-1,len/2));
        }else{
            System.out.println(getMiddle(arr,0,len-1,(len+1)/2));
        }
    }

    //此问题是找出第k小的数值
    static int getMiddle(int [] arr,int left,int right,int k){
        if(left == right){
            return arr[left];
        }
        int temIndex = random_partition(arr,left,right);
        int goalIndex = temIndex - left + 1;
            if(goalIndex == k){
                return arr[temIndex];
            }else if(goalIndex > k){
                return getMiddle(arr,left,temIndex-1,k);
            }else{
                return getMiddle(arr,temIndex+1,right,k - goalIndex);
            }
    }


    static int random_partition(int [] arr,int left,int right){
        // 随机获取主元,可以是left到right之间的任意一个数,包括头尾
        int randomIndex = getRandom(left,right);
        // 随机获取的主元与最后一个元素交换位置
        if(randomIndex != right){
            swap(arr,randomIndex,right);
        }
        // 接下来通过多次比较和交换,找到主元的位置,并把主元放到最终的位置,同时返回主要最终位置的下标
        int leftIndex = left - 1;
        int mainFactor = arr[right];
        // 下标为right的不参与比较,j上界为right-1
        for(int j = left; j <= right - 1;j++){
            if(arr[j] < mainFactor){
                // leftIndex标定的元素是目前所知的比主元小的最右面的元素
                leftIndex++;
                swap(arr,leftIndex,j);
            }
        }
        // 下面这个交换是把主元放到最终的位置
        swap(arr,leftIndex+1,right);
        return leftIndex+1;
    }

    // 交换数组中下标为left和right的元素
    static void swap(int [] arr,int left,int right){
        int tem = arr[left];
        arr[left] = arr[right];
        arr[right] = tem;
    }
    // 获取min到max之间的随机一个数,包括min和max
    static int getRandom(int min,int max){
        // 返回[0.0,1.0)的double型数值
        return (int)(Math.random()*(max-min)+min);
    }
}

 

参考博客:https://blog.csdn.net/u010111016/article/details/51446497?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBa

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值