回溯算法(backtracking):在许多情况下,回溯算法相当于穷举搜素的巧妙实现,但性能一般不理想。但在某些情况下,它相比蛮力穷举搜索,工作量有显著的节省。在暴力穷举中,在一步内删除一大组可能性的做法叫做剪枝(pruning)。
收费公路重建问题:
设给定N个点p1,p2,…,pn,它们位于x轴。xi是pi的x坐标。进一步设x1 = 0,Xn = DMax(距离集合中的最大值必然是X1与Xn的距离),以及这些点是从左到右给出的。
给出N个点在每一对点之间的距离。对于N个点,共有N(N-1)/2个距离。收费公路问题就是根据距离点集,重新构造一个位置点集。
对于第一个点,我们选择将第一个点置于0处。如果能够解出一个点集,就可以对所有的点加上一个偏移量而构建出无穷多的解。
在距离集合D中,是已经排好序的,从小到大递增的。
代码实现
int FindMax(int *D, int N) {
//在距离集合D中,寻找一个最大的值,为找到则返回-1
for (int i = N * (N - 1) / 2; i >= 1;i--) {
if (D[i] != -1) {
return D[i];
}
}
return -1;
}
int FindNum(int *D,int N,int Num) {
//在规模为N中的D数组,找到一个数Num,并把该数删除(置为-1)
//未找到则返回0
for (int i = 1; i <= N * (N - 1) / 2;i++) {
if (D[i] == Num) {
D[i] = -1;
return 1;
}
}
return 0;
}
void copy(int *Dtmp,int *Dset,int N) {
//将Dset集合中的元素复制到Dtmp中。
for (int i = 1; i <= N * (N - 1) / 2; i++) {
Dtmp[i] = Dset[i];
}
}
核心函数实现
/*
函数说明:X为存储解的集合,Dset为距离数组集合,N为点的规模,left和right分别为X的最左边,和最右边。
*/
int Place(int *X, int *Dset,int N, int left, int right) {
int DMax, i, found = 0, flag = 0; //DMax为当前距离集合的最大值
int *Dtmp = NULL; //Dtmp为Dset的副本,用于回溯
if (left > right) {
return 1;
}
DMax = FindMax(Dset, N); //找到最大值
Dtmp = new int[N*(N - 1) / 2 + 1]; //申请空间
copy(Dtmp, Dset, N); //复制Dset
//先尝试将DMax放在X[right]。
//判断X[i]-DMax的距离是否在集合中,i ∈ [1,left)
for (i = 1; i < left; i++) {
if (! FindNum(Dtmp,N,abs(X[i] - DMax))){
flag = 1;
break;
}
}
//判断X[i]-DMax的距离是否在集合中,i ∈ (right,N]
for (i = N; i > right; i--) {
if (!FindNum(Dtmp, N, abs(X[i] - DMax))) {
flag = 1;
break;
}
}
if (flag == 0) {
//表示可以放置
X[right] = DMax;
found = Place(X, Dtmp, N, left, right - 1); //递归下一层。
}
if (flag == 1 || found == 0) {
//尝试将DMax放在X[left]中。
//判断左边的点,是否到DMax点的距离是否存在。
flag = 0;
copy(Dtmp, Dset, N); //回溯,将Dtmp再次更新为Dset.
//判断X数组左边的点到Dmax的距离是否存在于数组中
for (i = 1; i < left; i++) {
if (!FindNum(Dtmp, N, abs(X[i] - (X[N] - DMax)))) {
flag = 1;
break;
}
}
//判断X数组右边的点到DMax的距离是否存在于数组中
for (i = N; i > right; i--) {
if (!FindNum(Dtmp, N, abs(X[i] - (X[N] - DMax)))) {
flag = 1;
break;
}
}
if (flag == 0) {
//可以放置
X[left] = X[N] - DMax;
found = Place(X, Dtmp, N, left + 1, right); //递归进入下一层
}
}
delete Dtmp; //释放内存
return found; //结果返回上一层
}
int turnPike(int *X, int *Dset,int Nv) {
//将最大和最小的点收录进顶点
X[1] = 0;
X[Nv] = Dset[Nv*(Nv - 1) / 2];
Dset[Nv*(Nv - 1) / 2] = -1; //将最大距离置为-1,表示空
return Place(X, Dset,Nv, 2, Nv - 1);
}
主函数实现
int main() {
int Nv; //Nv为点的个数,Nd为距离集合的个数
int *Dset, *X;
cout << "请输入点的个数" << endl;
cin >> Nv;
X = new int[Nv + 1]; //存储各点位置结果
Dset = new int[Nv * (Nv - 1) / 2 + 1]; //存储点集
cout << "请输入距离的集合" << endl;
for (int i = 1; i <= Nv * (Nv - 1) / 2; i++) {
cin >> Dset[i];
}
if (turnPike(X,Dset,Nv) == 1) {
//找到解
for (int i = 1; i <= Nv; i++) {
cout << X[i] << " ";
}
cout << endl;
}
else
{
//未找到解
cout << "未找到解" << endl;
}
return 0;
}