线性表
1.顺序表L删除所有值为x的元素,时间复杂度O(n),空间复杂度O(1)
void delx(SqlList* L,ElemType x){
i=0,j=0;
while(i<L->last){
if(L->elem[i]!=x){
L->elem[j]=L->elem[i];
i++,j++;
}else{
i++;
}
}
L->last=j-1;
}
2.带头结点单链表就地逆置
void ReserveList(LinkList L){
p=L->next;
L->next=NULL;
while(p){
q=p->next; //保留当前处理节点的下一个节点
p->next=L->next;
L-next=p;
p=q;
}
}
3.带头结点单链表L,以表中第一个元素值为标准,将表中所有值小于第一个元素放在第一个元素之前,大于放在之后
void changeList(LinkList L){
if(L->next==NULL){
return;
}
p1=L->next;
pre=p1; //pre始终指向正在处理元素的前一个位置
p=p1->next; //p为当前处理节点
while(p){
q=p->next; //保存当前处理节点的下一个节点
if(p->data>=p1->data){
pre=p;
p=q;
}else{
pre->next=p->next;
p->next=L->next;
L->next=p;
p=q;
}
}
}
栈与队列
1.括号匹配算法
void BracketMatch(char* str){ //str为输入字符
Stack S;
int i;char ch;
InitStack(&S);
for(i=0;str[i]!='\0';i++){
switch(str[i]){
case '(':
case '[':
case '{':
Push($S,str[i]);
break;
case ')':
case ']':
case '}':
if(isEmpty(S)){
printf("右括号多余");
}else{
GetTop(S,&ch);
if(Match(ch,str[i])){
Pop(&S,&ch);
}else{
printf("左右括号不同类");
}
}
break;
}
}
if(IsEmpty(S)){
printf("括号匹配");
}else{
printf("左括号多余");
}
}
2.无括号算术表达式处理
//读入一个简单表达式计算值,OPTR为运算符栈,OVS为运算数栈
int ExpEvaluation(){
InitStack($OPTR);
InitStack(&OVS);
Push($OPTR,'#');
ch=getchar();
while(ch!='#'||GetTop(OPTR)!='#'){
if(!In(ch,OPSet)){ //不是操作符 是操作数
n=GetNumber(ch);
Push(&OVS,n);
ch=getchar();
}else{
switch(Compare(ch,GetTop(&OPTR))){
case '>':Push(&OPTR,ch);ch=getchar();break;
case '=':
case '<':Pop(&OPTR,&op);Pop(&OVS,&b);
Pop(&OVS,&a);v=Excute(a,op,b);
Push(&OVS,v);break;
}
}
}
v=GetTop(&OVS);
return v;
}
3.汉诺塔递归算法
//将X上从上到下编号1至n,直径由小到大叠放的n个圆盘,按规则借助Y移动到Z上
void hannoi(int n,char X,char Y,cahr Z){
if(n==1){
move(X,1,Z);
}else{
hannoi(n-1,X,Z,Y);
move(X,n,Z);
honnoi(n-1,Y,X,Z);
}
}
4.打印杨辉三角形前n行元素
void YangHuiTriangle(){
SeqQueue Q;
InitQueue(&Q);
EnterQueue(&Q,1); //第1行元素入队
for(n=2;n<=N;n++){
EnterQueue(&Q,1); //第n行第一个元素入队
for(i=1;i<=n-2;i++){//利用n-1行元素产生第n行中间n-2个元素
DeleteQueue(&Q,&tmp);
printf(tmp);
GetHead(Q,&x);
tmp+=x;
EnterQueue(&Q,tmp);
}
DeleteQueue(&Q,&x);
printf(x);
EnterQueue(&Q,1); //第n行最后一个元素入队
}
while(!IsEmpty(Q)){
DeleteQueue(&Q,&x);
printf(x);
}
}
树与二叉树
一、二叉树的遍历与线索化
1.输出二叉树中的节点
void PreOrder(BiTree root){
if(root!=NULL){
printf(root->data);
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
2.输出二叉树中的叶子节点
void PreOrder(BiTree root){
if(root!=NULL){
if(root->LChild==NULL&&root->RChild==NULL){
printf(root->data);
}
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
3.统计叶子节点的数目
方法一:后序遍历实现
//leafCount为保存叶子节点数目的全局变量 初始值为0
void leaf(BiTree root){
if(root!=NULL){
leaf(root->LChild);
leaf(root->RChild);
if(root->LChild==NULL&&root->RChild==NULL){
leafCount++;
}
}
}
方法二:分治算法
void leaf(BiTree root){
int leafCount;
if(root==NULL){
leafCount=0;
}else if(root->LChild==NULL&&root->RChild==NULL){
leafCount=1;
}else{
leafCount=leaf(root->LChild)+leaf(root->RChild);
}
return leafCount;
}
4.拓展先序序列创建二叉树
void CreateBiTree(BiTree* bt){
char ch;
ch=getchar();
if(ch=='.'){
*bt=NULL;
}else{
*bt=(BiTree)malloc(sizeof(BiTNode));
(*bt)->data=ch;
CreateBiTree(&((*bt)->LChild));
CreateBiTree(&((*bt)->RChild));
}
}
5.求二叉树高度
分治法
int PostTreeDepth(BiTree bt){
int hr,hl,max;
if(bt!=NULL){
hl=PostTreeDepth(bt->LChild); //求左子树的深度
hr=PostTreeDepth(bt->RChild); //求右子树的深度
max=hr>hl?hr:hl;
return max+1; //加树根的高度
}else{
return 0;
}
}
先序遍历实现
//depth为全局变量 为当前求得的最大层次
void PreTreeDepth(BiTree bt,int h){
if(bt!=NULL){
if(h>depth){
depth=h;
}
PreTreeDepth(bt->LChild,h+1);
PreTreeDepth(bt->RChild,h+1);
}
}
6.按横向树形显示二叉树
//二叉树的横向显示是竖向显示的逆时针90度旋转,分析可知输出的节点序列正好为逆中序顺序
void PrintTree(BiTree bt,int nLayer){
if(bt==NULL){
return;
}
PrintTree(bt->RChild,nLayer+1);
for(i=0;i<nLayer;i++){
printf(" ");
}
printf(bt->data);
PrintTree(bt->LChild,nLayer+1);
}
二、基于栈的递归消除
1.中序遍历二叉树的非递归算法
void InOrder(BiTree root){
InitStack(&S);
p=root;
while(p!=NULL||!IsEmpty(S)){
if(p!=NULL){
Push(&S,p);
p=p->LChild;
}else{
Pop(&S,&p);
Visit(p->data);
p=p->RChild;
}
}
}
2.后序遍历二叉树的非递归算法
void PostOrder(BiTree root){
InitStack(&S);
p=root;
while(p!=NULL||!IsEmpty(S)){
if(p!=NULL){
Push(&S,p);
p=p->LChild;
}else{
GetTop(s,&p);
if(p->RChild==NULL||p->RChild==q){ //右子树为空或访问过
visit(p->data);
q=p; //将访问过的节点保存在q中,作为处理下一个节点的前驱
Pop(&S,&p);
p=NULL; //p置空方可继续退层,否则会重复进入刚访问的左子树
}else{
p=p->RChild;
}
}
}
}
三、线索二叉树
1.建立中序序列
//pre始终指向刚访问过的节点 初值为NULL
void InThread(BiTree root){
if(root!=NULL){
InThread(root->LChild);
if(root->LChild==NULL){
root->LChild=pre;
root->Ltag=1;
}
if(pre!=NULL&&pre->RChild==NULL){
pre->RChild=root;
pre->Rtag=1;
}
InThread(root->RChild);
}
}
2.在中序线索树中寻找节点的前驱节点
BiTNode* InPre(BiTNode* p){
if(p->Ltag==1){
pre=p->LChild;
}else{
//在p的左子树寻找 最右下端 节点
for(q=p->LChild;q->Rtag==0;q=q->RChild);
pre=q;
}
return pre;
}
3.在中序线索树寻找节点的后继节点
BiTNode* InNext(BiTNode* p){
if(p->RChild==1){
next=p->RChild;
}else{
//在p右子树寻找 最左下端 节点
for(q=p->RChild;q->Ltag==0;q=q->LChild);
next=q;
}
return next;
}
4.在中序线索树中求中序遍历的第一个节点
BiTNode* InFirst(BiTree bt){
p=bt;
if(!p) return NULL;
while(p->Ltag==0){
p=p->LChild;
}
return p;
}
5.遍历中序二叉线索树
void TInOrder(BiTree bt){
p=InFirst(bt);
while(p){
visit(p->data);
p=InNext(p);
}
}
总结与提高
1.二叉树相似性判断
//两颗二叉树都为空或者左右子树相似
int like(BiTree t1,BiTree t2){
int like1,like2;
if(t1==NULL&&t2==NULL){
return 1;
}else if(t1==NULL||t2==NULL){
return 0;
}else{
like1=like(t1->left,t2->left);
like2=like(t1->right,t2->right);
return like1&&like2;
}
}
2.求二叉树根节点到r节点之间的路径
这里的路径包括r节点的所有祖先,采用后序遍历的方法
void path(BiTree root,BiTNode* r){
InitStack(&S);
q=NULL; //q保存刚遍历过的节点,初始为空
p=root;
while(p!=NULL||top!=0){
while(p!=NULL){
Push(&S,p);
p=p->LChild;
}
if(!IsEmpty(S)){
p=GetTop(S);
if(p->RChild==NULL||p->RChild==q){
//===================================
if(p==r){
while(!IsEmpty){
tmp=Pop(&S);
printf(tmp);
}
return;
}else{
q=p;
Pop(&S);
p=NULL;
}
//=====================================
}else{
p=p->RChild;
}
}
}
}
3.层次遍历二叉树
队列实现
int LayerOrder(InTree bt){
SeqQueue Q;
BiTNode* p;
InitQueue(&Q);
if(bt==NULL){
return ERROR;
}
EnterQueue(&Q,bt);
while(!IsEmpty(Q)){
DeleteQueue(&Q,&p);
visit(p->data);
if(p->LChild){
EnterQueue(&Q,p->LChild);
}
if(p->RChild){
EnterQueue(&Q,p->RChild);
}
}
return OK;
}
图
一、图的存储结构
#define MAX_VERTEX_NUM 20
#define INFINTY 32768
//DG有向图 DN有向网 UDG无向图 UDN无向网
typedef enum{DG,DN,UDG,UDN} GraphKind;
typedef char VertexData;
邻接矩阵表示法
typedef struct ArcNode{
AdjType adj;
OtherInfo info;
}ArcNode;
typedef struct{
VertexData vertex[MAX_VERTEX_NUM];
ArcNode arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum,arcnum;
GraphKind kind;
}AdjMatrix;
邻接表 -->边稀疏时比邻接矩阵节省空间
typedef struct ArcNode{
int adjvex;
struct ArcNode* nextarc;
OtherInfo info;
}ArcNode;
typedef struct VertexNode{
VertexData data;
ArcNode* firstarc;
}VertexNode;
typedef struct{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum,arcnum;
GraphKind kind;
}AdjList;
十字链表 -->邻接表与逆邻接表结合
typedef struct ArcNode{
int tailvex,headvex; //顶点在图中位置
struct ArcNode* hlink,tlink;
}ArcNode;
typedef struct VertexNode{
VertexData data;
ArcNode* finstin,* firstout;
}VertexNode;
typedef struct{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum,arcnum;
GraphKind kind;
}OrthList;
邻接多重表 -->提供更为方便的边处理信息
typedef struct EdgeNode{
int mark,ivex,jvex;
struct EdgeNode* ilink,* jlink;
}EdgeNode;
typedef struct{
VertexData data;
EdgeNode* firstedge;
}VertexNode;
typedef struct{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum,arcnum;
GraphKind kind;
}AdjMultiList;
二、图的遍历
图的联通性问题、拓扑排序和关键路径等问题
访问标志数组作用:保证图中各顶点在遍历过程中访问且仅访问一次。
1.深度优先搜索
void TraverseGraph(Graph g){
for(vi=0;vi<g.vexnum;vi++){
visited[vi]=False;
}
//循环调用深度优先遍历连通子图,若g是联通图,此调用只执行一次
for(vi=0;vi<g.vexnum;vi++){
if(!visited[vi]){
DFS(g,vi);
}
}
}
void DFS(Graph g,int v0){
visit(v0);
visited[v0]=True;
w=FirstAdjVertex(g,v0);
while(w!=-1){ //邻接点存在
if(!visited[w]){
DFS(g,w);
}
w=NextAdjVertex(g,v0,w);
}
}
邻接矩阵DFS
void DFS(AdjMatrix g,int v0){
visit(v0);visited[v0]=True;
for(vi=0;vi<g.vexnum;vi++){
if(!visited[vi]&&g.arcs[v0][vi].adj==1){
DFS(g,vi);
}
}
}
邻接表DFS
void DFS(AdjList g,int v0){
visit(v0);visited[v0]=True;
p=g.vertex[v0].firstarc;
while(p!=NULL){
if(!visited[p->adjvex]){
DFS(g,p->adjvex);
}
p=p->nextarc;
}
}
栈实现非递归DFS
void DFS(Graph g,int v0){
InitStack(&S);
Push(&S,v0);
while(!IsEmpty(s)){
Pop(&S,&v);
if(!visited[v]){
visit(v);
visited[v]=True;
w=FirstAdjVertex(g,v);
while(w!=-1){
if(!visited[w]){
Push(&S,w);
}
w=NextAdjVertex(g,v,w);
}
}
}
}
2.广度优先搜索
队列实现非递归BFS
void BFS(Graph g,int v0){
visit(v0);visited[v0]=True;
InitQueue(&Q);
EnterQueue(&Q,v0);
while(!ISEmpty(Q)){
DeleteQueue(&Q,&v);
w=FirstAdiVertex(g,v);
while(w!=-1){
if(!visited[w]){
visite(w);
visited[w]=True;
EnterQueue(&Q,w);
}
w=NextAdjVertex(g,v,w);
}
}
}
三、图的应用
1.图的连通性问题
简单路径:路径中顶点均不相同
int pre[MAX_VERTEX_NUM]={-1}; //-1表示未被访问 pre记录的是vi-->vj时pre[j]=i
void Path(Graph g,int u,int v){
pre[u]=u;
DFS(G,u,v);
}
void DFS(Graph g,int u,int v){
if(u==v){
//找到终点,反向输出路径
print_path(pre,v);return;
}
w=FirstAdjVertx(g,u);
while(w!=-1){
if(!visited[w]){
pre[w]=u;
DFS(g,w,v);
}
w=NextAdjVertx(g,u,w);
}
}
MST:在一个连通图的所有生成树中,各边代价之和最小的那颗生成树
Prim算法
1.将连通网中的所有顶点分为两类(假设为 A 类和 B 类)。初始状态下,所有顶点位于 B 类
2.选择任意一个顶点,将其从 B 类移动到 A 类
3.将图中的所有顶点分为 A 类和 B 类,从 B 类的所有顶点出发,找出一条连接着 A 类中的某个顶点且权值最小的边,将此边连接着的 A 类中的顶点移动到 B 类
4.重复执行第 3 步,直至 B 类中的所有顶点全部移动到 A 类,恰好可以找到 N-1 条边
//辅助数组,0单元不用
struct{
int adjvex;
int lowcost;
}closedge[MAX_VERTEX_NUM+1];
//从顶点u出发,构造最小生成树,输出生成树每条边
MiniSpanTree_Prim(AdjMatrix gn,int u){
closedge[u].lowcost=0;
//初始化B类
for(i=1;i<=gn.vexnum;i++){
if(i!=u){
closedge[i].adjvex=u;
closedge[i].lowcost=gn.arcs[u][i].adj;
}
}
//需要找到n-1条边
for(e=1;e<=gn.vexnum-1;e++){
v=Minium(closedge);
u=closedge[v].adjvex;
printf(u,v);
//将顶点v并入A类
closedge[v].lowcost=0;
//更新B类
for(i=1;i<=gn.vexnum;i++){
if(gn.arcs[v][i].adj<closedge[i].lowcost){
closedge[i].lowcost=gn.arcs[v][i].adj;
closedge[i].adjvex=v;
}
}
}
}
kruskal算法 -->更适合于求边稀疏的网的最小生成树
将连通网中所有的边按照权值大小做升序排序,从权值最小的边开始选择,只要此边不和已选择的边一起构成环路,就可以选择它组成最小生成树。对于 N 个顶点的连通网,挑选出 N-1 条符合条件的边,这些边组成的生成树就是最小生成树。
2.有向无环图的应用
AOV网:Activity on vertex network
不能存在回路 拓扑序列不唯一
AOE网:Acvity on edge network
通俗来说,AOV网边无权值,AOE网边有权值
1.求出各个顶点的入度,将入度为0的节点入栈
2.当栈不为空的时候:
a.栈顶顶点i打印并出栈
b.将顶点i的每一个邻接点k入度减1,如果顶点k的入度变为0,将k入栈
int TopoSort(AdjList G){
Stack S;
int indegree[MAX_VERTEX_NUM];
ArcNode* p;
FindID(G,indegree); //求各顶点入度
InitStack(&S);
for(i=0;i<G.vexnum;i++){
if(indegree[i]==0){
Push(&S,i);
}
}
count=0;
while(!IsEmpty(S)){
Pop(&S,&i);
printf(G.vertex[i].data);
count++;
p=G.vertex[i].firstarc;
while(p!=NULL){
k=p->adjvex;
indegree[k]--;
if(indegree[k]==0){
Push(&S,k);
}
p=p->nextarc;
}
}
if(count<G.vexnum){
return ERROR; //该有向图有回路
}else{
return OK;
}
}
求入度算法
void FindID(AdjList G,int indegree[MAX_VERTEX_NUM]){
for(i=0;i<G.vexnum;i++){
indegree[i]=0;
}
for(i=0;i<G.vexnum;i++){
p=G.vertex[i].firstarc;
while(p!=NULL){
indegree[p->adjvex]++;
p=p->nextarc;
}
}
}
在AOE网中,所有活动都完成才能到达终点,因此完成整个工程所必须花费的时间(即最短工期)应该为源点到终点的最大路径长度。具有最大路径长度的路径称为关键路径。关键路径上的活动称为关键活动:
事件的最早发生时间:ve[k]
根据AOE网的性质,只有进入Vk的所有活动<Vj, Vk>都结束,Vk代表的事件才能发生,而活动<Vj, Vk>的最早结束时间为ve[j]+len<Vj, Vk>。所以,计算Vk的最早发生时间的方法为:
ve[0] = 0
ve[k] = max(ve[j] + len<Vj, Vk>)
事件的最迟发生时间:vl[k]
vl[k]是指在不推迟整个工期的前提下,事件Vk允许的最迟发生时间。根据AOE网的性质,只有顶点Vk代表的事件发生,从Vk出发的活动<Vk, Vj>才能开始,而活动<Vk, Vj>的最晚开始时间为vl[j] - len<Vk, Vj>。
活动的最早发生时间:ee[i]
ai由有向边<Vk, Vj>,根据AOE网的性质,只有顶点Vk代表的事件发生,活动ai才能开始,即活动ai的最早开始时间等于事件Vk的最早开始时间。
活动的最迟发生时间:el[i]
el[i]是指在不推迟真个工期的前提下,活动ai必须开始的最晚时间。若活动ai由有向边<Vk, Vj>表示,则ai的最晚开始时间要保证事件vj的最迟发生时间不拖后。
修改后的拓扑排序算法
int ve[MAX_VERTEX_NUM]; //每个顶点的最早发生时间
//G为有向网 T为返回逆拓扑序列的栈 并计算ve
int TopoOrder(AdjList G,Stack* T){
int indegree[MAX_VERTEX_NUM];
Stack S;
InitStack(&T);InitStack(&S);
FindID(G,indegree);
for(i=0;i<G.vexnum;i++){
if(indegree[i]==0){
Push(&S,i);
}
}
count=0;
//初始化最早发生时间
for(i=0;i<G.vexnum;i++){
ve[i]=0;
}
while(!IsEmpty(S)){
Pop(&S,&j);
Push(T,j); //按拓扑序列进栈
count++;
p=vertex[j].firstarc;
while(p!=NULL){
k=p->adjvex;
if(--indegree[k]==0){
Push(&S,k);
}
//按拓扑顺序计算事件ve
//ve[i]=Max{ve[k]+dut<k,i>}
if(ve[j]+p->Info.weight>ve[k]){
ve[k]=ve[j]+p->Info.weight;
}
p=p->nextarc;
}
}
if(count<G.vexnum){
return ERROR;
}else{
return OK;
}
}
关键路径算法
1.调用修改后的拓扑排序算法,求出每个事件的最早发生时间和逆拓扑排序栈T
2.将各顶点的最晚发生时间vl[i]初始化为汇点的最早发生时间
3.只要栈T不空,重复以下
a.栈顶顶点j出栈
b.对顶点j的每一个邻接点k,根据顶点k的最晚发生时间vl[k]和弧<j,k>的权值,更新节点j的最晚发生时间vl[j]
4.扫描每一条弧,计算最早发生时间ei和最晚发生时间li,如果ei等于li则输出该边
int CriticalPath(AdjList G){
int vl[MAX_VERTEX_NUM]; //每个顶点最迟发生时间
Stack S;
if(!TopoOrder(G,&T)){
return ERROR;
}
//将各顶点事件最迟发生时间初始化为汇点的最早发生时间
for(i=0;i<G.vexnum;i++){
vl[i]=ve[G.vexnum-1];
}
while(!IsEmpty(S)){
Pop(S,&j);
p=G.vertex[j].firstarc;
while(p!=NULL){
k=p->adjvex;
dut=p->weight;
//vl[i]=Min{vl[k]-dut<i,k>}
if(vl[k]-dut<vl[j]){
vl[j]=vl[k]-dut;
}
p=p->nextarc;
}
}
//求ei、li关键活动
for(j=0;j<G.vexnum;j++){
p=G.vertex[j].firstarc;
while(p!=NULL){
k=p->adjvex;
dut=p->Info.weight;
tag=(ei==li)?'*':' ';
printf(G.vertex[j].data,G.vertex[k].data,dut,ei,li,tag);
p=p->nextarc;
}
}
return OK;
}
3.最短路径问题
迪杰斯特拉算法 Dijkstra
Dijkstra算法适用于边权值为正的情况,如果边权值为负数就才用另一种最短路算法**Bellman-Ford算法。**适用于有向图和无向图。
#define INFINITY 32768
typedef unsigned int WeightType;
typedef WeightType AdjType;
typedef SeqList VertexSet;
//path[i]中存放顶点i当前最短路径 dist[i]存放顶点i的最短路径长度
ShortestPath_DJS(AdjMatrix g,int v0,WeightType dist[MAX_VERTEX_NUM],VertexSet path[MAX_VERTEX_NUM]){
//S为已找到最短路径的终点集合
VertexSet S;
//初始化dist[i] path[i]
for(i=0;i<g.vexnun;i++){
InitList(&path[i]);
dist[i]=g.arcs[v0][i].adj;
if(dist[i]<INIFINTY){
//AddTail表尾添加操作
AddTail(&path[i],g.vertex[v0]);
AddTail(&path[i],g.vertex[i]);
}
}
InitList(&S);
//将v0看作第一个已找到最短路径的终点
AddTail(&S,g.vertex[v0]);
//求v0到其余n-1个顶点的最短路径
for(t=1;t<=g.vexnum-1;t++){
min=INFINITY;
for(i=0;i<g.vexnum;i++){
if(!Member(g.vertex[i],S)&&dist[i]<min){
k=i;
min=dist[i];
}
}
if(min==INFINITY){
return;
}
AddTail(&S,g.vertex[k]);
//修正dist[i]
for(i=0;i<g.vexnum;i++){
if(!Member(g.vertex[i],s)&&g.argc[k][i].adj!=INFINITY&&(dist[k]+g.argc[k][i].adj<dist[i])){
dist[i]=disk[k]+g.arcs[k][i].adj;
path[i]=path[k];
AddTail(&path[i],g.vertex[i]);
}
}
}
}
typedef SeqList VertexSet;
//path为vi到vj的当前最短路径 dist为vi到vj的当前最短路径长度
ShortestPath_Floyd(AdjMatrix g,WeightType dist[MAX_VERTEX_NUM][MAX_VERTEX_NUM],VertexSet path[MAX_VERTEX_NUM][MAX_VERTEX_NUM]){
//初始化dist[i][j]与path[i][j]
for(i=0;i<g.vexnum;i++){
for(j=0;j<g.vexnum;j++){
InitList(&path[i][j]);
dist[i][j]=arcs[i][j].adj;
if(dist[i][j]<INFINITY){
AddTail(&path[i][j],g.vertex[i]);
AddTail(&path[i][j],g.vertex[j]);
}
}
}
for(k=0;k<g.vexnum;k++){
for(i=0;i<g.vexnum;i++){
for(j=0;j<g.vexnum;j++){
if(dist[i][k]+dist[k][j]<dist[i][j]){
dist[i][j]=dist[i][k]+dist[k][j];
//JoinList是合并线性表操作
path[i][j]=JoinList(path[i][k],path[k][j]);
}
}
}
}
}