【算法设计与分析】贪心法(其一) - - - 会场安排问题

目录

一、问题概述

二、问题分析

 三、例题演示(Java)

四、算法正确性证明

        1.贪心选择性质

        2.最优子结构性质

五、其他版本

C语言版本:

 C++版本:

 Python版本:


一、问题概述

设有n个会议的集合C={1,2,…,n},其中每个会议都要求使用同一个资源(如会议室),而在同一时间内只能有一个会议使用该资源。每个会议i都有要求使用该资源的起始时间bi结束时间ei,且bi < ei 。如果选择了会议i使用会议室,则它在半开区间[bi, ei)内占用该资源。如果[bi, ei)[bj , ej)不相交,则称会议i与会议j是相容的。会场安排问题要求在所给的会议集合中选出最大的相容活动子集,也即尽可能地选择更多的会议来使用资源


二、问题分析

★对该问题分析,我们可以列出三种不同的贪心策略

1.选择最早开始时间且不与已安排会议重叠的会议
2.选择使用时间最短且不与已安排会议重叠的会议
3.选择具有最早结束时间且不与已安排会议重叠的会议

我们可以通过列举一系列会议并将他们按照开始时间、持续时间、结束时间分别排序,并依据排好的顺序依次选择会议,找到可以选择尽可能多的会议的贪心策略。

☆最终,我们确定第三种贪心策略(以结束时间选择)可以选到更多的会议,满足要求。


三、例题演示(Java)

【例】这些会议按结束时间的升序排列如下表所示设有10个会议等待安排,用贪心法找出满足目标要求的会议集合。

等待安排的会议
会议i12345678910
开始时间bi1325356882
结束时间ei45678910111213

确定了贪心策略,这个题目就变得十分简单:

因为这道题目是已经按照结束时间排好序的,我们只需要通过ei <bj(上一个会议i的结束时间小于下一个会议j的开始时间)这个限制条件把符合要求的会议挑选出来就可以了。

以Java为例:

/*======Meeting类======*/
public class Meeting {

	int i;
	int Begin;
	int End;
	public Meeting() {
		super();
	}
	public Meeting(int i, int begin, int end) {
		super();
		this.i = i;
		Begin = begin;
		End = end;
	}
	public int getI() {
		return i;
	}
	public void setI(int i) {
		this.i = i;
	}
	public int getBegin() {
		return Begin;
	}
	public void setBegin(int begin) {
		Begin = begin;
	}
	public int getEnd() {
		return End;
	}
	public void setEnd(int end) {
		End = end;
	}
	@Override
	public String toString() {
		return "Meeting [i=" + i + ", Begin=" + Begin + ", End=" + End + "]\n";
	}
	
}
/*=====Main=====*/
import java.util.LinkedList;
import java.util.Vector;

public class Main {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Meeting meet1 =  new Meeting(1,1,4);
		Meeting meet2 =  new Meeting(2,3,5);
		Meeting meet3 =  new Meeting(3,2,6);
		Meeting meet4 =  new Meeting(4,5,7);
		Meeting meet5 =  new Meeting(5,3,8);
		Meeting meet6 =  new Meeting(6,5,9);
		Meeting meet7 =  new Meeting(7,6,10);
		Meeting meet8 =  new Meeting(8,8,11);
		Meeting meet9 =  new Meeting(9,8,12);
		Meeting meet10 =  new Meeting(10,2,13);
		Vector<Meeting> v = new Vector<Meeting>();
		v.add(meet1);
		v.add(meet2);
		v.add(meet3);
		v.add(meet4);
		v.add(meet5);
		v.add(meet6);
		v.add(meet7);
		v.add(meet8);
		v.add(meet9);
		v.add(meet10);
		System.out.println("==========全部会议如下============================");
		System.out.println(v);
		int e = 0;
		LinkedList<Meeting> list = new LinkedList<Meeting>();
		for(int i=0;i<v.size();i++) {
			if(e<v.get(i).Begin) {
				e=v.get(i).End;
				list.add(v.get(i));
			}
		}
		System.out.println("===========最优会议安排如下:=====================");
		System.out.println(list);

	}

}

 我们将这十组数据储存在一个vector容器中,再遍历一次该容器,将符合条件ei <bj的挑选出来,输出就可以完成了。

输出结果如下:

==========全部会议如下============================
[Meeting [i=1, Begin=1, End=4]
, Meeting [i=2, Begin=3, End=5]
, Meeting [i=3, Begin=2, End=6]
, Meeting [i=4, Begin=5, End=7]
, Meeting [i=5, Begin=3, End=8]
, Meeting [i=6, Begin=5, End=9]
, Meeting [i=7, Begin=6, End=10]
, Meeting [i=8, Begin=8, End=11]
, Meeting [i=9, Begin=8, End=12]
, Meeting [i=10, Begin=2, End=13]
]
===========最优会议安排如下:=====================
[Meeting [i=1, Begin=1, End=4]
, Meeting [i=4, Begin=5, End=7]
, Meeting [i=8, Begin=8, End=11]
]

注:该题目已经按照结束时间排好序,对于会议结束时间非升序排列的题目,我们需要按照结束时间对其进行排序之后再进行挑选。


四、算法正确性证明

         对贪心算法的正确性证明,我们要从两方面进行:

                1.问题存在从贪心选择开始的最优解。(满足贪心选择性质)

                2.一步一步的贪心选择能够得到问题的最优解。(满足最优子结构性质)

        1.贪心选择性质

证明思路:(归纳法)先提出一个全局最优解,证明可以对该解加以修改(“剪枝”),将以贪心选择为基础的解加上,该解也为最优解。

等待安排的会议
会议i12345678910
开始时间bi1325356882
结束时间ei45678910111213

        依据该例题,我们从会议2开始选择(不选择会议1),我们可以选择{2,4,8}三个会议,得到一个全局最优解,这时,我们从这个解中去掉会议2,加上会议1,这个解仍然成立,故而可以证明存在从贪心选择开始的最优解

        2.最优子结构性质

证明思路:(反证法)现提出一个全局最优解,去掉其中的某一部分剩下的解在局部也是最优的

等待安排的会议
会议i12345678910
开始时间bi1325356882
结束时间ei45678910111213

        我们可以选择{1,4,8}这三个会议,得到一个全局的最优解,这时,我们把会议1从该解中去除,得到{4,8},只要证明{4,8}这个解是会议4~10部分(红色边框)的局部最优解,就可以证明其满足最优子结构性质。

        证明过程:

        假设A = {1,4,8}为全局的最优解,去掉第一个解,得到A' = {4,8},之后,我们再假设一个解A*比A'更优(解释:A'选择了两个会议,我们就假设A*在局部中选择了三个会议),我们就可以得出A' <  A*,这时我们加上先前去掉的一部分,可以得出A'+{1} < A*+{1},而A'+{1}等价于我们之前假设的全局最优解A,即A < A*+{1},因此,如果在局部存在一个比A'更优的最优解,那么A就不是最优解,与我们最开始的假设相矛盾,所以A'必定是局部的最优解,故而证明其满足最优子结构性质


五、其他版本

        以上例题我们展示了Java版本代码,为辅助各位读者更好的学习,下面分别是C语言、C++以及Python实现上述算法。(语言的转换由ChatGPT完成,如发现错误,欢迎评论指正。

C语言版本:

#include <stdio.h>
#include <stdlib.h>

// 会议结构体
struct Meeting {
    int i;
    int Begin;
    int End;
};

int compare(const void *a, const void *b) {
    return ((struct Meeting *)a)->End - ((struct Meeting *)b)->End;
}

int main() {
    struct Meeting meetings[] = {
        {1, 1, 4},
        {2, 3, 5},
        {3, 2, 6},
        {4, 5, 7},
        {5, 3, 8},
        {6, 5, 9},
        {7, 6, 10},
        {8, 8, 11},
        {9, 8, 12},
        {10, 2, 13}
    };

    int n = sizeof(meetings) / sizeof(meetings[0]);

    qsort(meetings, n, sizeof(meetings[0]), compare);

    printf("==========全部会议如下============================\n");
    for (int i = 0; i < n; i++) {
        printf("Meeting [i=%d, Begin=%d, End=%d]\n", meetings[i].i, meetings[i].Begin, meetings[i].End);
    }

    int e = 0;
    printf("===========最优会议安排如下:=====================\n");
    for (int i = 0; i < n; i++) {
        if (e < meetings[i].Begin) {
            e = meetings[i].End;
            printf("Meeting [i=%d, Begin=%d, End=%d]\n", meetings[i].i, meetings[i].Begin, meetings[i].End);
        }
    }

    return 0;
}

 C++版本:

#include <iostream>
#include <vector>
#include <algorithm>

// 会议类
class Meeting {
public:
    int i;
    int Begin;
    int End;

    Meeting(int i, int begin, int end) : i(i), Begin(begin), End(end) {}
};

bool compare(const Meeting &a, const Meeting &b) {
    return a.End < b.End;
}

int main() {
    std::vector<Meeting> meetings = {
        {1, 1, 4},
        {2, 3, 5},
        {3, 2, 6},
        {4, 5, 7},
        {5, 3, 8},
        {6, 5, 9},
        {7, 6, 10},
        {8, 8, 11},
        {9, 8, 12},
        {10, 2, 13}
    };

    std::sort(meetings.begin(), meetings.end(), compare);

    std::cout << "==========全部会议如下============================" << std::endl;
    for (const auto &meeting : meetings) {
        std::cout << "Meeting [i=" << meeting.i << ", Begin=" << meeting.Begin << ", End=" << meeting.End << "]" << std::endl;
    }

    int e = 0;
    std::cout << "===========最优会议安排如下:=====================" << std::endl;
    for (const auto &meeting : meetings) {
        if (e < meeting.Begin) {
            e = meeting.End;
            std::cout << "Meeting [i=" << meeting.i << ", Begin=" << meeting.Begin << ", End=" << meeting.End << "]" << std::endl;
        }
    }

    return 0;
}

 Python版本:

class Meeting:
    def __init__(self, i, begin, end):
        self.i = i
        self.Begin = begin
        self.End = end

meetings = [
    Meeting(1, 1, 4),
    Meeting(2, 3, 5),
    Meeting(3, 2, 6),
    Meeting(4, 5, 7),
    Meeting(5, 3, 8),
    Meeting(6, 5, 9),
    Meeting(7, 6, 10),
    Meeting(8, 8, 11),
    Meeting(9, 8, 12),
    Meeting(10, 2, 13)
]

meetings.sort(key=lambda x: x.End)

print("==========全部会议如下============================")
for meeting in meetings:
    print(f"Meeting [i={meeting.i}, Begin={meeting.Begin}, End={meeting.End}]")

e = 0
print("===========最优会议安排如下:=====================")
for meeting in meetings:
    if e < meeting.Begin:
        e = meeting.End
        print(f"Meeting [i={meeting.i}, Begin={meeting.Begin}, End={meeting.End}]")


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Μινγκ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值