是否有标准算法将重叠对象平衡到桶中?
Is there a standard algorithm to balance overlapping objects into buckets?
我有一群用户,有给定的开始和结束时间,例如:
{ Name = "Peter", StartTime = "10:30", EndTime = "11:00" },
{ Name = "Dana", StartTime = "11:00", EndTime = "12:30" },
{ Name = "Raymond", StartTime = "10:30", EndTime = "14:00" },
{ Name = "Egon", StartTime = "12:00", EndTime = "13:00" },
{ Name = "Winston", StartTime = "10:00", EndTime = "12:00" }
我想根据它们重叠的时间(基于可配置的阈值,例如,它们至少需要重叠半小时)将它们放入桶中。我希望桶最好有 4 件大,但 2-5 之间的任何范围都是可以接受的。
在上面的示例中,没有 4 个人匹配,所以我有 3 个(Peter、Raymond、Winston)和 2 个(Dana、Egon)中的一个。
我设计了一个似乎依赖于机会而不是科学的算法原型:
- 按开始时间排序列表
- 创建一个空桶
- 从列表中选择第一个用户
- 根据存储桶中的所有用户检查该用户
- 如果该用户与存储桶中的每个人重叠,则将该人放入其中并将其从列表中删除
- 如果存储桶具有理想大小 (4),或者如果我循环检查同一个用户超过 3 次,请关闭存储桶并创建一个新的空存储桶
这对于前几个桶很有效,但会导致只有 2 个人的桶可以更好地组合。
我可以更改算法以从列表中删除所有理想桶并重新洗牌并尝试更多,但我觉得这应该是一个常见问题 - 这就像工人的轮班分配,或者 knapsack problem.
有人知道这类问题的标准算法吗?
(标记组合学因为我认为这是它适用的数学领域,如果错误请纠正我)
根据你的问题,我可能会做一些事情,比如首先制作一个名为 "Person" 的 class pr 之类的东西。赋予 "Name"、"Start Time" 和 "End Time" 的 class 属性。
class Person
{
public string name;
public double start_time;
public double end_time;
}
然后将它们放入某个 Person 类型的有序列表中。 (此外,我目前将时间存储为双精度。您只需将我拥有的时间的任何小数部分乘以 60/100 即可将它们转换回时间。
之后,您创建了一个 Bucket 列表,如果需要,您可以向其中添加新的 Bucket。然后根据您定义的阈值对列表进行排序,如果要比较的两个对象根据该阈值重叠,那么它们都会进入该 Bucket。如果它们不重叠,则移至下一个 Bucket,如果存在重叠,则将其添加到该 Bucket,依此类推,直到到达最后一个 Bucket。如果你遍历了所有的Buckets仍然没有重叠,那么为那个对象创建一个新的Bucket。
class MainFunc
{
static void Main(string[] args)
{
//makes a function to check if 2 values overlap over a threshold
//st stands for start time and et stands for end time
public bool IsWithinThreshold(double st1, double st2, double et1, double et2)
{
double threshold = .5;
if(st1 >= et2 || st2 >= et1)
{
return false
}
else
{
if(st1+threshold <= et2 && st1+threshold <= et1 || st2+threshold <= et1 && st2+threshold <=et2)
{
return true;
}
else
{
return false;
}
}
}
// makes objects of type Person with attributes of name, start time, and end time
Person Peter = new Person();
Peter.name = "Peter"
Peter.start_time = 10.5
Peter.end_time = 11.0
Person Dana = new Person();
Dana.name = "Dana"
Peter.start_time = 11.0
Peter.end_time = 12.5
Person Raymond = new Person();
Raymond.name = "Raymond"
Raymond.start_time = 10.5
Raymond.end_time = 14.0
Person Egon = new Person();
Egon.name = "Egon"
Egon.start_time = 12.0
Egon.end_time = 13.0
Person Winston = new Person();
Winston.name = "Winston"
Winston.start_time = 10.0
Winston.end_time = 12.0
//puts objects of type Person into an unordered list
List<Person> people = new List<Person>();
people.Add(Peter);
people.Add(Dana);
people.Add(Raymond);
people.Add(Egon);
people.Add(Winston);
//sets up a list of lists of People (Buckets in our case)
List<List<Person>> Buckets = new List<List<Person>>;
//sets up an intial Bucket and adds the first person on the list to it
List<Person> Bucketinitial = new List<Person>;
Bucketinitial.add(people[0]);
for(var i = 1; i < people.Count; i++)
{
for(var j = 0; j< Buckets.count; j++)
{
//sets a checker to make sure that all objects in a given Bucket overlap with the person we are checking
bool overlap = true;
for(var k = 0; k< Buckets[k].count; k++)
{
overlap = overlap & IsWithinThreshold(people[i].start_time,Buckets[j][k].start_time,people[i].end_time,Buckets[j][k].end_time)
}
if (overlap == true)
{
Buckets[j].add(people[i])
}
//if all the objects in a bucket don't overlap with the person...
//... make a new Bucket with that person
else
{
List<Person> NewBucket = new List<Person>;
NewBucket.add(people[i]);
Buckets.add(NewBucket);
}
}
}
}
}
然后只需添加一个打印命令即可打印出buckets列表中每个Bucket中每个对象的name属性。有什么问题请留言questions/concerns,干杯
tl;dr: win 动态规划(O(sort(n)) 时间)。
首先,请注意按开始时间顺序连续分桶是可以的。
提议(碎片整理):让a, b, c, d
成为不同的用户,使得StartTime(a) ≤ StartTime(b) ≤ StartTime(c) ≤ StartTime(d)
。如果 X
和 Y
是有效的桶,使得 a, c ∈ X
和 b, d ∈ Y
,那么 X - {c} ∪ {b}
和 Y - {a} ∪ {d}
也是有效的桶。
我只知道如何通过繁琐的案例分析来证明这一点(略)。
结果是,您可以假装将一段分成多行,其中“段落”是按开始时间顺序排列的用户列表,每“行”是一个桶。由于 Knuth 和 Plass 提出了一种优化换行的算法,其中对给定行的惩罚或多或少是一个任意函数。例如,您可以制作 4 桶成本 0、3 桶成本 1、2 桶成本 2 和 1 桶成本 100。
您可以修改算法以合并间隔树以加快搜索速度
- 按开始时间排序列表
- 将项目添加到区间树
- 创建一个空桶
- 从列表中选择第一项
- 使用间隔树的间隔搜索查找第一个填满桶的项目的阈值时间内最早的项目
- 从列表中删除桶中的项目
- 如果列表为空停止,否则转到第 4 步
基本上,您是在间隔步长(由您的可配置阈值给定)中从左向右移动,使用间隔树在您移动时快速查询最近的项目。
我有一群用户,有给定的开始和结束时间,例如:
{ Name = "Peter", StartTime = "10:30", EndTime = "11:00" },
{ Name = "Dana", StartTime = "11:00", EndTime = "12:30" },
{ Name = "Raymond", StartTime = "10:30", EndTime = "14:00" },
{ Name = "Egon", StartTime = "12:00", EndTime = "13:00" },
{ Name = "Winston", StartTime = "10:00", EndTime = "12:00" }
我想根据它们重叠的时间(基于可配置的阈值,例如,它们至少需要重叠半小时)将它们放入桶中。我希望桶最好有 4 件大,但 2-5 之间的任何范围都是可以接受的。
在上面的示例中,没有 4 个人匹配,所以我有 3 个(Peter、Raymond、Winston)和 2 个(Dana、Egon)中的一个。
我设计了一个似乎依赖于机会而不是科学的算法原型:
- 按开始时间排序列表
- 创建一个空桶
- 从列表中选择第一个用户
- 根据存储桶中的所有用户检查该用户
- 如果该用户与存储桶中的每个人重叠,则将该人放入其中并将其从列表中删除
- 如果存储桶具有理想大小 (4),或者如果我循环检查同一个用户超过 3 次,请关闭存储桶并创建一个新的空存储桶
这对于前几个桶很有效,但会导致只有 2 个人的桶可以更好地组合。
我可以更改算法以从列表中删除所有理想桶并重新洗牌并尝试更多,但我觉得这应该是一个常见问题 - 这就像工人的轮班分配,或者 knapsack problem.
有人知道这类问题的标准算法吗?
(标记组合学因为我认为这是它适用的数学领域,如果错误请纠正我)
根据你的问题,我可能会做一些事情,比如首先制作一个名为 "Person" 的 class pr 之类的东西。赋予 "Name"、"Start Time" 和 "End Time" 的 class 属性。
class Person
{
public string name;
public double start_time;
public double end_time;
}
然后将它们放入某个 Person 类型的有序列表中。 (此外,我目前将时间存储为双精度。您只需将我拥有的时间的任何小数部分乘以 60/100 即可将它们转换回时间。
之后,您创建了一个 Bucket 列表,如果需要,您可以向其中添加新的 Bucket。然后根据您定义的阈值对列表进行排序,如果要比较的两个对象根据该阈值重叠,那么它们都会进入该 Bucket。如果它们不重叠,则移至下一个 Bucket,如果存在重叠,则将其添加到该 Bucket,依此类推,直到到达最后一个 Bucket。如果你遍历了所有的Buckets仍然没有重叠,那么为那个对象创建一个新的Bucket。
class MainFunc
{
static void Main(string[] args)
{
//makes a function to check if 2 values overlap over a threshold
//st stands for start time and et stands for end time
public bool IsWithinThreshold(double st1, double st2, double et1, double et2)
{
double threshold = .5;
if(st1 >= et2 || st2 >= et1)
{
return false
}
else
{
if(st1+threshold <= et2 && st1+threshold <= et1 || st2+threshold <= et1 && st2+threshold <=et2)
{
return true;
}
else
{
return false;
}
}
}
// makes objects of type Person with attributes of name, start time, and end time
Person Peter = new Person();
Peter.name = "Peter"
Peter.start_time = 10.5
Peter.end_time = 11.0
Person Dana = new Person();
Dana.name = "Dana"
Peter.start_time = 11.0
Peter.end_time = 12.5
Person Raymond = new Person();
Raymond.name = "Raymond"
Raymond.start_time = 10.5
Raymond.end_time = 14.0
Person Egon = new Person();
Egon.name = "Egon"
Egon.start_time = 12.0
Egon.end_time = 13.0
Person Winston = new Person();
Winston.name = "Winston"
Winston.start_time = 10.0
Winston.end_time = 12.0
//puts objects of type Person into an unordered list
List<Person> people = new List<Person>();
people.Add(Peter);
people.Add(Dana);
people.Add(Raymond);
people.Add(Egon);
people.Add(Winston);
//sets up a list of lists of People (Buckets in our case)
List<List<Person>> Buckets = new List<List<Person>>;
//sets up an intial Bucket and adds the first person on the list to it
List<Person> Bucketinitial = new List<Person>;
Bucketinitial.add(people[0]);
for(var i = 1; i < people.Count; i++)
{
for(var j = 0; j< Buckets.count; j++)
{
//sets a checker to make sure that all objects in a given Bucket overlap with the person we are checking
bool overlap = true;
for(var k = 0; k< Buckets[k].count; k++)
{
overlap = overlap & IsWithinThreshold(people[i].start_time,Buckets[j][k].start_time,people[i].end_time,Buckets[j][k].end_time)
}
if (overlap == true)
{
Buckets[j].add(people[i])
}
//if all the objects in a bucket don't overlap with the person...
//... make a new Bucket with that person
else
{
List<Person> NewBucket = new List<Person>;
NewBucket.add(people[i]);
Buckets.add(NewBucket);
}
}
}
}
}
然后只需添加一个打印命令即可打印出buckets列表中每个Bucket中每个对象的name属性。有什么问题请留言questions/concerns,干杯
tl;dr: win 动态规划(O(sort(n)) 时间)。
首先,请注意按开始时间顺序连续分桶是可以的。
提议(碎片整理):让a, b, c, d
成为不同的用户,使得StartTime(a) ≤ StartTime(b) ≤ StartTime(c) ≤ StartTime(d)
。如果 X
和 Y
是有效的桶,使得 a, c ∈ X
和 b, d ∈ Y
,那么 X - {c} ∪ {b}
和 Y - {a} ∪ {d}
也是有效的桶。
我只知道如何通过繁琐的案例分析来证明这一点(略)。
结果是,您可以假装将一段分成多行,其中“段落”是按开始时间顺序排列的用户列表,每“行”是一个桶。由于 Knuth 和 Plass 提出了一种优化换行的算法,其中对给定行的惩罚或多或少是一个任意函数。例如,您可以制作 4 桶成本 0、3 桶成本 1、2 桶成本 2 和 1 桶成本 100。
您可以修改算法以合并间隔树以加快搜索速度
- 按开始时间排序列表
- 将项目添加到区间树
- 创建一个空桶
- 从列表中选择第一项
- 使用间隔树的间隔搜索查找第一个填满桶的项目的阈值时间内最早的项目
- 从列表中删除桶中的项目
- 如果列表为空停止,否则转到第 4 步
基本上,您是在间隔步长(由您的可配置阈值给定)中从左向右移动,使用间隔树在您移动时快速查询最近的项目。