[数据结构习题]线性表——三元组的最短距离
👉知识点导航💎:【数据结构】线性表——顺序存储
👉[王道数据结构]习题导航💎: p a g e 19.14 page19.14 page19.14
本节为线性表的习题 |
题目描述:
🎇思路①:暴力法
🔱思路分析:
🍰如果不做其他要求,对于求解三个数组中的最短距离问题,我们最先会想到暴力枚举法进行计算,即不断更新最短距离,直到循环结束
💫这里可能会想到暴力+剪枝的方法,但 时间复杂度仍为 O ( n 3 ) O(n^3) O(n3)
🚀对于功能函数:
-
先定义一个 极大值 I N F ( 这里极大值设为 0 x 3 f 3 f 3 f ) INF (这里极大值设为0x3f3f3f) INF(这里极大值设为0x3f3f3f)
-
采用三重循环的方式,依次抽取三个数组中的元素
-
因为 d = ∣ b [ j ] − a [ i ] ∣ + ∣ c [ k ] − b [ j ] ∣ + ∣ a [ i ] − c [ k ] ∣ d = |b[j] - a[i]| + |c[k] - b[j]| + |a[i] - c[k]| d=∣b[j]−a[i]∣+∣c[k]−b[j]∣+∣a[i]−c[k]∣,所以,前两重循环之后,我们只能进行一次剪枝:
//剪枝 if (abs(b[j] - a[i]) >= min) //如果这个距离已经大于当前最小值了,则结束此轮 continue;
因为有两个表达式与三个元素有关,所以当出现两个元素时,只能确定一个表达式,进行第三次循环后,再做出判断
-
不断更新最小值,并加入到三元组中,直到遍历结束
代码实现:
#define INF 0x3f3f3f
int Getmin(int a[], int b[], int c[], int l1,int l2, int l3,int * triple)
{
int min = INF;
for (int i = 0; i < l1; i++) {
for (int j = 0; j < l2; j++) {
//剪枝
if (abs(b[j] - a[i]) >= min) //如果这个距离已经大于当前最小值了,则结束此轮
continue;
for (int k = 0; k < l3; k++) {
int d = abs(b[j] - a[i]) + abs(c[k] - b[j]) + abs(a[i] - c[k]);
if (d < min)
{
min = d;
triple[0] = a[i]; triple[1] = b[j]; triple[2] = c[k];
}
}
}
}
return min;
}
🚀完整代码实现:
#include<iostream>
#include<cmath>
#define INF 0x3f3f3f
using namespace std;
int Getmin(int a[], int b[], int c[], int l1,int l2, int l3,int * triple)
{
int min = INF;
for (int i = 0; i < l1; i++) {
for (int j = 0; j < l2; j++) {
//剪枝
if (abs(b[j] - a[i]) >= min) //如果这个距离已经大于当前最小值了,则结束此轮
continue;
for (int k = 0; k < l3; k++) {
int d = abs(b[j] - a[i]) + abs(c[k] - b[j]) + abs(a[i] - c[k]);
if (d < min)
{
min = d;
triple[0] = a[i]; triple[1] = b[j]; triple[2] = c[k];
}
}
}
}
return min;
}
void Print(int a[], int n)
{
cout << "(";
for (int i = 0; i < n; i++)
{
if (i == 2)
{
cout << a[i];
break;
}
cout << a[i] << ",";
}cout <<")" << endl;
}
int main() {
int S1[] = { -1,0,9 };
int S2[] = { -25,-10,10,11 };
int S3[] = { 2,9,17,30,41 };
int triple[3]; //存放三元组
int l1 = sizeof(S1) / sizeof(S1[0]);
int l2 = sizeof(S2) / sizeof(S2[0]);
int l3 = sizeof(S3) / sizeof(S3[0]);
cout<<"最短距离为:"<<Getmin(S1, S2, S3,l1,l2,l3,triple)<<endl;
cout << "三元组为:" << endl;
Print(triple, 3);
system("pause");
return 0;
}
输出结果:√
😨因此,暴力法在难以剪枝的情况下,效率十分低下 !!!
🎇思路②:找最小
🔱思路分析:
由于 D = ∣ a − b ∣ + ∣ b − c ∣ + ∣ c − a ∣ D=|a-b|+|b-c|+|c-a| D=∣a−b∣+∣b−c∣+∣c−a∣,则有如下结论:
①当 a = b = c a=b=c a=b=c时:距离一定为最小(为0)
②若不满足,则假设 a ≤ b ≤ c a≤b≤c a≤b≤c,则所求距离如图所示:
🚀可以看到, L 1 = ∣ b − a ∣ , L 2 = ∣ c − b ∣ , L 3 = ∣ c − a ∣ L1=|b-a|,L2=|c-b|,L3=|c-a| L1=∣b−a∣,L2=∣c−b∣,L3=∣c−a∣,则 D = L 1 + L 2 + L 3 = 2 ∗ L 3 D=L1+L2+L3=2*L3 D=L1+L2+L3=2∗L3
算法思路:
🔆因此,由D的表达式可知,距离仅取决于最大和最小值,所以问题可以简化为固定c,找到一个a,使 ∣ c − a ∣ |c-a| ∣c−a∣最小
🚀step:
- 由于题目中数组为升序排列,因此,每个数组中最小的元素值即为第一个,则从前往后抽取数组内元素
- 对于手算实现,我们其实可以每次固定最小,然后找其他两个集合中比它大又尽可能小的元素,得到当前最小的元素对应的最小距离
我们通过实例来解释:
对于如下升序集合来说:
我们每一次都找到三个集合中,找到没有被处理过的最小元素,和其他两个集合中比它大的最小元素,得到该最小元素对应的最短距离后,令它所在的数组下标后移一位,继续重复操作,直到存在有数组访问溢出或 a = b = c a=b=c a=b=c
图解分析:
🎇所以,我们最终会得到前面k个最小元素对应的最短距离
✨为什么会是它的最短距离?
因为我们固定了最小元素,只需要找到最大的元素,即可确定它的距离,那么,我们尽可能从另外两个集合中抽取尽可能小的两个元素(但要大于所固定的最小元素),这样就使得最大的元素最小,也就得到了最短距离
🎇最终结束时,集合 S 1 S1 S1中的第三个元素为最小值,已经无法再向右移动,所以结束
✨因为当有一组超界时,也就是最小值无 a a a法向上增加,那么此时增加 b , c b,c b,c只会使结果更大,将无意义,所以结束
🚀step:
- 使用min记录最短距离,初始赋值为极大 ( 0 x 3 f 3 f 3 f ) (0x3f3f3f) (0x3f3f3f)
- 得到数组 S 1 , S 2 , S 3 S1,S2,S3 S1,S2,S3,令初始指针指向数组的首位置 i = j = k = 0 i=j=k=0 i=j=k=0,当 i < L ( S 1 ) , j < L ( S 2 ) , k < L ( S 3 ) i<L(S1),j<L(S2),k<L(S3) i<L(S1),j<L(S2),k<L(S3)时:
计算距离d,若 d < m i n d<min d<min, m i n = d min=d min=d,将当前最小元素对应数组的指针+1,试图1找到更小的距离
💯 时间复杂度:
由于,只有当一个数组全部遍历结束之后才会结束,因此,最坏的情况为,需要遍历数量级为 L m a x = L ( S 1 ) + L ( S 2 ) + L ( S 3 ) L_{max}=L(S1)+L(S2)+L(S3) Lmax=L(S1)+L(S2)+L(S3)的次数,所以时间复杂度为 O ( L m a x ) O(L_{max}) O(Lmax)
🚀功能函数:
①判断最小元素:
//判断此时三元组中的最小值是不是a
bool isMin(int a, int b, int c)
{
if (a <= b && a <= c)
return true;
return false;
}
②得到最短距离:
//功能函数
int Findmin(int a[], int b[], int c[], int l1, int l2, int l3,int *triple)
{
int i = 0, j = 0, k = 0;
int min = INF;
while (i < l1 && j < l2 && k < l3 && min>0)
{
int d = abs(a[i] - b[j]) + abs(c[k] - b[j]) + abs(a[i] - c[k]);
if (d < min)
{
min = d;
triple[0] = a[i]; triple[1] = b[j]; triple[2] = c[k];
}
//判断当前作为最小的集合元素
if (isMin(a[i], b[j], c[k]))
i++;
else if (isMin(b[j], a[i], c[k]))
j++;
else
k++;
}
return min;
}
注意这里 m i n = = 0 min==0 min==0时也会停下,因为不可能有更小的了
🚀完整代码实现:
#include<iostream>
#include<cmath>
#define INF 0x3f3f3f //定义一个极大值
using namespace std;
//判断此时三元组中的最小值是不是a
bool isMin(int a, int b, int c)
{
if (a <= b && a <= c)
return true;
return false;
}
//功能函数
int Findmin(int a[], int b[], int c[], int l1, int l2, int l3,int *triple)
{
int i = 0, j = 0, k = 0;
int min = INF;
while (i < l1 && j < l2 && k < l3 && min>0)
{
int d = abs(a[i] - b[j]) + abs(c[k] - b[j]) + abs(a[i] - c[k]);
if (d < min)
{
min = d;
triple[0] = a[i]; triple[1] = b[j]; triple[2] = c[k];
}
//判断当前作为最小的集合元素
if (isMin(a[i], b[j], c[k]))
i++;
else if (isMin(b[j], a[i], c[k]))
j++;
else
k++;
}
return min;
}
//输出
void Print(int a[], int n)
{
cout << "(";
for (int i = 0; i < n; i++)
{
if (i == 2)
{
cout << a[i];
break;
}
cout << a[i] << ",";
}cout <<")" << endl;
}
int main() {
int S1[] = { -1,0,9 };
int S2[] = { -25,-10,10,11 };
int S3[] = { 2,9,17,30,41 };
int triple[3];
int l1 = sizeof(S1) / sizeof(S1[0]);
int l2 = sizeof(S2) / sizeof(S2[0]);
int l3 = sizeof(S3) / sizeof(S3[0]);
cout <<"最短距离为:" << Findmin(S1, S2, S3, l1, l2, l3,triple) << endl;;
cout << "三元组为:" << endl;
Print(triple, 3);
system("pause");
return 0;
}
输出结果:√