寻找大多数无法解决的物品

时间:2015-04-09 20:22:25

标签: algorithm complexity-theory mergesort divide-and-conquer

我找到了解决此任务的问题。

  

你有N个学生和N个课程。学生只能参加一门课程   许多学生可以参加一门课程。两个学生是   同学如果参加同一课程。如何找出是否存在   N个学生中有N / 2个同学吗?

     

条件:你可以带两个学生,问他们是不是同学   只有你能得到的答案是“是”或“否”。你需要这样做   在O(N * log(N))。

我只需要知道如何制作它,伪代码就可以了。我想它会将学生列表分成合并排序,这给了我复杂性的对数部分。任何想法都会很棒。

3 个答案:

答案 0 :(得分:1)

如果可以知道每门课程的学生人数,那么就应该知道是否有一个学生人数> = N / 2的课程。在这种情况下,在最坏的情况下,您的复杂度为O(N)

如果无法知道每门课程的学生人数,那么您可以使用改变的快速入门。在每个周期中,您挑选一名随机学生,并将其他学生分成同学和非同学。如果同学的数量是> = N / 2,你停止因为你有答案,否则你分析非同学分区。如果该分区中的学生人数是< N / 2你停止因为不可能有一定数量的同学> = N / 2,否则你从非同学分区中挑选另一名学生并仅使用非同学元素重复所有内容。

我们从快速排序算法中得到的只是我们对学生进行分区的方式。上述算法与排序无关。在伪代码中,它看起来像这样(为了清楚起见,数组索引从1开始):

Student[] students = all_students;
int startIndex = 1;
int endIndex = N;    // number of students
int i;

while(startIndex <= N/2){
    endIndex = N;    // this index must point to the last position in each cycle
    students.swap(startIndex, start index +  random_int % (endIndex-startIndex));

    for(i = startIndex + 1; i < endIndex;){
        if(students[startIndex].isClassmatesWith(students[i])){
            i++;
        }else{
            students.swap(i,endIndex);
            endIndex--;
        }
    }

    if(i-startIndex >= N/2){
        return true;
    }

    startIndex = i;
}
return false;

算法启动前分区的情况很简单:

| all_students_that_must_be_analyzed |

在第一次运行期间,这组学生将以这种方式进行分区:

| classmates | to_be_analyzed | not_classmates |

并且在每次运行之后,学生组将被分区如下:

| to_ignore | classmates | to_be_analyzed | not_classmates |

在每次运行结束时,学生组将按以下方式进行分区:

| to_ignore | classmates | not_classmates |

此时我们需要检查同学分区是否有超过N / 2个元素。如果有,那么我们得到肯定的结果,如果不是,我们需要检查 not_classmates 分区是否具有&gt; = N / 2个元素。如果有,那么我们需要继续进行另一次运行,否则我们会得到负面结果。

关于复杂性

更深入地考虑上述算法的复杂性,有两个主要因素影响它,它们是:

  1. 每门课程的学生人数(没有必要知道这个算法的数字)。
  2. 算法每次迭代中找到的同学平均数。
  3. 算法的一个重要部分是随机选择要分析的学生。

    最糟糕的情况是每门课程有1名学生。在这种情况下(我会说明显的原因)复杂性为O(N^2)。如果课程的学生人数不同,那么这种情况就不会发生。

    最糟糕情况的一个例子是,当我们为每门课程提供10名学生,10门课程和1名学生时。我们会第一次检查10名学生,第二次检查9名学生,第三次检查8名学生,依此类推。这带来O(N^2)复杂性。

    最佳案例情景将是您选择的第一个学生在多个学生的课程中> = N / 2。在这种情况下,复杂性将是O(N),因为它会在第一次运行中停止。

    最佳案例场景的一个例子是,当我们有10名学生,其中5名(或更多)是同学时,在第一次运行中,我们选择这5名学生中的一名。在这种情况下,我们只会检查同学一次,找到5个同学,然后返回true

    平均情况是最有趣的部分(更接近真实场景)。在这种情况下,需要进行一些概率计算。

    首先,选择特定课程的学生的机会是[number_of_students_in_the_course] / N。这意味着,在第一次运行中,更有可能选择一个有很多同学的学生。

    话虽如此,让我们考虑这样一种情况,即每次迭代中找到的同学平均数是一个小于N / 2的数字(在快速排序的平均情况下,每个分区的长度也是如此)。让我们说,在每次迭代中找到的同学的平均数量是其余M个学生(不是之前选择的学生的同学)的10%(为便于计算而采取的数量)。在这种情况下,我们将为每次迭代获得M的这些值:

    1. M1 = N - 0.1*N = 0.9*N
    2. M2 = M1 - 0.1*M1 = 0.9*M1 = 0.9*0.9*N = 0.81*N
    3. M3 = M2 - 0.1*M2 = 0.9*M2 = 0.9*0.81*N = 0.729*N我会将其四舍五入为0.73*N以便于计算
    4. M4 = 0.9*M3 = 0.9*0.73*N = 0.657*N ~= 0.66*N
    5. M5 = 0.9*M4 = 0.9*0.66*N = 0.594*N ~= 0.6*N
    6. M6 = 0.9*M5 = 0.9*0.6*N = 0.54*N
    7. M7 = 0.9*M6 = 0.9*0.54*N = 0.486*N ~= 0.49*N
    8. 该算法停止,因为我们有49%的剩余学生,我们不能有超过N / 2名同学。
    9. 显然,在普通同学比例较小的情况下,迭代次数会更多,但结合第一个事实(在很多学生的课程中,学生在早期迭代中被选中的概率更高),复杂性倾向于O(N),迭代次数(在伪代码的外部循环中)将(或多或少)恒定,并且不依赖于N.

      为了更好地解释这种情况,让我们使用更大(但更现实)的数字和超过1个分布。让我们说我们有100名学生(为了计算简单而采用的数字),并且这些学生以下列(假设的)方式之一分配到课程中(这些数字仅用于解释目的,它们不是算法工作所必需的):

      1. 50,30,10,5,1,1,1,1,1
      2. 35,27,25,10,5,1,1,1
      3. 11,9,9,8,7,7,5,5,5,5,5,5,5,5,5,1,1
      4. 给出的数字也是(在这种特殊情况下)第一次选择课程中的学生(不是特定学生,只是该课程的学生)的概率。第一种情况是我们有一半学生的课程。第二种情况是,当我们没有一半的学生课程但超过1门课程有很多学生。第三种情况是我们在课程中有类似的分布。

        在第一种情况下,第一次训练的学生在第一次训练中被选中的可能性为50%,第二次训练的学生被选中的可能性为30%,第三次训练的学生有30%的可能性。课程被选中,第4门课程的学生被选中的概率为5%,第5门课程的学生被选中的概率为1%,第6,第7,第8和第9课程的学生也是如此。对于第一个案例的学生来说,提早被提取的概率更高,并且如果来自该课程的学生在第一次运行中没有被选中,则在第二次运行中被选中的概率仅增加。例如,我们假设在第一轮比赛中挑选了第二道菜的学生。 30%的学生将被删除&#34; (如在&#34;不再考虑&#34;)并且不在第二轮中分析。在第二轮比赛中,我们将剩下70名学生。在第二轮中从第一门课程中挑选学生的概率为5/7,超过70%。让我们假设 - 运气不好 - 在第二轮比赛中,第三道菜的学生被选中。在第3轮比赛中,我们将剩下60名学生,并且在第3轮比赛中获得第一门课程的学生的概率为5/6(超过80%)。我会说我们可以认为我们运气不好在第3轮比赛结束,第1道菜的学生被选中,方法返回true:)

        对于第二和第三种情况,我会遵循每次运行的概率,只是为了简化计算。

        在第二种情况下,我们将有一名学生参加第一轮选修的第一门课程。由于同学的数量不是&lt; = N / 2,算法将继续第二次运行。在第二轮比赛结束时,我们将&#34;删除&#34;从学生集35 + 27 = 62名学生。在第三轮比赛中,我们将剩下38名学生,并且是38&lt; (N / 2)= 50计算停止并返回false

        同样的情况发生在第三种情况下(我们&#34;删除&#34;平均每次运行中剩余学生的10%),但步骤更多。

        最后的注意事项

        在任何情况下,最坏情况中算法的复杂性为O(N^2)平均案例场景主要基于概率,并且倾向于从有很多与会者的课程中提前选择学生。这种行为往往会使复杂性降至O(N),这是我们在最佳案例中所具有的复杂性。

        算法测试

        为了测试算法的理论复杂性,我在C#编写了以下代码:

        public class Course
        {
            public int ID { get; set; }
        
            public Course() : this(0) { }
        
            public Course(int id)
            {
                ID = id;
            }
        
            public override bool Equals(object obj)
            {
                return (obj is Course) && this.Equals((Course)obj);
            }
        
            public bool Equals(Course other)
            {
                return ID == other.ID;
            }
        }
        
        public class Student
        {
            public int ID { get; set; }
            public Course Class { get; set; }
        
            public Student(int id, Course course)
            {
                ID = id;
                Class = course;
            }
        
            public Student(int id) : this(id, null) { }
        
            public Student() : this(0) { }
        
            public bool IsClassmatesWith(Student other)
            {
                return Class == other.Class;
            }
        
            public override bool Equals(object obj)
            {
                return (obj is Student) && this.Equals((Student)obj);
            }
        
            public bool Equals(Student other)
            {
                return ID == other.ID && Class == other.Class;
            }
        }
        
        class Program
        {
            static int[] Sizes { get; set; }
            static List<Student> Students { get; set; }
            static List<Course> Courses { get; set; }
        
            static void Initialize()
            {
                Sizes = new int[] { 2, 10, 100, 1000, 10000, 100000, 1000000 };
                Students = new List<Student>();
                Courses = new List<Course>();
            }
        
            static void PopulateCoursesList(int size)
            {
                for (int i = 1; i <= size; i++)
                {
                    Courses.Add(new Course(i));
                }
            }
        
            static void PopulateStudentsList(int size)
            {
                Random ran = new Random();
        
                for (int i = 1; i <= size; i++)
                {
                    Students.Add(new Student(i, Courses[ran.Next(Courses.Count)]));
                }
            }
        
            static void Swap<T>(List<T> list, int i, int j)
            {
                if (i < list.Count && j < list.Count)
                {
                    T temp = list[i];
                    list[i] = list[j];
                    list[j] = temp;
                }
            }
        
            static bool AreHalfOfStudentsClassmates()
            {
                int startIndex = 0;
                int endIndex;
                int i;
                int numberOfStudentsToConsider = (Students.Count + 1) / 2;
                Random ran = new Random();
        
                while (startIndex <= numberOfStudentsToConsider)
                {
                    endIndex = Students.Count - 1;
                    Swap(Students, startIndex, startIndex + ran.Next(endIndex + 1 - startIndex));
        
                    for (i = startIndex + 1; i <= endIndex; )
                    {
                        if (Students[startIndex].IsClassmatesWith(Students[i]))
                        {
                            i++;
                        }
                        else
                        {
                            Swap(Students, i, endIndex);
                            endIndex--;
                        }
                    }
        
                    if (i - startIndex + 1 >= numberOfStudentsToConsider)
                    {
                        return true;
                    }
        
                    startIndex = i;
                }
        
                return false;
            }
        
            static void Main(string[] args)
            {
                Initialize();
                int studentsSize, coursesSize;
                Stopwatch stopwatch = new Stopwatch();
                TimeSpan duration;
                bool result;
        
                for (int i = 0; i < Sizes.Length; i++)
                {
                    for (int j = 0; j < Sizes.Length; j++)
                    {
                        Courses.Clear();
                        Students.Clear();
                        studentsSize = Sizes[j];
                        coursesSize = Sizes[i];
                        PopulateCoursesList(coursesSize);
                        PopulateStudentsList(studentsSize);
        
                        Console.WriteLine("Test for {0} students and {1} courses.", studentsSize, coursesSize);
                        stopwatch.Start();
                        result = AreHalfOfStudentsClassmates();
                        stopwatch.Stop();
                        duration = stopwatch.Elapsed;
                        var studentsGrouping = Students.GroupBy(s => s.Class);
                        var classWithMoreThanHalfOfTheStudents = studentsGrouping.FirstOrDefault(g => g.Count() >= (studentsSize + 1) / 2);
                        Console.WriteLine(result ? "At least half of the students are classmates." : "Less than half of the students are classmates");
        
                        if ((result && classWithMoreThanHalfOfTheStudents == null)
                            || (!result && classWithMoreThanHalfOfTheStudents != null))
                        {
                            Console.WriteLine("There is something wrong with the result");
                        }
        
                        Console.WriteLine("Test duration: {0}", duration);
                        Console.WriteLine();
                    }
                }
        
                Console.ReadKey();
            }
        }
        

        执行时间与平均案例场景的预期相符。随意使用代码,你只需要复制并粘贴它就可以了。

答案 1 :(得分:1)

首先,配对每个学生(1&amp; 2,3&amp; 4,5&amp; 6 ......等),然后检查并查看哪一对在同一个班级。这对组合中的第一个学生获得晋升#34;如果有一个&#34; oddball&#34;学生,他们在自己的班级,所以他们也得到晋升。如果单个班级包含≥50%的学生,那么&gt; = 50%的晋升学生也在此课程中。如果没有学生晋升,那么如果一个班级包含≥50%的学生,那么第一或第二名学生必须在班级中,所以只需提升他们两个。这导致了> = 50%的促销活动属于大班级的情况。这总是需要⌊N/2⌋比较。

现在,当我们检查升职学生时,如果一个班级包含≥50%的学生,那么&gt; = 50%的升职学生都在这个班级。因此,我们可以简单地递归,直到我们达到停止状态:只有不到三名晋升学生。在每一步,我们推广<= 50%的学生(有时加一),所以这一步最多发生⌈log(N,2)⌉次。

如果升读的学生人数少于三人,那么我们知道如果> = 50%的原始学生在课堂上,那么这些剩下的学生中至少有一人在该课程中。因此,我们可以简单地将每个原始学生与这些升职学生进行比较,这将显示(A)具有&gt; = 50%学生的班级,或(B)没有班级具有≥50%的学生。学生们。这最多需要(N-1)次比较,但只发生一次。请注意,所有原始学生都可能均匀地与剩下的两名学生中的一名学生匹配,这可以检测到两个班级都有50%的学生。

因此复杂性为N/2 *~ log(N,2) + N-1。但是,*~表示我们不会在每次log(N,2)次迭代中迭代所有N / 2名学生,只会减少分数N / 2,N / 4,N / 8。 ..,总和为N.因此总复杂度为N/2 + N/2 + N-1 = 2N-1,当我们删除常量时,我​​们得到O(N)。 (我觉得我可能在这一段中犯了数学错误。如果有人发现它,请告诉我)

在此处查看此操作:http://coliru.stacked-crooked.com/a/144075406b7566c2(由于我在实施中所做的简化,比较计数可能略微超过估算值)

<小时/> 这里的关键是,如果> 50%的学生在一个班级,那么&gt; = 50%的任意对都在该班级,假设这个古怪的学生与自己匹配。一个诀窍是,如果恰好50%匹配,它们可能完全按照原始顺序交替,因此没有人得到晋升。幸运的是,唯一的情况是交替,所以通过推广第一和第二学生,即使在那个边缘的情况下,&gt; = 50%的促销活动是在大班。

证明&gt; = 50%的促销活动属于大班,这很复杂,我甚至不确定我能说清楚为什么会这样。令人困惑的是,它也不适合任何其他分数。如果目标是> = 30%的比较,那么所提升的学生的 none 完全可能在目标类中。所以&gt; = 50%是神奇的数字,它根本不是任意的。

答案 2 :(得分:0)

我会发布一些我的想法.. 首先,我认为我们需要做一些像mergesort这样的事情来制作那个对数部分......我想,在最低层,我们只有2个学生要比较,我们只是问并得到答案。但这并没有解决任何问题。在这种情况下,我们将只有N / 2对学生和知识,要么他们是同学,要么不是。这没有帮助..

下一个想法好一点。我没有把那个设置划分到最低水平,但是当我有4个学生时,我就停止了。所以我有N / 4小集,我把每个人都比较了。如果我发现,至少有两个是同学,那就很好。如果没有,而且所有人都来自不同的班级,我完全忘记了那一组4人。当我将这个应用到每个小组时,我开始将他们加入到8人小组中,只是比较那些已经被标记为同学的人。 (感谢传递性)。再说一次......如果至少有4个同学,在8人组中,我很高兴,如果没有,我就忘记了那个小组。这应该重复,直到我有两套学生,并对两组学生进行一次比较,以得到最终答案。但问题是,在一半中可能有n / 2-1个同学,而在另一半中只有一个学生与他们匹配......而这种算法并不适合这个想法。