0031算法笔记——【回溯法】旅行员售货问题和圆排列问题

1、旅行员售货问题

问题描述

某售货员要到若干城市去推销商品,已知各城市之间的路程(旅费),他要选定一条从驻地出发,经过每个城市一遍,最后回到驻地的路线,使总的路程(总旅费)最小。

问题分析

旅行售货员问题的解空间是一棵排列树。对于排列树的回溯法与生成1,2,……n的所有排列的递归算法Perm类似。开始时x=[1,2,……n],则相应的排列树有x[1:n]的所有排列构成。

在递归算法Backtrack中,当i=n时,当前扩展节点是排列树的叶节点的父节点。此时算法检测图G是否存在一条从顶点x[n-1]到顶点x[n]的边和一条从顶点x[n]到顶点1的边。如果这两条边都存在,则找到一条旅行员售货

回路。此时,算法还需要判断这条回路的费用是否优于已找到的当前最优回流的费用bestc。如果是,则必须更新当前最优值bestc和当前最优解bestx。

当i

算法具体代码如下:

[cpp]view plain copy

1.//5d9 旅行员售货问题回溯法求解

2.#include "stdafx.h"

3.#include

4.#include

https://www.360docs.net/doc/2317683989.html,ing namespace std;

6.

7.ifstream fin("5d9.txt");

8.const int N = 4;//图的顶点数

9.

10.template

11.class Traveling

12.{

13.template

14.friend Type TSP(Type **a, int n);

15.private:

16.void Backtrack(int i);

17.int n, // 图G的顶点数

18. *x, // 当前解

19. *bestx; // 当前最优解

20. Type **a, // 图G的领接矩阵

21. cc, // 当前费用

22. bestc; // 当前最优值

23.int NoEdge; // 无边标记

24. };

25.

26.template

27.inline void Swap(Type &a, Type &b);

28.

29.template

30.Type TSP(Type **a, int n);

31.

32.int main()

33.{

34. cout<<"图的顶点个数 n="<

35.

36.int **a=new int*[N+1];

37.for(int i=0;i<=N;i++)

38. {

39. a[i]=new int[N+1];

40. }

41.

42. cout<<"图的邻接矩阵为:"<

43.

44.for(int i=1;i<=N;i++)

45. {

46.for(int j=1;j<=N;j++)

47. {

48. fin>>a[i][j];

49. cout<

50. }

51. cout<

52. }

53. cout<<"最短回路的长为:"<

54.

55.for(int i=0;i<=N;i++)

56. {

57.delete []a[i];

58. }

59.delete []a;

60.

61. a=0;

62.return 0;

63.}

64.

65.template

66.void Traveling::Backtrack(int i)

67.{

68.if (i == n)

69. {

70.if (a[x[n-1]][x[n]] != 0 && a[x[n]][1] != 0 &&

71. (cc + a[x[n-1]][x[n]] + a[x[n]][1] < bestc || bestc == 0))

72. {

73.for (int j = 1; j <= n; j++) bestx[j] = x[j];

74. bestc = cc + a[x[n-1]][x[n]] + a[x[n]][1];

75. }

76. }

77.else

78. {

79.for (int j = i; j <= n; j++)

80. {

81.// 是否可进入x[j]子树?

82.if (a[x[i-1]][x[j]] != 0 && (cc + a[x[i-1]][x[i]] < bestc || be

stc == 0))

83. {

84.// 搜索子树

85. Swap(x[i], x[j]);

86. cc += a[x[i-1]][x[i]]; //当前费用累加

87. Backtrack(i+1); //排列向右扩展,排列树向下一层扩展

88. cc -= a[x[i-1]][x[i]];

89. Swap(x[i], x[j]);

90. }

91. }

92. }

93.}

94.

95.template

96.Type TSP(Type **a, int n)

97.{

98. Traveling Y;

99. Y.n=n;

100. Y.x=new int[n+1];

101. Y.bestx=new int[n+1];

102.

103.for(int i=1;i<=n;i++)

104. {

105. Y.x[i]=i;

106. }

107.

108. Y.a=a;

109. https://www.360docs.net/doc/2317683989.html,=0;

110. Y.bestc=0;

111.

112. Y.NoEdge=0;

113. Y.Backtrack(2);

114.

115. cout<<"最短回路为:"<

116.for(int i=1;i<=n;i++)

117. {

118. cout< ";

119. }

120. cout<

121.

122.delete [] Y.x;

123. Y.x=0;

124.delete [] Y.bestx;

125.

126. Y.bestx=0;

127.return Y.bestc;

128.}

129.

130.template

131.inline void Swap(Type &a, Type &b)

132.{

133. Type temp=a;

134. a=b;

135. b=temp;

136.}

算法backtrack在最坏情况下可能需要更新当前最优解O((n-1)!)次,每次更新bestx需计算时间O(n),从而整个算法的计算时间复杂性为

O(n!)。

程序运行结果如图:

2、圆排列问题

问题描述

给定n个大小不等的圆c1,c2,…,cn,现要将这n个圆排进一个矩形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个圆的所有排列中找出有最小长度的圆排列。例如,当n=3,且所给的3个圆的半径分别为1,1,2时,这3个圆的最小长度的圆排列如图所示。其最小长度为。

问题分析

圆排列问题的解空间是一棵排列树。按照回溯法搜索排列树的算法框架,设开始时a=[r1,r2,……rn]是所给的n个元的半径,则相应的排列树由a[1:n]的所有排列构成。

解圆排列问题的回溯算法中,CirclePerm(n,a)返回找到的最小的圆排列长度。初始时,数组a是输入的n个圆的半径,计算结束后返回相应于最优解的圆排列。center计算圆在当前圆排列中的横坐标,由x^2 =

sqrt((r1+r2)^2-(r1-r2)^2)推导出x = 2*sqrt(r1*r2)。Compoute计算当前圆排列的

长度。变量min记录当前最小圆排列长度。数组r表示当前圆排列。数组x则记录当前圆排列中各圆的圆心横坐标。

在递归算法Backtrack中,当i>n时,算法搜索至叶节点,得到新的圆排列方案。此时算法调用Compute计算当前圆排列的长度,适时更新当前最优值。

当i

算法具体代码如下:

[cpp]view plain copy

1.//圆排列问题回溯法求解

2.#include "stdafx.h"

3.#include

4.#include

https://www.360docs.net/doc/2317683989.html,ing namespace std;

6.

7.float CirclePerm(int n,float *a);

8.

9.template

10.inline void Swap(Type &a, Type &b);

11.

12.int main()

13.{

14.float *a = new float[4];

15. a[1] = 1,a[2] = 1,a[3] = 2;

16. cout<<"圆排列中各圆的半径分别为:"<

17.for(int i=1; i<4; i++)

18. {

19. cout<

20. }

21. cout<

22. cout<<"最小圆排列长度为:";

23. cout<

24.return 0;

25.}

26.

27.class Circle

28.{

29.friend float CirclePerm(int,float *);

30.private:

31.float Center(int t);//计算当前所选择的圆在当前圆排列中圆心的横坐标

32.void Compute();//计算当前圆排列的长度

33.void Backtrack(int t);

34.

35.float min, //当前最优值

36. *x, //当前圆排列圆心横坐标

37. *r; //当前圆排列

38.int n; //圆排列中圆的个数

39.};

40.

41.// 计算当前所选择圆的圆心横坐标

42.float Circle::Center(int t)

43.{

44.float temp=0;

45.for (int j=1;j

46. {

47.//由x^2 = sqrt((r1+r2)^2-(r1-r2)^2)推导而来

48.float valuex=x[j]+2.0*sqrt(r[t]*r[j]);

49.if (valuex>temp)

50. {

51. temp=valuex;

52. }

53. }

54.return temp;

55.}

56.

57.// 计算当前圆排列的长度

58.void Circle::Compute(void)

59.{

60.float low=0,high=0;

61.for (int i=1;i<=n;i++)

62. {

63.if (x[i]-r[i]

64. {

65. low=x[i]-r[i];

66. }

67.

68.if (x[i]+r[i]>high)

69. {

70. high=x[i]+r[i];

71. }

72. }

73.if (high-low

74. {

75. min=high-low;

76. }

77.}

78.

79.void Circle::Backtrack(int t)

80.{

81.if (t>n)

82. {

83. Compute();

84. }

85.else

86. {

87.for (int j = t; j <= n; j++)

88. {

89. Swap(r[t], r[j]);

90.float centerx=Center(t);

91.if (centerx+r[t]+r[1]

92. {

93. x[t]=centerx;

94. Backtrack(t+1);

95. }

96. Swap(r[t], r[j]);

97. }

98. }

99.}

100.

101.float CirclePerm(int n,float *a)

102.{

103. Circle X;

104. X.n = n;

105. X.r = a;

106. X.min = 100000;

107.float *x = new float[n+1];

108. X.x = x;

109. X.Backtrack(1);

110.delete []x;

111.return X.min;

112.}

113.

114.template

115.inline void Swap(Type &a, Type &b)

116.{

117. Type temp=a;

118. a=b;

119. b=temp;

120.}

如果不考虑计算当前圆排列中各圆的圆心横坐标和计算当前圆排列长度所需的计算时间按,则Backtrack需要O(n!)计算时间。由于算法Backtrack在最坏情况下需要计算O(n!)次圆排列长度,每次计算需要O(n)计算时间,从而整个算法的计算时间复杂性为O((n+1)!)上述算法尚有许多改进的余地。例如,像1,2,…,n-1,n和

n,n-1, …,2,1这种互为镜像的排列具有相同的圆排列长度,只计算一个就够了,可减少约一半的计算量。另一方面,如果所给的n个圆中有k 个圆有相同的半径,则这k个圆产生的k!个完全相同的圆排列,只计算一个就够了。

程序运行结果为:

相关文档
最新文档