算法导论求n个点的最小距离
在中文算法导论649页算法:
0:把所有的点按照横坐标排序
1:用一条竖直的线L将所有的点分成两等份
2:递归算出左半部门的这段两点距离d1,右半部门的这段两点距离d2,取d=min(d1,d2) 3:算出"1个在左半部分,另外1个在右半部分"这样的点对的最短距离d3
4:结果=min(d1,d2,d3)
关键就是这第3步貌似这需要n^2的时间,把左边每1个点以及右面每1个点都相比较一下,其实奥秘就在这里。
首先,两边的点,与分割线L的距离超过d的,都可以扔掉了,其次,纵然两个点P1,P2(不妨令P1在左边,P2在右面)与分割线L的距离(程度距离)都小于d,如果它们的纵坐标之差大于d,也没戏了。
就是这两点使得搜索范围大大减小:对左半部分的,与L的距离在d之内的,每1个P1来讲:右半部分内,切合以上两个条件的点P2至多只有六个!
原因就是:
d是两个半平面各自内,任意两点的最小距离,因此在同一个半平面内,任何两点距离都不有可能超过d。
咱们又要求P1以及P2的程度距离不能超过d,垂直距离也不能超过d,在这个d*2d 的小方块内,至多只能放下六个距离不小于d的点。
因此,第3步总的比较距离的回数不超过n*6
第3步的具体做法是:
3.1删除所有到L的距离大于d的点O(n)
3.2把右半平面的点按照纵坐标y排序O(nlogn)
3.3对左半平面内的每个点P1,找出右半平面内纵坐标与P1的纵坐标的差在d以内的点P2,计算距离取最小值,算出d3 O(n*6)=O(n)
改进:咱们对3.2这个排序的O(nlogn)不太满意.
既然全部算法是递归的,咱们可以哄骗第2步的子递归中已经排好序的序列,在第3.2部合并这两个子列,这样3.2的复杂度变成了O(n)。
这样,全般算法就是O(nlogn)的。
代码如下:VC6.0下编译通过
#include"stdafx.h"
#include stdio.h
#include stdlib.h
#include math.h
#define MAX 10000
typedef struct point{
int x,y;
}POINT;
double delta=MAX;
int totalnum;
POINT mem_p1,mem_p2;
int cmx(const void*a,const void*b)//比较x坐标
{
return((POINT*)a)->x-((POINT*)b)->x;
}
int cmy(const void*a,const void*b)//比较y坐标
{
return((POINT*)a)->y-((POINT*)b)->y;
}
double min(double p1,double p2)
{
return p1 p2?p2:p1;
}
double dist(POINT s1,POINT s2)//求两点的距离
{
double dx=s1.x-s2.x;
double dy=s1.y-s2.y;
return sqrt(dx*dx+dy*dy);
}
void rec_cl_pair(POINT a,int i,int j)
{
double temp,xx;
int k;
if(j-i 3)//小于或等于三个点的时辰可以直接求得最小距离{
qsort(a+i,j-i+1,sizeof(a[0]),cmy);//按Y坐标自小到大排列xx=dist(a[i],a[i+1]);//两个点的距离
if(j-i= =1) //只有两个点的时侯直接返回
{
if(xx< delta)
{
mem_p1=a[i];
mem_p2=a[i+1];
delta=xx;
}
return;
}
double t=dist(a[i+1],a[i+2]);//有三个点的环境
if(t { mem_p1=a[i+1]; mem_p2=a[i+2]; delta=t; } t=dist(a[i],a[i+2]); if(t { mem_p1=a[i]; mem_p2=a[i+2]; delta=t; } return; } k=(i+j)/2; double middle=a[k].x;//中线点 rec_cl_pair(a,i,k);//求左边点的最小距离 rec_cl_pair(a,k+1,j);//求右面点的最小距离 int t=0; POINT v[100]; for(k=i;k<=j;k++) { if(a[k].x> middle-delta&&a[k].x< middle+delta) { t++; v[t]=a[k];//存入离中线距离小于delta的点 } }//t-1为残剩点的个数 qsort(v+1,t,sizeof(v[1]),cmy);//以Y坐标的巨细进行排序 for(k=1;k< t;k++) { for(int s=k+1;s { temp=dist(v[k],v[s]); if(temp< delta) { delta=temp; mem_p1=v[k]; mem_p2=v[s]; } } } } void close_pair(POINT a) { int n=totalnum; qsort(a+1,n,sizeof(a[1]),cmx);//a接收的是a+1地址,按X坐标自小到大排列rec_cl_pair(a,1,n); } void main(int argc,char*argv) { POINT*a; scanf("%d",&totalnum) ;//输入点的总数 a=(POINT*)malloc(sizeof(point)*(totalnum+1));//多申请1个内存空间a[0]不消 for(int i=1;i=totalnum;i++) scanf("%d%d",&a[i].x,&a[i].y);//输入点的X以及Y坐标 close_pair(a); printf("这段点对的距离为:%.3f\n",delta);//地址从a+1开始 printf("最短距离的两个点为:\n(%d,%d)\n(%d,%d)\n",mem_p1.x,mem_p1.y,mem_p2.x,mem_p2.y); } Dijkstra单点对全部顶点最短路径算法 /* infinity 无穷 vertex顶点 distance 距离 matrix 建模 start 开始 point 点 goal 目标 */ /*解题大致掌握: 如找出一个起点k,n个终点,此时本程序就是解决找出从起k到1~n各个终点的最短路径。 1> 定义path_cost数组[E_N条边][3] 对应的起点,终点,两点间距离,该数组作用用来记录当前图的数据信息。 2> 定义graph_matrix数组[起点值][终点值]=路径长度,该数组作用用来建成二维表方便操作顶点;该数组下标范围为V_N即多少个顶点。 3> 定义distance数组[顶点值]=最短路径长度,作用是记录最短路径长度。 4> 定义goal[V_N+1]={0};用来记录该顶点是否被访过,1或0表示。 5> 以上声明完毕,将其path_cost信息注入到graph_matrix。 6> 根据起点k,在graph_matrix中找出起点为k的值并用distance记录,即 graph_matrix[k][i],i为循环量。 7> 将当前原始点k到k的最短路径长度赋为0,goal[k]=1即已选择过。 重点算法: 1> 找出与k为起点的所有终点的最短路径short_distance及终点值short_vertex(顶点);条件是与之指向的终点还没选择过。 2> 此时将此点标记已选择过。 3> 找出当前最短路径+以前顶点做起点的所有终点的最短路径,条件是与之当前终点没被选择过。 重复123步。 */ /*注意:此图为有向图*/ #include #define INFINITE 9999 //无穷,或是不能到达的顶点 #define V_N 6 //6个顶点 #define E_N 10 //10条边 #define BEGIN 1 //从BEGIN起点 #define END 6 //访问的有END个终点 int graph_matrix[V_N+1][V_N+1]; //下标从1开始,图数组 int distance[V_N+1]; //下标从1开始,路径长度 void add_graph_matrix(int path_cost[]) //图建立-邻接矩阵 { int start_point; //边起点 int end_point; //边终点 for (int i=1; i<=V_N+1; i++) for (int j=1; j<=V_N+1; j++) if (i==j) graph_matrix[i][j]=0; //原顶点路径长度为0 else graph_matrix[i][j]=INFINITE; //初始非原顶点i至j路径长度为无穷大for (i=0; i { start_point=path_cost[i*3]; //读取第i条边中的起点 end_point=path_cost[i*3+1]; //读取第i条边中的终点 graph_matrix[start_point][end_point]=path_cost[i*3+2]; //模型图[起点][终点]=路径长度值 } } void print_graph_matrix() //输出图模型 { for (int i=1; i<=V_N; i++) { printf("Vertex(%d):",i); for (int j=1; j<=V_N; j++) if (graph_matrix[i][j]==INFINITE) printf("\tX"); else printf("\t%d",graph_matrix[i][j]); putchar('\n'); } } void short_path(int vertex_i, int vertex_n) { int short_vertex=vertex_i; //记录最短距离的顶点 int short_distance; //记录最短距离 int goal[V_N+1]={0}; //记录顶点是否被选取 for (int i=1; i<=vertex_n; i++) distance[i]=graph_matrix[vertex_i][i]; //找出所有与vertex_i连接的终点其值边长 goal[vertex_i]=1; //原起点被选择 distance[vertex_i]=0; //路径长度在原点为0 for (i=1; i<=vertex_n; i++) { short_distance=INFINITE; for (int j=1; j<=vertex_n; j++) //找出最短距离的顶点 if (goal[j]==0 && short_distance>distance[j]) {//当该顶点没有被选择并且当前顶点的路径长度小于当前已记录路径长度 short_distance=distance[j]; //此时记录新的最短路径长度 short_vertex=j; //记录这个的顶点 } //此时找出了与vertex_i连接的最短路径及顶点 goal[short_vertex]=1; //记录这个最短路径的顶点已被选择 for (j=1; j<=vertex_n; j++) { /*当第j个顶点没有被选择并且当前最短路径加上与当前连接的终顶点路,使之找到当前的起与终点j的最小路径*/ if(goal[j]==0&&distance[short_vertex]+graph_matrix[short_vertex][j] //记录vertex_i到这个新顶点的路径长度 } } } void main() { int path_cost[E_N][3]={{1,2,22},{1,4,80},{1,6,99},{2,3,21},{4,5,10},{5,6,25}, {3,6,14},{3,5,95},{3,4,30},{6,4,13}}; //边的数据{起点,终点,路径长度} add_graph_matrix(&path_cost[0][0]); printf("Graph Matrix:\n"); printf("Vertex[i]:\tV(1)\tV(2)\tV(3)\tV(4)\tV(5)\tV(6)\n"); print_graph_matrix(); short_path(BEGIN,END+1); putchar('\n'); for (int i=1; i<=V_N; i++) printf("Vertex(%d) from all Vertex(%d) short path is: %d\n",BEGIN,i,distance[i]); putchar('\n'); } Dijkstra算法原理主要就是已知源节点(v)和n个节点间代价函数(有向网络矩阵cost),通过不断将节点加入到一个节点子集S 中,使得经过加入S后的各节点的路径代价是最小的,直至S节点包含了所有的n个节点停止。(具体算法阐明网上很多资料)。闲话少说,直接附程序吧~ /* readme:自述文件 first,you need to input the node number, the cost matrix and the source node;首先你必须输入矩阵节点的节点数; then the program will compute the best path.程序计算最佳路径 finally,the program will output the lowest distance to the destination node, the pre-node and show the best path. */ #i nclude #i nclude //Dijkstra算法实现函数 void Dijkstra(int n,int v,int dist[],int prev[],int **cost) { int i; int j; int maxint = 65535;//定义一个最大的数值,作为不相连的两个节点的代价权值 int *s ;//定义具有最短路径的节点子集s s = (int *)malloc(sizeof(int) * n); //初始化最小路径代价和前一跳节点值 for (i = 1; i <= n; i++) { dist[i] = cost[v][i]; s[i] = 0; if (dist[i] == maxint) { prev[i] = 0; } else { prev[i] = v; } } dist[v] = 0; s[v] = 1;//源节点作为最初的s子集 for (i = 1; i < n; i++) { int temp = maxint; int u = v; //加入具有最小代价的邻居节点到s子集 for (j = 1; j <= n; j++) { if ((!s[j]) && (dist[j] < temp)) { u = j; temp = dist[j]; } } s[u] = 1; //计算加入新的节点后,更新路径使得其产生代价最短for (j = 1; j <= n; j++) { if ((!s[j]) && (cost[u][j] < maxint)) { int newdist = dist[u] + cost[u][j]; if (newdist < dist[j]) { dist[j] = newdist; prev[j] = u; } } } } } //展示最佳路径函数 void ShowPath(int n,int v,int u,int *dist,int *prev) { int j = 0; int w = u; int count = 0; int *way ; way=(int *)malloc(sizeof(int)*(n+1)); //回溯路径 while (w != v) { count++; way[count] = prev[w]; w = prev[w]; } //输出路径 printf("the best path is:\n"); for (j = count; j >= 1; j--) { printf("%d -> ",way[j]); } printf("%d\n",u); } //主函数,主要做输入输出工作 void main() { int i,j,t; int n,v,u; int **cost;//代价矩阵 int *dist;//最短路径代价 int *prev;//前一跳节点空间 printf("please input the node number: "); scanf("%d",&n); printf("please input the cost status:\n"); cost=(int **)malloc(sizeof(int)*(n+1)); for (i = 1; i <= n; i++) { cost[i]=(int *)malloc(sizeof(int)*(n+1)); } //输入代价矩阵 for (j = 1; j <= n; j++) { for (t = 1; t <= n; t++) { scanf("%d",&cost[j][t]); } } dist = (int *)malloc(sizeof(int)*n); prev = (int *)malloc(sizeof(int)*n); printf("please input the source node: "); scanf("%d",&v); //调用dijkstra算法 Dijkstra(n, v, dist, prev, cost); printf("*****************************\n"); printf("have confirm the best path\n"); printf("*****************************\n"); for(i = 1; i <= n ; i++) { if(i!=v) { printf("the distance cost from node %d to node %d is %d\n",v,i,dist[i]); printf("the pre-node of node %d is node %d \n",i,prev[i]); ShowPath(n,v,i, dist, prev); } } } Dijkstra算法 Dijkstra算法是典型最短路算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。 Dijkstra算法是很有代表性的最短路算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。 Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表方式,Drew为了和下面要介绍的A* 算法和D* 算法表述一致,这里均采用OPEN,CLOSE表的方式。 其采用的是贪心法的算法策略 大概过程: 创建两个表,OPEN, CLOSE。 OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。 1.访问路网中距离起始点最近且没有被检查过的点,把这个点放入OPEN组中等待检查。 2.从OPEN表中找出距起始点最近的点,找出这个点的所有子节点,把这个点放到CLOSE表中。 3.遍历考察这个点的子节点。求出这些子节点距起始点的距离值,放子节点到OPEN表中。 4.重复第2和第3步,直到OPEN表为空,或找到目标点。4.重复第2和第3步,直到OPEN表为空,或找到目标点。4.重复第2和第3步,直到OPEN表为空,或找到目标点。4.重复第2和第3步,直到OPEN表为空,或找到目标点。 #include #define MaxNum 765432100 using namespace std; ifstream fin("Dijkstra.in"); ofstream fout("Dijkstra.out"); int Map[501][501]; bool is_arrived[501]; int Dist[501],From[501],Stack[501]; int p,q,k,Path,Source,Vertex,Temp,SetCard; int FindMin() { int p,Temp=0,Minm=MaxNum; for(p=1;p<=Vertex;p++) if ((Dist[p] { Temp=p; } return Temp; } int main() { memset(is_arrived,0,sizeof(is_arrived)); fin >> Source >> Vertex; for(p=1;p<=Vertex;p++) for(q=1;q<=Vertex;q++) { fin >> Map[p][q]; if (Map[p][q]==0) Map[p][q]=MaxNum; } for(p=1;p<=Vertex;p++) { Dist[p]=Map[Source][p]; if (Dist[p]!=MaxNum) From[p]=Source; else From[p]=p; } is_arrived[Source]=true; SetCard=1; do { Temp=FindMin(); if (Temp!=0) { SetCard=SetCard+1; is_arrived[Temp]=true; for(p=1;p<=Vertex;p++) if ((Dist[p]>Dist[Temp]+Map[Temp][p])&&(!is_arrived[p])) { Dist[p]=Dist[Temp]+Map[Temp][p]; From[p]=Temp; } } else break; } while (SetCard!=Vertex); for(p=1;p<=Vertex;p++) { fout << "========================\n"; fout << "Source:" << Source << "\n Target:" << p << '\n'; if (Dist[p]==MaxNum) { fout << "Distance:" << "Infinity\n"; fout << "Path:No Way!"; } else { fout << "Distance:" << Dist[p] << '\n'; k=1; Path=p; while (From[Path]!=Path) { Stack[k]=Path; Path=From[Path]; k=k+1; } fout << "Path:" << Source; for(q=k-1;q>=1;q--) fout << "-->" << Stack[q]; } fout << "\n========================\n\n"; } fin.close(); fout.close(); return 0; } Sample Input 2 7 00 20 50 30 00 00 00 20 00 25 00 00 70 00 50 25 00 40 25 50 00 30 00 40 00 55 00 00 00 00 25 55 00 10 00 00 70 50 00 10 00 00 00 00 00 00 00 00 00 Sample Output ======================== Source:2 Target:1 Path:2-->1 ======================== ======================== Source:2 Target:3 Distance:25 Path:2-->3 ======================== ======================== Source:2 Target:4 Distance:50 Path:2-->1-->4 ======================== ======================== Source:2 Target:5 Distance:50 Path:2-->3-->5 ======================== ======================== Source:2 Target:6 Distance:60 Path:2-->3-->5-->6 ======================== ======================== Source:2 Target:7 Distance:Infinity Path:No Way! ======================== 示例程序及相关子程序: void Dijkstra(int n,int[] Distance,int[] iPath) { int MinDis,u; int i,j; //从邻接矩阵复制第n个顶点可以走出的路线,就是复制第n行到Distance[] for(i=0;i { Distance=Arc[n,i]; Visited=0; }//第n个顶点被访问,因为第n个顶点是开始点 //找到该顶点能到其他顶点的路线、并且不是开始的顶点n、以前也没走过。 //相当于寻找u点,这个点不是开始点n for(i=0;i { u=0; MinDis=No; for(j=0;j if(Visited[j] == 0&&(Distance[j] { MinDis=Distance[j]; u=j; } //如范例P1871图G6,Distance=[No,No,10,No,30,100],第一次找就是V2,所以u=2 //找完了,MinDis等于不连接,则返回。这种情况类似V5。 if(MinDis==No) return ; //确立第u个顶点将被使用,相当于Arc[v,u]+Arc[u,w]中的第u顶点。 Visited[u]=1; //寻找第u个顶点到其他所有顶点的最小路,实际就是找Arc[u,j]、j取值在[0,VerNum]。 //如果有Arc[i,u]+Arc[u,j] //实际中,因为Distance[]是要的结果,对于起始点确定的情况下,就是: //如果(Distance[u] + Arc[u,j]) <= Distance[j] 则: //Distance[j] = Distance[u] + Arc[u, j]; //而iPath[]保存了u点的编号; //同理:对新找出的路线,要设置Visited[j]=0,以后再找其他路,这个路可能别利用到。例如V3 for(j=0;j if(Visited[j]==0&&Arc[u,j] { if ((Distance[u] + Arc[u,j]) <= Distance[j]) { Distance[j] = Distance[u] + Arc[u, j]; Visited[j]=0; iPath[j] = u; } } } } //辅助函数 void Prim() { int i,m,n=0; for(i=0;i { Visited=0; T=new TreeNode(); T.Text =V; } Visited[n]++; listBox1.Items.Add (V[n]); while(Visit()>0) { if((m=MinAdjNode(n))!=-1) { T[n].Nodes.Add(T[m]); n=m; Visited[n]++; } else { n=MinNode(0); if(n>0) T[Min2].Nodes.Add(T[Min1]); Visited[n]++; } listBox1.Items.Add (V[n]); } treeView1.Nodes.Add(T[0]); } void TopoSort() { int i,n; listBox1.Items.Clear(); Stack S=new Stack(); for(i=0;i Visited=0; for(i=VerNum-1;i>=0;i--) if(InDegree(i)==0) { S.Push(i); Visited++; } while(S.Count!=0) { n=(int )S.Pop(); listBox1.Items.Add (V[n]); ClearLink(n); for(i=VerNum-1;i>=0;i--) if(Visited==0&&InDegree(i)==0) { S.Push(i); Visited++; } } } void AOE Trave(int n,TreeNode TR,int w) { int i,w0; if(OutDegree(n)==0) return; for(i=0;i if((w0=Arc[n,i])!=0) { listBox1.Items.Add (V+"\t"+(w+w0).ToString()+"\t"+i.ToString()+"\t"+n.ToString()); TreeNode T1=new TreeNode(); T1.Text =V+" [W="+(w+w0).ToString()+"]"; TR.Nodes.Add(T1); AOETrave(i,T1,w+w0); } } void AOE() { int i,w=0,m=1; TreeNode T1=new TreeNode(); for(i=0;i { Visited=0; } T1.Text =V[0]; listBox1.Items.Add ("双亲表示法显示这个生成树:"); listBox1.Items.Add ("V\tW\tID\tPID"); for(i=0;i { if((w=Arc[0,i])!=0) { listBox1.Items.Add (V+"\t"+w.ToString()+"\t"+i.ToString()+"\t0"); TreeNode T2=new TreeNode(); T2.Text=V+" [W="+w.ToString()+"]"; AOETrave(i,T2,w); T1.Nodes.Add (T2); listBox1.Items.Add("\t\t树"+m.ToString()); m++; } } treeView1.Nodes.Clear(); treeView1.Nodes.Add (T1); } int IsZero() { int i;