概述
本文讲述数据结构中最常用到的三大算法:分治法、动态规划法和贪心算法,主要从这些算法的经典案例入手来对算法进行分析和理解。
分治法
分治法可以通俗的理解为将一条大鱼分成好几块,分别料理每一块鱼肉,然后再组成一道菜。也就是说分治法是将一个大的问题分成好多个小的问题,这些小问题解决后从而解决整个大问题,在处理过程中这些小问题的处理方法可以不尽相同。我们从下面这个案例来进行进一步的分析和理解。
问题描述
设a[0:n-1]
是已排好序的数组,请改写二分搜索算法,使得当x
不在数组中时,返回小于x
的最大元素位置i
和大于x
的最小元素位置j
。当搜索元素在数组中时,i
和j
相同,均为x
在数组中的位置。
算法思路与设计
把问题简化为在n
个元素的集合中找最大最小值,将这n
个数分为两组,分别找出最大值和最小值。然后递归分解直到每组的个数小于等于2
。利用二分搜索的思想,在数组中查找关键字x
,low
和top
为数组头尾下标。当low<=top
时,如果x
等于数组中间值,则表示x
在数组中,返回下标i,j
;如果x
大于数组中间值,则low
等于中间值+1
,如果x
小于中间值,则top
等于中间值+1
,经过不断循环,直到low
大于top
;如果还是没找到x
,则把top
复制给i,low
赋值给j
,然后返回下标i,j
。完整代码如下。
#include<stdio.h>
int search(int a[],int length, int x)
{
int i = 0, j = 0;
int s= -1;
int top = length - 1;
int mid = 0;
int low = 0;
while (low <= top)
{
mid = (low + top) / 2;
if (a[mid] == x)
{
s = mid;
}
if (a[mid] < x)
{
low = mid + 1;
}
else
{
top = mid - 1;
}
}
if (s == -1)
{
i = top;
j = low;
}
else
{
i = s;
j = i;
}
printf("%d %d",i,j);
return 0;
}
int main()
{
int arr[100];
int x,n;
scanf("%d %d",&n,&x);
for(int i=0;i<n;i++)
{
scanf("%d",&arr[i]);
}
search(arr,n,x);
return 0;
}
动态规划法
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
问题描述
给定n(n<=100)
种物品和一个背包。物品i的重量是wi
,价值为vi
,背包的容量为C(C<=1000)
。问:应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有两个选择:装入或不装入。不能将物品i装入多次,也不能只装入部分物品i
。
算法思路与设计
动态规划就是一个填表的过程。现在有1
个背包,背包容量是10
,有5
个物品,编号为1,2,3,4,5
,他们都有各自的重量和价格。要求在不超过背包容量的情况下,使背包装载物品的价值最大。我们可以吧问题拆分为五个子问题。 我们可以将1,2,3,4,5
子问题的答案都存入一张表中。因为求解2
子问题,需要用到1
子问题的答案(2
的每一步方案要与1
的每一步方案比较,如何2
的该步方案优于1
所对应的方案。则将2
的这步方案标为可行。如果不优于1
的,或者不满足问题的约束条件,则舍弃该方案。继续沿用该步所对应的1
的方案作为该步的方案)。求解3
子问题,需要用到2
子问题的答案,一直递推到求解5
子问题,需要用到4
子问题的答案。而5
子问题就是原问题。5
子问题的答案就是最终原问题的解。完整代码如下:
实验结果
输入案例:
5 10
2 6
2 3
6 5
5 4
4 6
输出结果:15
#define EMPTY
#include<stdio.h>
#include <iostream>
using namespace std ;
int main()
{
int V ,T;
scanf("%d %d",&T,&V);
int f[V+1],w[100],c[100];
for(int m=0;m<T;m++){
scanf("%d",&c[m]);
scanf("%d",&w[m]);
}
const int INF = -66536 ;
#ifdef EMPTY
for(int i = 0 ; i <= V ;i++)
f[i] = 0 ;
#else
f[0] = 0 ;
for(int i = 1 ; i <= V ;i++)
f[i] = INF ;
#endif
for(int i = 0 ; i < T ; i++)
{
for(int v = V ; v >= c[i] ;v--)
{
f[v] = max(f[v-c[i]] + w[i] , f[v]);
}
}
cout<<f[V];
return 0;
}
贪心算法
贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。贪心算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪心算法采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择,就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解。
问题描述
有n头牛(1<=n<=50,000)
要挤奶。给定每头牛挤奶的时间区间A,B
。牛需要呆在畜栏里才能挤奶。一个畜栏同一时间只能容纳一头牛。问至少需要多少个畜栏,才能完成全部挤奶工作。(在同一个畜栏的两头牛,它们挤奶时间区间不能在端点重合)
算法思路与设计
我们可以把每头牛的挤奶时间按开始时间递增或者按结束时间递赠排序,求出一个最大的能兼容这些时间区间的子集,从而把这些牛安排早一个畜栏中;如果还有未安排完的奶牛,即将其时间集合再求其最大的子集,再把它们安排在这第二个畜栏中。如此一直排到安排完为止,而这些子集的个数也就是畜栏的最小个数。完整代码如下:
实验结果
输入样例:
5
1 10
2 4
3 6
5 8
4 7
输出样例:
4
//判断区间是否重叠
bool isOverlap(int x[999][2], int i, int j,int s[]) {
if (x[i][0] > x[j][1] || x[i][1] < x[j][0]) {
for (int m = j + 1; m < i; m++) {
if (s[m] == s[j] && isOverlap(x, i, m, s))
return true;
}
return false;
}
else
return true;
}
//贪心
void Greedy(int x[999][2]) {
int s[999] = { 0 };
for (int i=0; i < 7; i++) {
for (int j = 0; j < i; j++) {
if (!isOverlap(x, i, j,s)) {
s[i] = s[j];
break;
}
else {
continue;
}
}
if (s[i] == 0)
s[i] = maxNum(s) + 1;
}
cout << maxNum(s)<< endl; //所需的畜栏个数
for (int i = 0; i < 7; i++) {
cout << s[i] << endl;//每头牛对应的畜栏
}
}
//畜栏个数
int maxNum(int x[]) {
int max = x[0];
for (int i = 1; i < 7; i++) {
if (max < x[i]) {
max = x[i];
}
}
return max;
}