許多計算應用程式不僅包含項集合,而且包含這些項對之間的連通集合。這些連通關係直接產生大量問題:跟隨這些連通 ,能夠從一個項到達另一個項嗎?從已知項,可以到達其他多少個項?從此項到彼項的最佳路徑是哪一條?
我們使用一種稱作圖(graph)的抽象對象來給這種情形建模。下面給出本文使用的研究圖演算法的基本ADT介面:
typedef struct{
int v;
int w;
} Edge;
Edge EDGE(int, int);
struct graph{
int V;
int E;
int **adj;
};
typedef struct graph* Graph;
Graph GRAPHinit(int);
void GRAPHinsert(Graph, Edge);
void GRAPHremove(Graph, Edge);
int GRAPHedges(Edge[], Graph G);
Graph GRAPHcopy(Graph);
void GRAPHdestroy(Graph);
void GRAPHshow(Graph G);
Graph GRAPHrand(int, int);
int ** MATRIXint(int, int, int);
接下來是他們的實現:
Edge EDGE(int v, int w)
{
Edge edge;
edge.v = v;
edge.w = w;
return edge;
}
Graph GRAPHinit(int V)
{
Graph G = (Graph)malloc(sizeof *G);
G->V = V;
G->E = 0;
G->adj = MATRIXint(V, V, 0);
return G;
}
void GRAPHinsert(Graph G, Edge e)
{
int v = e.v, w = e.w;
if(G->adj[v][w] == 0)
G->E++;
G->adj[v][w] = 1;
G->adj[w][v] = 1;
}
void GRAPHremove(Graph G, Edge e)
{
int v = e.v, w = e.w;
if(G->adj[v][w] == 1)
G->E--;
G->adj[v][w] = 0;
G->adj[w][v] = 0;
}
/**
* get the edges of the Graph
*/
int GRAPHedges(Edge a[], Graph G)
{
int v, w, E = 0;
for(v = 0; v < G->V; v++)
for(w = v; w < G->V; w++)
if(G->adj[v][w] == 1)
a[E++] = EDGE(v, w);
return E;
}
/**
* create a matrix of integer
* r - row of the matrix
* c - column of the matrix
* val - the value of the matrix
*/
int ** MATRIXint(int r, int c, int val)
{
int i,j;
int **t = (int **)malloc(r * sizeof(int *));
for(i = 0; i<r; i++)
t[i] = (int *)malloc(c*sizeof(int));
for(i = 0; i<r; i++)
for(j = 0; j<c; j++)
t[i][j] = val;
return t;
}
Graph GRAPHcopy(Graph G)
{
int i,j;
Graph g = (Graph)malloc(sizeof *G);
g->V = G->V;
g->E = G->E;
g->adj = MATRIXint(G->V, G->V, 0);
for(i = 0; i<G->V; i++)
for(j = 0; j<G->V; j++)
g->adj[i][j] = G->adj[i][j];
return g;
}
/**
* destroy the graph and free the memory, first free
* the adj, then free the graph
*/
void GRAPHdestroy(Graph G)
{
int i;
for(i = 0; i<G->V; i++)
free(G->adj[i]);
free(G);
}
void GRAPHshow(Graph G)
{
int i,j;
printf("%d vertices, %d edges/n", G->V, G->E);
for(i = 0; i < G->V; i++)
{
printf("%2d: ", i);
for(j = 0; j < G->V; j++)
if(G->adj[i][j] == 1)
printf(" %2d", j);
printf("/n");
}
}
/**
* generate a random graph which has V vertices and E edges
*/
Graph GRAPHrand(int V, int E)
{
// create a graph
Graph g = GRAPHinit(V);
while(g->E < E)
{
Edge e;
e.v = rand() % V;
e.w = rand() % V;
GRAPHinsert(g, e);
}
return g;
}
當然這裡面沒有涉及到遍曆圖之類的演算法,不過這是基本的東西嘛。然後我們可以編寫圖形化程式,將圖這種抽象結構以圖形的方式展現出來。下面先寫一個視窗程序,其中g為Graph類型,pt為POINT結構指標,為了在WinMain主函數和WndProc之間傳遞,將它們聲明為全域變數就可以了:
Graph g;
POINT *pt;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
/**
* entry of the program
*/
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// do some initialize work.
g = GRAPHrand(25, 50);
pt = (POINT *)malloc(sizeof(*pt)*(g->V));
// create window struct.
WNDCLASSEX wndclass;
wndclass.cbClsExtra = 0;
wndclass.cbSize = sizeof(wndclass);
wndclass.cbWndExtra = 0;
wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wndclass.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
wndclass.hIconSm = NULL;
wndclass.hInstance = hInstance;
wndclass.lpfnWndProc = WndProc;
wndclass.lpszClassName = "MainFrame";
wndclass.lpszMenuName = NULL;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
// register class
::RegisterClassEx(&wndclass);
// create window
HWND hwnd = ::CreateWindowEx(0,
"MainFrame",
"My Program",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, // x
CW_USEDEFAULT, // y
CW_USEDEFAULT, // nWidth
CW_USEDEFAULT, // nHeight
NULL,
NULL,
hInstance,
NULL);
if(hwnd == NULL)
{
::MessageBox(NULL, "CreateWindowEx failed", "Error", MB_OK);
return -1;
}
// generate a random graph
RandVertices(g, pt, hwnd);
// show application window
::ShowWindow(hwnd, nCmdShow);
::UpdateWindow(hwnd);
// message loop
MSG msg;
while(::GetMessage(&msg, NULL, 0, 0))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
return msg.wParam;
}
/**
* Window Precedure
*/
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd, &ps);
// make the vertices filled
SelectObject(ps.hdc, GetStockObject(GRAY_BRUSH));
DrawEdges(g, pt, hwnd, hdc);
DrawVertices(g, pt, hwnd, ps.hdc);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
::PostQuitMessage(0);
break;
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
RandVertices()為每個頂點隨即安排一個座標,這個程式中DrawEdges(),DrawVertices()是我們圖抽象資料類型與圖形化顯示的橋樑。在此我們用它來串連抽象和具體的東西,它的實現如下:
void DrawEdges(Graph g, POINT pt[], HWND hwnd, HDC hdc)
{
int i;
Edge *edge = (Edge *)malloc(sizeof(*edge)*(g->E));
// get the edges
GRAPHedges(edge, g);
// draw the edge
for(i = 0; i < g->E; i++)
{
MoveToEx(hdc, pt[edge[i].v].x, pt[edge[i].v].y, NULL);
LineTo(hdc, pt[edge[i].w].x, pt[edge[i].w].y);
}
free(edge);
}
void DrawVertices(Graph g, POINT pt[], HWND hwnd, HDC hdc)
{
int i;
// draw the vertices of the graph
for(i = 0; i < g->V; i++)
Ellipse(hdc, pt[i].x-3, pt[i].y-3, pt[i].x+3, pt[i].y+3);
}
/**
* set the position of the vertices in the client
* remark - the size of p[] must equal to the number of the vertices in
* graph g
*/
void RandVertices(Graph g, POINT pt[], HWND hwnd)
{
int i;
int cxClient, cyClient;
RECT rt;
::GetClientRect(hwnd, &rt);
// get the width and height of the client
cxClient = rt.right - rt.left;
cyClient = rt.bottom - rt.top;
// randomly set x and y for every vertices
for(i = 0; i < g->V; i++)
{
pt[i].x = abs(rand())%cxClient;
pt[i].y = abs(rand())%cyClient;
}
}
程式很簡單,但可以進行大量的擴充,你可以對其修改,讓產生的頂點位置由你來安排,選擇繪製出定點,搜尋圖中的迴路,尋找一個定點到另一個定點的路徑等等,然後在顯示到你的介面上,相信會很有意思的,呵呵。
下面是我的一個25個頂點,50條邊的隨機產生圖,相信經過你更好的設計會產生更加複雜的圖,如果能圖形化解決實際的問題,那麼就更能為那些不理解演算法的人講述一個形象的方案了: