您的位置:首页 > 其它

划分方法的应用

2015-08-05 13:57 239 查看
划分对于分治的算法设计很重要,下面就专门说一个划分的算法设计,然后再举例子说一些具体的应用。

int Partition(int data[], int start, int end)
{
if(data == NULL)
return -1;
int index = RandomInRange(start, end);
int small = start - 1;

swap(data[index], data[end]);

for(index = start; index < end; index++)
{
if(data[index] < data[end])
{
++small;
if(small != index)
{
swap(data[small], data[index]);
}//if
}//if
}//for

++small;
swap(data[small], data[end]);

return small;
}//Partition()


  下面看一下划分的应用。

快速排序中的划分

void QuickSort(int data[], int start, int end)
{
if(start == end)
return;

int index = Partition(data, start, end);

if(index > start)
QuickSort(data, start, index - 1);
if(index < end)
QuickSort(data, index + 1, end);
}


  递归的程序一定要有递归出口。

查找第K大的数字

int findKth(int *data, int start, int end, int k)
{
int pos = partition(data, start, end);
if(pos + 1 == k)
return data[pos];
else if(pos + 1 > k)
return findKth(data,start, pos -1, k);
else
return findKth(data, pos + 1, end, k);
}

int findKthLargest(int *nums, int numsSize, int k)
{
//转换成求第k小元素
   int m = numsSize - k +  1;

   int result = findKth(nums, 0, numsSize - 1, m);

   return result;
}


  

数组中出现次数超过一半的数字

这个问题貌似很难,但是设想如果这个数组已经排序好了,那么这个出现次数超过一半的数字肯定会出现在中位数的地方。所以可以利用上面的算法,找数组中的中位数小的元素。

但是测试用例一定要首先写好,要防止空的数组、数组中根本不存在超过一半多这样的数字,这次用循环来实现找第k小的元素,不用递归了。

int MoreThanHalfNum_Solution(vector<int> numbers)
{
if(numbers.size() == 0)
return 0;

int start = 0;
int end = numbers.size() - 1;
int middle = numbers.size() >> 1;

int index = partition(numbers, start, end);

while(index != middle)
{
if(index > middle)
{
end = index - 1;
index = partition(numbers, start, end);
}
else
{
start = index + 1;
index = partition(numbers, start, end);
}
}//while

int result = numbers[middle];

if(!checkMoreThanHalf(numbers,result))
{
return 0;
}

return result;
}


  注意上面的循环的方式,感觉比递归的方式更好。但是通过中位数找到的数字可能不是正确的答案,最后要验证一下,这个数字是不是出现的次数多于数组大小的一半。

bool checkMoreThanHalf(vector<int> &numbers, int result)
{
int times = 0;
for(int i = 0; i < numbers.size(); i++)
{
if(numbers[i] == result)
{
times++;
}
}//for

if(times * 2 > numbers.size())
{
return true;
}

return false;
}


  注意上面的函数的返回值:正确的情况下,返回查找到的数据,错误的时候,返回错误码0。

不管怎么弄,上面的算法的实现都是基于划分算法来实现的,其实除了这种方法,还可以有其他的解决方案

设置两个变量,一个变量用于保存目前出现次数最多的那个数字,另一变量用来记录出现的次数。随着不断的遍历,每遍历一个数,如果和前面保存的数字相同,计数器就+1,不相同就-1。如果计数器减到0,就代表之前存的数已经被淹没掉了,这是要重新记录新的数字,同时置计数变量为1。既然结果数字多于数组个数的一半,那么最后记录中留下的肯定是这个结果数字。但是要注意,原数组中可能本来就没有多于一半的这样的数字,但是最后还是会有一个结果的,所以要最后校验一次。如果发现错误的话,及时的返回错误码0。

int MoreThanHalfNum(vector<int> numbers)
{
int times = 1;
int result = numbers[0];

for(int i = 1; i < numbers.size(); i++)
{
if(numbers[i] == result)
{
times++;
}
else
{
times--;
if(times == 0)
{
result = numbers[i];
times = 1;
}

}//else
}//for

if(!checkMoreThanHalf(numbers, result))
{
return 0;
}

return result;
}


  对于查找数组中出现次数多于一半的数字,有这两种方法,他们的时间复杂度都是O(n),但是注意第一种改变了原来的数组,但是第二种没有改变原数组,是一种很好的方法。

获取数组中的最小的K个数

这个题目第一次看到可能会想到先排序,然后就可以很简单的获得最小的k个数了,但是排序的时间复杂度是O(nlogn),这可能达不到算法时间复杂度的要求,所以又想到了划分。类似前面那个出现次数大于数组个数一半的题目,只要划分能够定位到k-1那个元素。那么[0...k-1]的这些数字就是最小的k个数字。

vector<int> GetKLeastNumbers(vector<int> input, int k)
{
vector<int> result;

if(input.size() == 0 || k <= 0 || k > input.size())
return result;

int start = 0;
int end = input.size() - 1;

int index = partition(input, start, end);
while(index != k - 1)
{
if(index > k -1)
{
end = index - 1;
index = partition(input, start, end);
}
else
{
start = index + 1;
index = partition(input, start, end);
}

}//while

for(int i = 0; i < k; i++)
{
result.push_back(input[i]);
}

return result;
}


  但是这个算法会改变原来的数组,所以如果要求不改变原来的数组,得想另外的办法。

最小K个数的另一种解决的办法

我们可以另外建立一个容器,在这个容器中存储最小的K个数字。一次遍历原始的数字,如果这时K的容器数组没有满,直接把这个原始数据放到容器中,如果容器已经满了,则比较原始数组的值和容器中最大值,如果原始的数字小,则替换容器中的最大值。否则忽略这个原始的数据,继续向后遍历。

因此在容器满了之后要做下面3件事:

在K的容器中,找到最大值;可能会删除这个最大值;可能会插入一个新的值。

如果用一个二叉树完成上面的3个操作,时间复杂度就能够控制在O(logk),这样的话整个算法的时间复杂度控制在O(nlogk)。由于每次都是从容器中找到最大值,所以很容易考虑到最大堆,这样的话就可以在O(1)的时间找到容器的最大值,但是删除和插入的时间复杂度是O(logk)。

对于二叉树,可以选用set或者multiset来代替,因为这两个东西都是基于红黑树实现的,红黑树的查找、删除、插入的时间复杂度都是O(logk)。

typedef multiset<int, greater<int> > intSet;
typedef multiset<int, greater<int> >::iterator setIterator;

vector<int> GetKLeastNumbers(const vector<int> &data, int k)
{
intSet leastNumbers;
vector<int> result;

int len = data.size();

leastNumbers.clear();

//检验边界值,增加代码的健壮性
if(k < 1 || data.size() == 0 || data.size() < k)
{
return result;
}

for(int i = 0; i < len;i++)
{
if(leastNumbers.size() < k)
{
leastNumbers.insert(data[i]);
}
else
{
setIterator iterGreatest = leastNumbers.begin();

if(data[i] < *(leastNumbers.begin()))
{
leastNumbers.erase(iterGreatest);
leastNumbers.insert(data[i]);
}
}//else
}//for

//放到vector中返回数据
for(setIterator iterK = leastNumbers.begin(); iterK != leastNumbers.end(); iterK++)
{
result.push_back(*iterK);
}

return result;

}


  这种算法的优点是它不会改动原来的数组,同时特别的适合海量的数据,因为可以不用一次把全部的数读取到内存中来,可以分批的读入内存。所以这种算法很适合n很大但是k很小的情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: