参考:Robert Sedgewick,《算法:C语言实现》第1章
1.1算法
我们把数据结构看作是算法的副产品或者最终产物,研究这些数据结构从而以便理解算法。简单的算法可以导致非常复杂的数据结构,反之,复杂算法可以利用简单的数据结构。
1.2 典型问题-连通性问题
要点:如果需求需要输出N个对象(整数)中,存在连接的数对。那么最多只能输出N-1个数对。如果能够输出N-1个数对,那说明给定的所有对象都是连通的。
1.2.1 快速查找算法
利用整数数组,每个整数对应一个对象,使用数组下标表示新建的数对。
通俗理解:数组a[N],使用自然数列赋值,表示有N台电脑(对象),大家都不相等,则所有电脑之间都没有连接。数值相等则表示有链接!!! 输入数对1-2,就是询问电脑1和电脑2有没有连接。
算法实现方法:输入数对1-2,就表示电脑1和电脑2新建连接。并把电脑2的值(数据)赋值给电脑1。数值相等则表示有链接!!!当再次输入1-2时,判断a[1] == a[2],即表示1-2已经建立了连接!!
#include <stdio.h>
#define N (1000)
int main()
{
int i, p, q, t;
int id[N];
//自然数列,所有对象的数值互不相等,则表示大家之间都没有连接
for (i = 0; i < N; i++)
{
id[i] = i;
}
//循环读入整数对
while (scanf("%d-%d", &p, &q) == 2)
{
//如果对象p与q是连通的,则读取下一对数对
if (id[p] == id[q])
continue;
//如果id[p]与id[q]的值不相等,则说明p-q是新对,就是没有连接
//则将所有原本与id[p]元素值相等的所有元素连接到q,即建立连接
for (t = id[p], i = 0; i < N; i++)
{
if (id[i] == t)
id[i] = id[q];
}
//因为p-q是新对,所以输出这个对
printf("New pair: %d-%d\n", p, q);
}
return 0;
}
运行结果
可以通过具体的数据变化来查看算法的实现过程。
可以看出,为了实现查找操作,只需测试指定数组元素的值是否相等就行,因此称之为快速查找;而合并操作对于每一对输入都需要遍历整个数组元素,因此为慢速合并。
求解N个对象的连通性问题,如果执行M次合并操作,那么快速查找算法至少要执行M*N条指令。
1.2.2 快速合并算法
#include <stdio.h>
#define N (10)
int main()
{
int i, p, q, j;
int id[N];
//初始化对象集合中元素的初始值
for (i = 0; i < N; i++) id[i] = i;
//循环读入整数对
while (scanf("%d-%d", &p, &q) == 2)
{
//从位置P读取值,即读取p的根节点
for (i = p; i != id[i]; i = id[i]);
for (j = q; j != id[j]; j = id[j]);
if (i == j)
{
//i、j 位置对象的值相等则是已存在的连接
//即p和q的根节点相同
continue;
}
else
{
//不相等则说明是新连接
id[i] = j;
//因为p-q是新对,所以输出这个对
printf("New pair: %d-%d\n", p, q);
}
}
return 0;
}
通过两个for循环,去查找数对p和q的根节点,并合并该节点。
通俗理解:假设原始数组为a[10] = 0 1 2 3 4 5 6 7 8 9 , 数组元素的值,就表示这些对象(电脑)的上一个节点。初始情况是0~9各不相同。就表示大家的上一个节点都是自己本身,,都不一样,当前不存在连接。
当第一次输入1-2时,则去比较对象1和对象2的根节点是否相同,发现不相同(根节点不同)说明是新建连接。之后并把对象2的根节点值(2)赋值给对象1,表示对象1现在的根节点是对象2。则当前的数组为a[10] = 0 2 2 3 4 5 6 7 8 9
当第二次输入2-3时,对象2对象3的根节点也不相同,为新连接,与上面的操作相同。但是与快速查找法不同的是,现在只赋值对象2的根节点,而不去赋值对象1的根节点。则当前的数组为a[10] = 0 2 3 3 4 5 6 7 8 9
当第三次输入1-3时,for循环语句
for (i = p; i != id[i]; i = id[i]);
,会循环向上查询上一个节点,直到查询到根节点(最上面的节点)。对象1的上一个节点(对象1的值)是2,然后对象2的上一个节点是3,此时对象3就是对象1的根节点了,退出循环。查找对象3的根节点,就很简单就是他本身。那么1和3具有相同的根节点,则说明是已有连接。此时的数组数据不变。
当第四次输入3-9时,数组为a[10] = 0 2 3 9 4 5 6 7 8 9。
当第五次输入1-9时,则原本对象1的根节点对象3,又有了新的根节点对象9,则1-9仍然是已有连接。通过这样的方式,还可以变相输出连接顺序(代码贴在1.2.2.1)。
快速合并法是快速查找法的一个优化算法,因为对于每个输入它并不是总要遍历整个数组。它去掉了快速查找算法的主要局限性(对N个对象执行M次合并操作,程序至少需要M*N条指令)。
1.2.2.1 快速合并法基础上 输出连接顺序
#include <stdio.h>
#define N (10)
int main()
{
int i, p, q, j;
int id[N];
int sn[N], t;
//初始化对象集合中元素的初始值
for (i = 0; i < N; i++) id[i] = i;
//循环读入整数对
while (scanf("%d-%d", &p, &q) == 2)
{
for (t=0; t<N; t++) sn[t]=0;
t=0;
//从位置P读取值,即读取p的根节点
for (i = p; i != id[i]; i = id[i])
{
sn[t++] = i;
}
for (j = q; j != id[j]; j = id[j]);
if (i == j)
{
sn[t] = j;
printf("Old pair: ");
for(t=0; sn[t]!=0; t++)
{
printf("%d-", sn[t]);
}
printf("\n");
//i、j 位置对象的值相等则是已存在的连接
//即p和q的根节点相同
continue;
}
else
{
//不相等则说明是新连接
id[i] = j;
//因为p-q是新对,所以输出这个对
printf("New pair: %d-%d\n", p, q);
}
}
return 0;
运行结果