[从头学数学] 第252节 Python实现数据结构:图(Graph)

剧情提要:
阿伟看到了一本比较有趣的书,是关于《计算几何》的,2008年由北清派出版。很好奇
它里面讲了些什么,就来看看啦。


正剧开始:
星历2016年07月29日 11:51:46, 银河系厄尔斯星球中华帝国江南行省。

[工程师阿伟]正在和[机器小伟]一起研究[计算几何]]。

[从头学数学] 第252节 Python实现数据结构:图(Graph)_第1张图片


第一个部分:Graph

import xml.dom.minidom
import DataStruct as ds;


class Graph():
    def readXmlFile(self, file):
        xmldoc = xml.dom.minidom.parse(file)
        
        Graph = xmldoc.getElementsByTagName("Graph")[0];
        attr = Graph.attributes;
        width = float(attr["width"].value);
        height = float(attr["height"].value);
        directed = attr["directed"].value;
        weighted = attr["weighted"].value;
        G = [width, height, directed, weighted];
        print('图属性:', G);

        Vertices = xmldoc.getElementsByTagName("Vertex");
        V = [];
        for vertex in Vertices:
            attr = vertex.attributes;
            vid = int(attr["vertexId"].value);
            x = float(attr["x"].value);
            y = float(attr["y"].value);
            lable = int(attr["label"].value);
            V.append([vid, x, y, lable]);

        Edges = xmldoc.getElementsByTagName("Edge");
        E = [];
        for edge in Edges:            
            attr = edge.attributes;
            tail = int(attr["tail"].value);
            head = int(attr["head"].value);
            E.append([tail, head]);

        print('顶点集:', V);
        print('边集:', E);
        return (G, V, E);

    def writeXmlFile(self, file, GVETuple):
        file = open(file, "w")
        file.write('\n')

        G, V, E = GVETuple;

        GAttr = ['width', 'height', 'directed', 'weighted'];
        VAttr = ['vertexId', 'x', 'y', 'label'];
        EAttr = ['tail', 'head'];
        
        file.write('\n");

        indent = ' '*2;

        lev = 1;
        
        file.write(lev*indent+'\n');
        lev += 1;
        for j in range(len(V)):
            file.write(lev*indent+'\n");
        lev -= 1;
        file.write(lev*indent+'\n');

        file.write(lev*indent+'\n');
        lev+=1;
        for j in range(len(V)):
            file.write(lev*indent+'\n");
        lev -= 1;
        file.write(lev*indent+'\n');

        lev -= 1;
        file.write('\n');
        file.close()

    #判断从起点到终点是否有通道
    def graphDFS(self, graphTuple, start, goal):
        # G = (V,E) is the graph with vertices, V, and edges, E.
        G,V,E = graphTuple;
        stack = ds.Stack()
        visited = ds.HashSet()
        stack.push(start)

        path = [[start]];

        while not stack.isEmpty():
            # A vertex is popped from the stack. This is called the current vertex.
            current = stack.pop()
            
            # The current vertex is added to the visited set.
            visited.add(current)            

            # If the current vertex is the goal vertex, then we discontinue the
            # search reporting that we found the goal.
            if current == goal:
                #return True  # or return path to goal perhaps
                path[0] += [goal];
                return path;

            cursor = path[0];
            path = path[1:];
            
            
            # Otherwise, for every adjacent vertex, v, to the current vertex
            # in the graph, v is pushed on the stack of vertices yet to search
            # unless v is already in the visited set in which case the edge
            # leading to v is ignored.
            for v in self.adjacent(current, E):
                if not v in visited: 
                    stack.push(v)
                    if (v != goal):
                        path.append(cursor+[v]);


            # If we get this far, then we did not find the goal.
        #return False  # or return an empty path
        return [];

    #在边集中含有当前点的边
    def adjacent(self, current, E, directed = True):
        #E为边集,格式为[[tail, head], ...]
        V = [];
        if (directed):
            for i in range(len(E)):
                if (E[i][0] == current):
                    V.append(E[i][1]);

        else:
            for i in range(len(E)):
                if (E[i][0] == current):
                    V.append(E[i][1]);
                elif (E[i][1] == current):
                    V.append(E[i][0]);

        return V;

    class __Vertex:
        def __init__(self,vertexId,x,y,label):
            self.vertexId = vertexId
            self.x = x
            self.y = y
            self.label = label
            self.adjacent = []
            self.previous = None

    class __Edge:
        def __init__(self,v1,v2,weight=0):
            self.v1 = v1
            self.v2 = v2
            self.weight = weight

        def __lt__(self,other):
        return self.weight < other.weight 

用例:

def tmp():
    graph = Graph();
    data = graph.readXmlFile("graph.xml");
    #graph.writeXmlFile("graphcopy.xml", data);

    #由于用例的顶点序号和它的标签数字不一样,所以有了下面这么多变换
    path = graph.graphDFS(data, 10, 9);
    print('path: ', path);

    G,V,E = data;
    vNum = len(V);
    for k in range(len(path)):
        print('第{0}条路径'.format(k+1));
        for i in range(len(path[k])):
            print(V[vNum-path[k][i]-1][3],'->', end = '');

        print('');


一张不考虑权重的有向图:

#图1


  
    
    
    
    
    
    
    
    
    
    
    
    
    
  
  
    
    
    
    
    
    
    
    
    
    
    
    
    
  


[从头学数学] 第252节 Python实现数据结构:图(Graph)_第2张图片


第二个部分:绘图部分

图属性: [595.8, 229.2, 'True', 'False']
顶点集: [[12, 343.15, 156.1, 10], [11, 246.15, 161.1, 9], [10, 288.15, 58.1, 0], [9, 374.15, 58.1, 1], [8, 135.15, 156.1, 6], [7, 49.65, 83.1, 2], [6, 167.15, 83.05, 3], [5, 121.15, 19.1, 8], [4, 419.15, 204.1, 11], [3, 426.15, 87.1, 4], [2, 546.15, 96.1, 5], [1, 546.15, 210.1, 7], [0, 485.15, 161.1, 12]]
边集: [[12, 10], [10, 6], [6, 11], [7, 6], [7, 8], [7, 5], [11, 12], [12, 4], [4, 0], [0, 2], [2, 1], [2, 3], [3, 9], [9, 10], [9, 12]]

	if (1) {
		
		var text = new DrawText();
		
		plot.translate(0, 200);
		
		//有向图两点间画向量连接,否则用直线连接
		var drawVector = $Graph[2];	
		
		
		var vNum = $Verts.length;
		var eNum = $Edges.length;
		
		var x1, y1, x2, y2, dx, dy;
		var P1, P2;
		
		for (var i = 0; i < eNum; i++) {
			P1 = $Verts[vNum-$Edges[i][0]-1];
			P2 = $Verts[vNum-$Edges[i][1]-1];
			
			x1 = P1[1];
			y1 = P1[2];
			x2 = P2[1];
			y2 = P2[2];
			
			dx = (x2-x1)/4;
			dy = (y2-y1)/4;
			
			x1 += dx;
			x2 -= dx;
			y1 += dy;
			y2 -= dy;
			
			if (drawVector == "True") {
				shape.vectorDraw([[x1, -y1], [x2, -y2]], 'blue', 1);
			}
			else {
				shape.multiLineDraw([[x1, -y1], [x2, -y2]], 'blue', 1);
			}
		}		
		
		for (var i = 0; i < vNum; i++) {
			text.textWithCircle([$Verts[i][3].toFixed(0)], $Verts[i][1], $Verts[i][2], 0,
			'green', 30, 'M');
		}


//文本显示方便类
function DrawText() {
	this.protype = function(str, xPos, yPos,  traits, rotate, style, fontSize, alignment) {	
		//traits参数指定了显示正常/加粗/斜体等区别
		//style 是颜色/渐变的区别
		//fontSize是字体字号的区别
		//alignment是对齐方式的区别
		
		rotate = rotate ? rotate : 0;
		style = style ? style : 'black';
		fontSize = fontSize >= 10 ? fontSize : 20;
		//左对齐0, 中对齐1, 右对齐2
		alignment = alignment ? alignment : 'L';
		
		var tmp = fontSize.toFixed(0)+'px';
		var font = '';
		
		if (traits == 'normal')  {
			font = "normal normal normal "+tmp+" Times New Roman";
		}
		else if (traits == 'bold') {
			font = 'normal normal 800 '+tmp+' Arial';
		}
		else if (traits == 'italic') {
			font = 'italic normal bold '+tmp+' Microsoft Sans Serif';
		}
		else {
			font = "normal normal normal "+tmp+" Times New Roman";
		}
		
		plot.save()
			.setFont(font)
			//.setTextBaseline('top')
			.setFillStyle(style)
			.translate(xPos, yPos)
			.rotate(-rotate);
			
		var x = 0, y = 0;
		var measure = 0;
		var s = '';
		
		//多行
		var len = str.length;
		
		for (var i = 0; i < len; i++) {
			s = str[i];
			measure = plot.measureText(s);

			if (alignment == 1 || alignment.toUpperCase() == 'M' || alignment.toUpperCase() == 'C'){
				//[x,y]为居中对齐的中点
				plot.fillText(s, x-measure/2, y, measure);
			}
			else if (alignment == 2 || alignment.toUpperCase() == 'R'){
				//[x,y]为右对齐的右边边界点
				plot.fillText(s, x-measure, y, measure);
			}
			else {
				//于[x,y]处左对齐
				plot.fillText(s, x, y, measure);
			}
			
			
			y += fontSize*1.5;
		}
		
		plot.restore();	
		
	
	}
	
	this.normal = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		return this.protype(str, xPos, yPos, 'normal', rotate, style, fontSize, alignment);

	}
		
	this.bold = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		return this.protype(str, xPos, yPos, 'bold', rotate, style, fontSize, alignment);
	}
	
	this.italic = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		return this.protype(str, xPos, yPos, 'italic', rotate, style, fontSize, alignment);

	}
	
	//计算文字显示需要的边界范围,这是一个矩形,并且考虑到了文字的旋转和多行问题
	this.calcRectBound = function(str, xPos, yPos, rotate, fontSize, alignment) {
		rotate = rotate ? rotate : 0;
		fontSize = fontSize >= 10 ? fontSize : 20;
		//左对齐0, 中对齐1, 右对齐2
		alignment = alignment ? alignment : 'L';
		var font = "normal normal normal "+fontSize.toFixed(0)+'px'+" Times New Roman";
		plot.save()
			.setFont(font);
			
		var lines = str.length;
		var maxChars = 0;
		var lineChars  = 0;
		
		for (var i = 0; i < lines; i++) {
			lineChars = plot.measureText(str[i])+5;
			if (lineChars > maxChars) {
				maxChars = lineChars;				
			}
		}
		
		var boundWidth = maxChars;
		var boundHeight = fontSize*1.5*lines;
		var hMargin = 0;
		var array = [];
		
		//根据行数确定偏移距离
		if (lines == 1) {
			hMargin = -boundHeight/2+Math.floor(fontSize/3);
		}
		else {
			hMargin = fontSize/2 - 1.5*(lines-1)*fontSize/2;
		}

		if (alignment == 1 || alignment.toUpperCase() == 'M' || alignment.toUpperCase() == 'C'){
			array = [[boundWidth/2, 0], 
					 [-boundWidth/2, 0],
					 [-boundWidth/2, boundHeight], 
					 [boundWidth/2, boundHeight]];
			
		}
		else if (alignment == 2 || alignment.toUpperCase() == 'R'){
			array = [[0, 0], 
					 [-boundWidth, 0],
					 [-boundWidth, boundHeight], 
					 [0, boundHeight]];
		}
		else {
			array = [[boundWidth, 0], 
					 [0, 0],
					 [0, boundHeight], 
					 [boundWidth, boundHeight]];
		}
		
		var transform = new Transform();
		array = transform.translate(transform.rotate(transform.translate(array, 0, hMargin), rotate), xPos, -yPos);
		
		plot.restore();
		return array;
		
	
	}
	
	
	//计算文字显示需要的边界范围,这是一个椭圆形,并且考虑到了文字的旋转和多行问题
	this.calcEllipBound = function(str, xPos, yPos, rotate, fontSize, alignment) {
		rotate = rotate ? rotate : 0;
		fontSize = fontSize >= 10 ? fontSize : 20;
		//左对齐0, 中对齐1, 右对齐2
		alignment = alignment ? alignment : 'L';
		var font = "normal normal normal "+fontSize.toFixed(0)+'px'+" Times New Roman";
		plot.save()
			.setFont(font);
			
		var lines = str.length;
		var maxChars = 0;
		var lineChars  = 0;
		
		for (var i = 0; i < lines; i++) {
			lineChars = plot.measureText(str[i]);
			if (lineChars > maxChars) {
				maxChars = lineChars;				
			}
		}
		
		var boundWidth = maxChars;
		var boundHeight = fontSize*1.5*lines;
		var hMargin = 0;
		var array = [];
		var transform = new Transform();
		
		//椭圆的长短半轴
		var ea = boundWidth/2*1.2, eb = boundHeight/2*1.2;
		
		//根据行数确定偏移距离
		if (lines == 1) {
			hMargin = fontSize/2;
		}
		else {
			hMargin = fontSize/2 - 1.5*(lines-1)*fontSize/2;
		}
				
		if (alignment == 1 || alignment.toUpperCase() == 'M' || alignment.toUpperCase() == 'C'){
			array = shape.ellipse(ea, eb);
			array = transform.translate(transform.rotate(transform.translate(array, 0, hMargin), rotate), xPos, -yPos);
			
		}
		else if (alignment == 2 || alignment.toUpperCase() == 'R'){
			array = shape.ellipse(ea, eb);
			array = transform.translate(transform.rotate(transform.translate(array, -boundWidth/2, hMargin), rotate), xPos, -yPos);
			
		}
		else {
			array = shape.ellipse(ea, eb);
			array = transform.translate(transform.rotate(transform.translate(array, boundWidth/2, hMargin), rotate), xPos, -yPos);
		}
		

		
		plot.restore();
		return array;
		
	
	}
	
	this.textWithRect = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		var boundArray = this.calcRectBound(str, xPos, yPos, rotate, fontSize, alignment);
		//hint(boundArray);
		shape.strokeDraw(boundArray, style, 1);
		this.normal(str, xPos, yPos, rotate, style, fontSize, alignment);
	
	}
	
	//文字伴随一柄小旗
	//当旋转时,现在还未能完全算清楚准确的坐标变换
	this.textWithFlag = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		var transform = new Transform();
		shape.fillDraw(shape.nEdge(xPos, -yPos-5, 10, 3, Math.PI), style, 1);
		var boundArray = this.calcRectBound(str, 0, 0, 0, fontSize, 'L');
		
		//取阵列中间两个点,是为了加一柄棋杆。
		var p_1x = boundArray[1][0], p_1y = boundArray[1][1],
			p_2x = boundArray[2][0], p_2y = boundArray[2][1];
			
		var dx = 1*(p_2x-p_1x), dy = 1*(p_2y-p_1y);
		p_2x = p_2x-2*dx;
		p_2y = p_2y-2*dy;
		
		var line = [[p_1x, p_1y], [p_2x, p_2y]];
		
		xPos = xPos - dx;
		yPos = yPos - dy-fontSize/4;
		
		boundArray = transform.translate(transform.rotate(boundArray, rotate), xPos, -yPos);
		line = transform.translate(transform.rotate(line, rotate), xPos, -yPos);
		

		
		shape.multiLineDraw([].concat(line), style, 1);
		//hint(boundArray);
		shape.strokeDraw(boundArray, style, 1);
		this.bold(str, xPos, yPos, rotate, style, fontSize, 'L');
	
	}
	
	
	this.textWithEllipse = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		var boundArray = this.calcEllipBound(str, xPos, yPos, rotate, fontSize, alignment);
		//hint(boundArray);
		shape.strokeDraw(boundArray, style, 1);
		this.normal(str, xPos, yPos, rotate, style, fontSize, alignment);
	
	}
	
	this.textWithCircle = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		var r = 30;
		//hint(boundArray);
		shape.strokeCircle(xPos, yPos, r);
		this.normal(str, xPos, yPos+fontSize/4, rotate, style, fontSize, alignment);
	
	}
	
	//两个点连直线,上面写文字
	this.textWithLine = function(str, xPos1, yPos1, xPos2, yPos2, style, fontSize, alignment) {
		var rotate = xPos2 == xPos1 ? 0 : Math.atan((yPos2-yPos1)/(xPos2-xPos1));
		//hint(boundArray);
		shape.multiLineDraw([[xPos1, yPos1], [xPos2, yPos2]], style, 1);
		this.bold(str, xPos, yPos, rotate, style, fontSize, alignment);
	
	}
	
	//两个点连向量,上面写文字
	this.textWithVector = function(str, xPos1, yPos1, xPos2, yPos2, style, fontSize, alignment) {
		var rotate = xPos2 == xPos1 ? 0 : Math.atan((yPos2-yPos1)/(xPos2-xPos1));
		//hint(boundArray);
		shape.vectorDraw([[xPos1, yPos1], [xPos2, yPos2]], style, 1);
		this.bold(str, xPos, yPos, rotate, style, fontSize, alignment);
	
	}
	
	
	
	this.textWithSphere = function(str, xPos, yPos, rotate, style, fontSize, alignment, sphereR) {
		var R = sphereR > 5 ? sphereR : 5;
		shape.sphere([xPos, yPos], R, style);
		this.bold(str, xPos, yPos-R, rotate, style, fontSize, alignment);
	
	}
	
	//一组圆圈,以第一圈为中心,在它的外边围一圈较小的圆
	this.textWithCluster = function(str, xPos, yPos, rotate, style, fontSize, alignment) {
		var centerD = fontSize*4;
		var peripheralD = fontSize*3;
		//中央大圆
		var circle_C = shape.nEdge(xPos, yPos, centerD/2, 36, 0);
		//传入的str应该是一个字符串数组,['s1', 's2', ...]这种格式
		//把s1填入中央圆圈,其它的填入它周围的一系列圆圈中
		
		//字符串数组中除第一个串以外的字符串的个数
		var count = str.length - 1;
		
		
		//小弟不能太少,到少留三个位置
		if (count < 3) count = 3;
		
		//内外圆的圆心距离 periOff > (centerD+peripheralD)/2
		//同时要满足 (periOff* 6)/count > peripheralD		
		var periOff = Math.max((centerD+peripheralD)/2, peripheralD*count/6);
		
		var periArray = shape.nEdge(0, 0, periOff, count);
		
		var transform = new Transform;
		periArray = transform.translate(transform.rotate(periArray, rotate), xPos, yPos);
		
		//画圆圈集群
		shape.strokeDraw([].concat(circle_C), style, 1);
		var circle_P = [];
		for (var i = 0; i < count; i++) {
			circle_P = shape.nEdge(periArray[i][0], periArray[i][1], peripheralD/2, 36, 0);
			shape.strokeDraw(circle_P, style, 1);
		}
		
		this.bold([str[0]], xPos, Math.abs(yPos)+0.5*fontSize, 0, style, fontSize, alignment);
		
		for (var i = 0; i < count; i++) {
			this.normal([str[1+i]], periArray[i][0], Math.abs(periArray[i][1])+0.4*fontSize, 0, style, fontSize*0.7, alignment);
		}
	}

}

第三个部分:Dijk寻路算法

###
# @usage   求两个给定地点,所有路径中的最短值
# @author  mw
# @date    2016年01月13日  星期三  09:50:23 
# @param
# @return
#
###
class DijkstraPath():
    #初始化
    def __init__(self, node_map):
        self.node_map = node_map;
        self.node_length = len(node_map);
        self.used_node_list = [];
        self.collected_node_dict = {};
        
    #调用函数
    def __call__(self, from_node, to_node):
        self.from_node = from_node;
        self.to_node = to_node;
        self._init_dijkstra();
        return self._format_path();
    
    def _init_dijkstra(self):
        self.used_node_list.append(self.from_node);
        self.collected_node_dict[self.from_node] = [0, -1];
        for index1, node1 in enumerate(self.node_map[self.from_node]):
            if node1:
                self.collected_node_dict[index1] = [node1, self.from_node];
        self._foreach_dijkstra();
        
    def _foreach_dijkstra(self):
        #保证每个点最多只到一次
        if len(self.used_node_list) == self.node_length - 1:
            return;
        #由于items()方法会改变原字典内容,所以此处拷贝副本
        collected_node_dict = dict(self.collected_node_dict);
        # 遍历已有权值节点
        for key, val in collected_node_dict.items():  
            if key not in self.used_node_list and key != to_node:
                self.used_node_list.append(key);
            else:
                continue;
            # 对节点进行遍历
            for index1, node1 in enumerate(self.node_map[key]):  
                # 如果节点在权值节点中并且权值大于新权值
                if node1 and index1 in self.collected_node_dict \
                   and self.collected_node_dict[index1][0] > node1 + val[0]:
                    # 更新权值
                    self.collected_node_dict[index1][0] = node1 + val[0]
                    self.collected_node_dict[index1][1] = key;
                elif node1 and index1 not in self.collected_node_dict:
                    self.collected_node_dict[index1] = [node1 + val[0], key];
        #递归
        self._foreach_dijkstra();
        
    def _format_path(self):
        node_list = [];
        temp_node = self.to_node;
        node_list.append((temp_node, self.collected_node_dict[temp_node][0]));
        while self.collected_node_dict[temp_node][1] != -1:
            temp_node = self.collected_node_dict[temp_node][1];
            node_list.append((temp_node, self.collected_node_dict[temp_node][0]));
        node_list.reverse();
        return node_list;

    def pathString(self, node):
        node_list = self._format_path();
        size = len(node_list);
        s = '选择的路线是:\n';
        for i in range(size):
            if i < size-1:
                s += str(node[node_list[i][0]])+'-->';
            else:
                s += str(node[node_list[i][0]]);
        s+= ' ,这条路线总长为{0}米'.format(node_list[size-1][1]);
        return s;
    
def set_node_map(node_map, node, node_list):
    for x, y, val in node_list:
        node_map[node.index(x)][node.index(y)] = node_map[node.index(y)][node.index(x)] =  val

用例:

if __name__ == "__main__":
    #节点表
    node = [1, 2, 3, 4, 5, 6];
    addressString = ['学校', '医院', '体育场', '公园', '小年宫', '小伟家'];
    #带权重的边表
    node_list = [(1,6,250), (1,2,300), (5,6,300), (4,5,470),
                 (3,4,150), (2,3,450),(2,5,250)];
    #节点数量的平方
    node_map = [[0 for val in range(len(node))] for val in range(len(node))];
    #预设节点表
    set_node_map(node_map, node, node_list);
    #Demo 求A --> D 的最短距离
    from_node = node.index(6);
    to_node = node.index(3);

    #求取路径
    dijkstrapath = DijkstraPath(node_map);
    #路径字符串
    path = dijkstrapath(from_node, to_node);

    print(dijkstrapath.pathString(addressString));


本节到此结束,欲知后事如何,请看下回分解。

你可能感兴趣的:(从头学数学,从头学数学)