Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2119690
  • 博文数量: 249
  • 博客积分: 1305
  • 博客等级: 军士长
  • 技术积分: 4733
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-17 10:37
个人简介

不懂的东西还有很多,随着不断的学习,不懂的东西更多,无法消灭更多不懂的东西,那就不断的充实自己吧。 欢迎关注微信公众号:菜鸟的机器学习

文章分类

全部博文(249)

文章存档

2015年(1)

2014年(4)

2013年(208)

2012年(35)

2011年(1)

分类: C/C++

2013-04-16 14:27:33

问题描述:

T[0:n-1]n个元素的数组。对任一元素x,设S(x)={i|T[i]=x}。当|S(x)|>n/2时,称xT的主元素。设计一个线性时间算法,确定T[0:n-1]是否有一个主元素。

分析与解答:

(1)基于分治法的线性期望时间求主元素算法

中位数:数列排序后位于最中间的那个数,如果一个数列有主元素,那么必然是中位数。求一个数列有没有主元素,只要看中位数是不是主元素。

找中位数的方法:选择一个元素作为划分起点,然后用快速排序的方法将小于它的移动到左边,大于它的移动到右边。这样将元素划分为两个部分。此时,划分元素所在位置为k。如果k>n/2,那么继续用同样的方法在左边部分找;反之,就在右边部分找;k=n/2时,就找到了中位数。

根据快速排序的思想,可以在平均时间复杂度为O(n)的时间找出一个数列的中位数。然后再用O(n)的时间检查它是否为主元素。
    代码如下所示。

  1. #include <iostream>
  2. using namespace std;

  3. #define MAXNUM 100

  4. //基于分治法的线性期望时间求主元素算法
  5. void Swap(int *a, int *b)
  6. {
  7.     int tmp = *a;
  8.     *a = *b;
  9.     *b = tmp;
  10. }

  11. //随机划分
  12. int PartitionRandom(int a[], int first, int last)
  13. {
  14.     int priot = first + rand() % (last - first + 1);
  15.     Swap(&a[first], &a[priot]);
  16.     int key = a[first];

  17.     while(first < last)
  18.     {
  19.         while(first < last && a[last] >= key)
  20.         {
  21.             last--;
  22.         }
  23.         Swap(&a[first], &a[last]);

  24.         while(first < last && a[first] <= key)
  25.         {
  26.             first++;
  27.         }
  28.         Swap(&a[first], &a[last]);
  29.     }
  30.     return first;
  31. }

  32. int Select(int a[], int first, int last , int i)
  33. {
  34.     if(first == last)
  35.     {
  36.         return a[first];
  37.     }
  38.     int priot = PartitionRandom(a, first, last);
  39.     int k = priot - first + 1;
  40.     if(k == i)
  41.     {
  42.         return a[k];
  43.     }
  44.     if(k > i)
  45.     {
  46.         return Select(a, first, priot - 1, i);
  47.     }
  48.     else//k < i
  49.     {
  50.         return Select(a, priot + 1, last, i - k);
  51.     }
  52. }

  53. void FindMaster(int a[], int length)
  54. {
  55.     int mid = length/2;
  56.     int key = Select(a, 0, length - 1, mid);
  57.     cout << "中位数为:" << key << endl;
  58.     int count = 0;
  59.     for(int i = 0; i < length; i++)
  60.     {
  61.         if(a[i] == key)
  62.         {
  63.             count++;
  64.         }
  65.     }
  66.     if(count > mid)
  67.     {
  68.         cout << "主元素为:" << key << endl;
  69.     }
  70.     else
  71.     {
  72.         cout << "该数组中没有主元素" << endl;
  73.     }

  74. }

  75. int main(int argc, char* argv[])
  76. {
  77.     int a[MAXNUM];
  78.     int length;
  79.     cout << "请输入数组元素个数:";
  80.     cin >> length;
  81.     for(int i = 0; i < length; i++)
  82.     {
  83.         cin >> a[i];
  84.     }
  85.     cout << "输入的元素如下所示:" << endl;
  86.     for(int i = 0; i < length; i++)
  87.     {
  88.         cout << a[i] << " ";
  89.     }
  90.     cout << endl;

  91.     FindMaster(a, length);
  92.     
  93.     return 0;
  94. }
        时间复杂度分析:由于查找中位数的平均复杂度为O(n),然后遍历一次数组,进行判定,时间辅助度为O(n)。所以,总的时间复杂度为O(n)+O(n) = O(n)。

(2)无序关系时求主元素的O(nlogn)算法
        中存在主元素,则将分为两部分后,的主元素也必为两部分中至少一部分的主元素,因此可用分治法。
        将元素划分为两部分,递归地检查两部分有无主元素。算法如下:
        a. 只含一个元素,则此元素就是主元素,返回此数。
        b. 分为两部分T1 T2(二者元素个数相等或只差一个),分别递归调用此方法求其主元素m1 m2
        c. m1 m2 都存在且相等,则这个数就是的主元素,返回此数。
        d. m1 m2 都存在且不等,则分别检查这两个数是否为的主元素,若有则返回此数,若无则返回空值。
        e. m1 m2 只有一个存在,则检查这个数是否为的主元素,若是则返回此数,若否就返回空值。
        f. m1 m2 都不存在,则无主元素,返回空值。

(3)无序集的主元素问题的线性时间算法
    
这个问题可以采用《编程之美》上“寻找发帖水王”的方法。如果每次删除两个不同的数字(不管是否包含主元素的数字),那么在剩下的数字中,主元素的出现的次数仍然超过总数的一半。可以通过不断的重复这个过程,转化为更小的问题,从而得到答案。
      
代码如下所示。

  1. void Find(int a[], int length)
  2. {
  3.     int candidate;
  4.     int i, ntimes;
  5.     i = 0;
  6.     ntimes = 0;
  7.     
  8.     for(i = 0; i < length; i++)
  9.     {
  10.         if(ntimes == 0)//计数为0时,读入新的元素,计数加1
  11.         {
  12.             candidate = a[i];
  13.             ntimes = 1;
  14.         }
  15.         else
  16.         {
  17.             if(candidate == a[i])//如果数据相同,计数加1
  18.             {
  19.                 ntimes++;
  20.             }
  21.             else
  22.             {
  23.                 ntimes--;    //如果计数不同,则计数减1,相当于删除了两个元素
  24.             }
  25.         }
  26.     }

  27.     int count = 0;
  28.     for(i = 0; i <length; i++)
  29.     {
  30.         if(candidate == a[i])
  31.             count++;
  32.     }
  33.     //最终得到的candidate元素有可能是序列最末位的两个元素之一
  34.     //因此,需要验证
  35.     if(count > length/2)
  36.     {
  37.         cout << endl << "主元素为: " << candidate << endl;
  38.     }
  39.     else
  40.     {
  41.         cout << "没有主元素." << endl;
  42.     }
  43. }
        时间复杂度分析:遍历一次数组需要O(n)的时间,所以总的时间复杂度为O(n),且只需要常数的额外内存。




        参考:《算法设计实验题解》、http://blog.sina.com.cn/s/blog_4ae8f77f0100uptr.html,感谢这位朋友的思路。



                                                                        梦醒潇湘love
                                                                2013年4月16日 14:22








阅读(8994) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~