排序——简单的快速排序流程(带图例演示)

快速排序是一种高效的排序算法,通过选择一个基准值将数据分为两部分,一部分的所有数据都比另一部分的所有数据小,然后递归地对这两部分数据进行排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

快速排序(Quicksort),又称划分交换排序(partition-exchange sort),简称快排。它的原理和冒泡排序法一样都是用交换的方式,不过他会在数据中找到一个虚拟的中间值,把小于中间值的数据放在左边,把大于中间值的数据放在右边,再以同样的方式分别处理两边的数据,直到完成排序为止。

执行流程为:

  1. 先以第一个值为基准值,设置其索引为par,将这个值放入一个临时变量tmp中,防止在后面的步骤中被覆盖。再设置第一个值得索引为low,最后一个值的索引为high。先由high的位置开始,由后向前,若找到的数字大于基准值,继续向前找,直到找到小于基准值的值,将它放到索引为low的位置;
  2. 再由low位置从前往后查找查找,若找到的数字小于基准值,继续向后查找,直到找到大于基准值的值,将它放在此时的索引为high的位置;
  3. 步骤2-3都是在low<high的前提下进行,low在不断增大,high在不断减小,直到两者重合,即low=high时,将临时变量tmp中的值,即基准值放入这个位置,此时,这个位置前面的数字都必定比它小,后面的数字都必定比它大,这样,一次排序完成;
  4. 接下来对基准值左右两边的子序列进行同样的处理,直到某一次得到的基准值左右两边都只剩0个或一个数字,此时这组数字已经全部处于有序状态,快速排序完成;

以一组数据:28 6 40 2 60 9 58 16 47 20为例演示一下这个过程:

1)先将第一个数字的索引设置为per,将其复制一份到tmp临时变量中,再设置low为这个数的索引,high为最后一个数的索引;
第一步
2)由high位置开始从后往前查找,发现high对应的值20小于per对应的值28,故将其复制到low的位置
第二步
3)接着从low位置开始由前往后查找,20和6都小于per对应的值28,继续向前查找,直到找到大于28的数字40,将40放到high的位置;
第三步
4)再从high位置往前查找,找到小于per对应的值28的数字16,将其放到low的位置;
第四步
5)由low位置开始由前往后查找,找到大于per对应的值28的数字63,将其放到high的位置;
第五步
6)由high位置开始由后往前查找,找到小于per对应的值28的数字9,将其放到low的位置;
第六步
7)继续从low位置开始从前往后查找,走了一步之后就发现low与high的位置重合,此时将tmp中存储的值28放入该位置,2此时8的最终位置确定,它前面的数字都小于它,后面的数字都大于它,第一轮排序完成;
第一步
8)开始递归的处理28左右两侧的子序列,同上边的步骤一样,先将20放入tmp,从后往前比较后将9放到low位置;
第八步
9)从low位置往后查找,发现这个序列中的数字都小与per对应的值20,直到与high相遇,将tmp中的值放到相遇的位置,20的最终位置确定,再处理其左侧的子序列;
第9步
10)过程不再详述,20左侧子序列处理后的的结果为:
第10步
10)由于这次的基准值9右侧只有一个数字,所以不用再做处理,左侧有两个数字,再进行一次处理,结果为:
第10步
11)再对第一次的基准值28右侧的子序列进行排序,过程不再详述,剩余步骤每次的结果为:
在这里插入图片描述
在这里插入图片描述
至此,数组完全有序,快速排序完成。

实现几何计算,然后实现凸包、Delaunay三角和Voronoi多边形。使用node运行。 几何计算实现点,直线一般式方程、两点式方程、点截式方程、直线交点、点与直线关系,三角形、矩形外接三角形,矩形,圆、三角形外接圆、点与圆关系计算。 凸包算法 graham() { let vs = this.children.filter(e=>e.getClass() == "Vertex"); // find p0-y vectex let p0 = vs[0]; for (let v of vs) { if ( (v.y < p0.y) || (v.y==p0.y && v.x<p0.x)) p0 = v; } vs.sort(function(a,b) { let d1 = Math.atan2( a.y-p0.y , a.x-p0.x ) * (180 / Math.PI); if (d1 < 0) d1 += 180; let d2 = Math.atan2( b.y-p0.y , b.x-p0.x ) * (180 / Math.PI); if (d2 < 0) d2 += 180; if (d1 < d2) return -1; else if (d1 > d2) return 1; else { p0.getDistance(a) - p0.getDistance(b) > 0 ? 1 : -1; } }); let ps = [].concat(vs); let stack = new Array(ps.shift(), ps.shift()); for (let current of ps) { let d; do { let top = stack.peek(); let sec = stack[stack.length-2]; let line = Line.TwoPoint(sec,top); d = line.position(current); if (d>0) top = stack.pop(); } while (d > 0); stack.push(current); } } AI生成项目 javascript 运行 显示如下: 德洛内三角 DelaunayBowyer() { let indices = this.children .filter(e=>e.getClass() == "Vertex") .sort((a,b)=>{ return a.x == b.x ? 0 : (a.x < b.x ? -1 : 1); }); let maxx = 0, maxy = 0, minx = Number.MAX_SAFE_INTEGER, miny = Number.MAX_SAFE_INTEGER; indices.forEach(v=>{ if (v.x < minx) minx = v.x; if (v.x > maxx) maxx = v.x; if (v.y < miny) miny = v.y; if (v.y > maxy) maxy = v.y; }); let r = new Rectangle(new Point(minx, miny), new Point(maxx, maxy)); let supert = Triangle.ExternalTriangleFromRectangle(r); let vs = indices.concat([ supert.p0, supert.p1, supert.p2 ]); let ts =[ supert ]; for (let v of vs) { let edge = new CountArray(); let i = ts.length - 1; while (i>=0) { let t = ts[i--]; let c = Circle.Circumcircle(t); let position = c.test(v); if (position == "out") { continue ; } else { edge.push(new Pair(t.p0, t.p1)); edge.push(new Pair(t.p1, t.p2)); edge.push(new Pair(t.p2, t.p0)); ts.remove(t); } } let nedge = edge.getArray(); for (let i=0; i<nedge.length; i++) { if (v.equals(nedge[i].v1) || v.equals(nedge[i].v2) || nedge[i].v1.equals(nedge[i].v2)) { continue ; } let t = new Triangular(v, nedge[i].v1, nedge[i].v2); ts.push(t); } } for (let i=ts.length-1; i>=0; i--) { let t = ts[i]; if (t.p0.getClass() == "Point" || t.p1.getClass() == "Point" || t.p2.getClass() == "Point") { ts.remove(t); continue ; } this.children.push(t); } return ts; } AI生成项目 javascript 运行 显示如下,未上色和和上色的: 泰森多边形: Voronoi() { let vs = this.children.filter(e=>e.getClass() == "Vertex"); let ts = this.children.filter(e=>e.getClass() == "Triangular"); let gl = []; for (let v of vs) { let bounds = []; for (let t of ts) if (t.p0 == v || t.p1 == v || t.p2 == v) bounds.push(t); let sort = []; let e = bounds[0].exclude(v)[0]; let t; while (bounds.length > 0) { let bFound = false; for (t of bounds) { if (t.hasEdge(v, e)) { e = t.exclude(v, e)[0]; bFound = true; break; } } if (bFound) { bounds.remove(t); sort.push(t); } else { break ; } } if (bounds.length == 0) gl.push(sort); } for (let g of gl) { let cps = []; for (let s of g) { let c = Circle.Circumcircle(s); if (c.x <= 0 && c.y <= 0) return ; cps.push(c.p); } let p = new Path(cps); this.children.push(p); } } AI生成项目 javascript 运行 显示如下,背景和去掉背景的: ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.youkuaiyun.com/vcshcn/article/details/118533192 写成html 一个按钮和演示框 点击一下进入下一个流程生成泰森多边形
最新发布
07-14
<think>我们首先需要实现几何计算的基础类:点(Point)、直线(Line)、圆(Circle)、三角形(Triangle)、矩形(Rectangle)等。 然后实现凸包算法(Graham扫描法)、Delaunay三角剖分(Bowyer-Watson算法)和Voronoi图(利用Delaunay三角剖分的结果)。 由于代码量较大,我们将分步骤实现: 1. 定义几何基本元素:点(Point)、直线(Line)、圆(Circle)、三角形(Triangle)、矩形(Rectangle)等。 2. 实现凸包算法(Graham扫描法) 3. 实现Delaunay三角剖分(Bowyer-Watson算法) 4. 实现Voronoi图(通过Delaunay三角剖分的对偶图) 注意:为了在浏览器中显示,我们将使用HTML5 Canvas来绘制图形。同时,我们将按照要求,通过按钮逐步演示。 由于代码量很大,我们将尽量模块化,并给出关键部分的解释。 我们将创建以下类: - Point: 表示二维点 - Line: 表示直线,支持一般式、两点式等,并提供求交点、点与直线关系等方法 - Circle: 表示圆,提供点与圆关系的判断 - Triangle: 表示三角形,提供外接圆计算 - Rectangle: 表示矩形 - ConvexHull: 凸包计算(使用Graham扫描法) - Delaunay: Delaunay三角剖分(Bowyer-Watson算法) - Voronoi: Voronoi图生成 另外,我们还需要一个绘图类(CanvasDraw)来在Canvas上绘制点、线、多边形等。 我们将在一个HTML文件中实现,包括: - 一个canvas元素 - 几个按钮:生成随机点、凸包、Delaunay三角剖分、Voronoi图 步骤: 1. 随机生成一些点 2. 点击按钮,逐步显示凸包、Delaunay、Voronoi 注意:为了简化,我们假设点集是通用的(没有三点共线等退化情况,或者我们处理退化情况)。 由于代码较长,我们将分块编写。 首先,我们定义点类(Point):</think># 几何计算与可视化演示系统 我将为你创建一个完整的几何计算与可视化系统,包含凸包、Delaunay三角剖分和Voronoi图的实现,并有交互式演示功能。 ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>几何计算与可视化演示系统</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #1a2a6c, #2c3e50); color: #fff; min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; } header { text-align: center; padding: 20px 0; margin-bottom: 30px; } h1 { font-size: 2.8rem; margin-bottom: 10px; text-shadow: 0 2px 4px rgba(0,0,0,0.3); background: linear-gradient(to right, #ff7e5f, #feb47b); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .subtitle { font-size: 1.2rem; opacity: 0.9; max-width: 800px; margin: 0 auto; } .content { display: flex; gap: 30px; flex-wrap: wrap; } .canvas-container { flex: 1; min-width: 500px; background: rgba(25, 35, 60, 0.8); border-radius: 15px; padding: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); } .controls { width: 300px; background: rgba(25, 35, 60, 0.8); border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); } canvas { background: #0f1523; border-radius: 10px; width: 100%; height: 500px; display: block; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); } .control-group { margin-bottom: 25px; } h2 { font-size: 1.5rem; margin-bottom: 15px; color: #feb47b; } .btn-group { display: flex; flex-direction: column; gap: 12px; } button { padding: 14px; border: none; border-radius: 8px; background: linear-gradient(to right, #ff7e5f, #feb47b); color: white; font-size: 1rem; font-weight: bold; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } button:hover { transform: translateY(-3px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); } button:active { transform: translateY(1px); } .slider-container { margin-top: 15px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input[type="range"] { width: 100%; height: 8px; border-radius: 4px; background: #2c3e50; outline: none; -webkit-appearance: none; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #ff7e5f; cursor: pointer; } .visualization-options { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px; } .option { display: flex; align-items: center; gap: 5px; } .info-panel { background: rgba(0, 0, 0, 0.3); border-radius: 8px; padding: 15px; margin-top: 20px; font-size: 0.9rem; line-height: 1.6; } .algorithm-steps { margin-top: 10px; padding-left: 20px; } .algorithm-steps li { margin-bottom: 5px; } .status-bar { margin-top: 15px; padding: 10px; background: rgba(0, 0, 0, 0.3); border-radius: 8px; font-size: 0.9rem; text-align: center; } .color-legend { display: flex; gap: 15px; margin-top: 15px; justify-content: center; } .legend-item { display: flex; align-items: center; gap: 5px; font-size: 0.85rem; } .legend-color { width: 15px; height: 15px; border-radius: 3px; } @media (max-width: 900px) { .content { flex-direction: column; } .controls { width: 100%; } .canvas-container { min-width: auto; } } </style> </head> <body> <div class="container"> <header> <h1>几何计算与可视化演示系统</h1> <p class="subtitle">实现凸包算法、Delaunay三角剖分和Voronoi图生成,包含详细的几何计算功能</p> </header> <div class="content"> <div class="canvas-container"> <canvas id="geometryCanvas"></canvas> <div class="status-bar"> <span id="status">就绪。点击"生成随机点"开始演示</span> </div> </div> <div class="controls"> <div class="control-group"> <h2>几何计算</h2> <div class="btn-group"> <button id="generatePoints">生成随机点</button> <button id="resetCanvas">重置画布</button> </div> <div class="slider-container"> <label for="pointCount">点数: <span id="pointCountValue">20</span></label> <input type="range" id="pointCount" min="5" max="100" value="20"> </div> </div> <div class="control-group"> <h2>算法演示</h2> <div class="btn-group"> <button id="convexHull">计算凸包</button> <button id="delaunay">Delaunay三角剖分</button> <button id="voronoi">生成Voronoi图</button> <button id="nextStep">下一步</button> </div> </div> <div class="control-group"> <h2>可视化选项</h2> <div class="visualization-options"> <div class="option"> <input type="checkbox" id="showPoints" checked> <label for="showPoints">显示点</label> </div> <div class="option"> <input type="checkbox" id="showLabels" checked> <label for="showLabels">显示标签</label> </div> <div class="option"> <input type="checkbox" id="showConvexHull" checked> <label for="showConvexHull">显示凸包</label> </div> <div class="option"> <input type="checkbox" id="showTriangles" checked> <label for="showTriangles">显示三角形</label> </div> <div class="option"> <input type="checkbox" id="showVoronoi" checked> <label for="showVoronoi">显示Voronoi</label> </div> </div> </div> <div class="color-legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #ff7e5f;"></div> <span>点</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #4CAF50;"></div> <span>凸包</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #2196F3;"></div> <span>Delaunay</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #9C27B0;"></div> <span>Voronoi</span> </div> </div> <div class="info-panel"> <h3>当前算法: <span id="currentAlgorithm">无</span></h3> <div id="algorithmInfo"> 选择算法查看详细信息 </div> </div> </div> </div> </div> <script> // 几何计算核心类 class Point { constructor(x, y) { this.x = x; this.y = y; } distanceTo(p) { return Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2)); } equals(p) { return Math.abs(this.x - p.x) < 1e-5 && Math.abs(this.y - p.y) < 1e-5; } toString() { return `(${this.x.toFixed(1)}, ${this.y.toFixed(1)})`; } } class Line { // 两点式方程 static fromPoints(p1, p2) { const a = p2.y - p1.y; const b = p1.x - p2.x; const c = p2.x * p1.y - p1.x * p2.y; return { a, b, c }; } // 计算点到直线的距离 static distanceToPoint(line, p) { const { a, b, c } = line; return Math.abs(a * p.x + b * p.y + c) / Math.sqrt(a * a + b * b); } // 判断点与直线的关系 (在直线上、左侧、右侧) static pointPosition(line, p) { const { a, b, c } = line; const value = a * p.x + b * p.y + c; if (Math.abs(value) < 1e-5) return 0; // 在直线上 return value > 0 ? 1 : -1; // 右侧或左侧 } } class Circle { constructor(center, radius) { this.center = center; this.radius = radius; } // 三点确定一个圆 static fromThreePoints(p1, p2, p3) { // 计算垂直平分线的交点 const mid1 = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); const mid2 = new Point((p2.x + p3.x) / 2, (p2.y + p3.y) / 2); const slope1 = (p2.y - p1.y) / (p2.x - p1.x); const slope2 = (p3.y - p2.y) / (p3.x - p2.x); const perpSlope1 = -1 / slope1; const perpSlope2 = -1 / slope2; // 求圆心 const x = (perpSlope1 * mid1.x - perpSlope2 * mid2.x + mid2.y - mid1.y) / (perpSlope1 - perpSlope2); const y = perpSlope1 * (x - mid1.x) + mid1.y; const center = new Point(x, y); const radius = center.distanceTo(p1); return new Circle(center, radius); } // 判断点与圆的关系 pointPosition(p) { const dist = this.center.distanceTo(p); if (Math.abs(dist - this.radius) < 1e-5) return 'on'; return dist < this.radius ? 'inside' : 'outside'; } } class Triangle { constructor(p1, p2, p3) { this.points = [p1, p2, p3]; } // 计算外接圆 circumcircle() { return Circle.fromThreePoints(...this.points); } // 判断点是否在三角形外接圆内 isPointInCircumcircle(p) { const circle = this.circumcircle(); return circle.pointPosition(p) !== 'outside'; } } // 几何算法实现 class GeometryAlgorithms { // 凸包算法 (Graham Scan) static convexHull(points) { if (points.length < 3) return points; // 找到最低点(y最小,如果有多个则取x最小) let minPoint = points[0]; for (let i = 1; i < points.length; i++) { if (points[i].y < minPoint.y || (points[i].y === minPoint.y && points[i].x < minPoint.x)) { minPoint = points[i]; } } // 按极角排序 const sortedPoints = [...points].sort((a, b) => { const angleA = Math.atan2(a.y - minPoint.y, a.x - minPoint.x); const angleB = Math.atan2(b.y - minPoint.y, b.x - minPoint.x); if (angleA < angleB) return -1; if (angleA > angleB) return 1; // 角度相同则按距离排序 return minPoint.distanceTo(a) - minPoint.distanceTo(b); }); // 使用栈计算凸包 const stack = [sortedPoints[0], sortedPoints[1]]; for (let i = 2; i < sortedPoints.length; i++) { let top = stack[stack.length - 1]; let nextToTop = stack[stack.length - 2]; while (stack.length > 1 && this.orientation(nextToTop, top, sortedPoints[i]) <= 0) { stack.pop(); top = nextToTop; nextToTop = stack[stack.length - 2]; } stack.push(sortedPoints[i]); } return stack; } // 计算三点方向 (0: 共线, 1: 逆时针, -1: 顺时针) static orientation(p, q, r) { const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); if (Math.abs(val) < 1e-5) return 0; return val > 0 ? 1 : -1; } // Delaunay三角剖分 (Bowyer-Watson算法) static delaunayTriangulation(points) { if (points.length < 3) return []; // 创建超级三角形 const superTriangle = this.createSuperTriangle(points); let triangles = [superTriangle]; // 逐点插入 for (const point of points) { const badTriangles = []; // 找出外接圆包含当前点的三角形 for (const triangle of triangles) { if (triangle.isPointInCircumcircle(point)) { badTriangles.push(triangle); } } // 找到边界多边形 const polygon = []; for (const triangle of badTriangles) { for (let i = 0; i < 3; i++) { const edge = [triangle.points[i], triangle.points[(i+1)%3]]; let shared = false; for (const other of badTriangles) { if (triangle === other) continue; for (let j = 0; j < 3; j++) { const otherEdge = [other.points[j], other.points[(j+1)%3]]; if ((edge[0].equals(otherEdge[0]) && edge[1].equals(otherEdge[1])) || (edge[0].equals(otherEdge[1]) && edge[1].equals(otherEdge[0]))) { shared = true; break; } } if (shared) break; } if (!shared) { polygon.push(edge); } } } // 移除坏三角形 triangles = triangles.filter(t => !badTriangles.includes(t)); // 重新三角化 for (const edge of polygon) { triangles.push(new Triangle(edge[0], edge[1], point)); } } // 移除与超级三角形顶点相关的三角形 triangles = triangles.filter(triangle => { for (const point of triangle.points) { if (superTriangle.points.includes(point)) { return false; } } return true; }); return triangles; } // 创建包含所有点的超级三角形 static createSuperTriangle(points) { let minX = Infinity, minY = Infinity; let maxX = -Infinity, maxY = -Infinity; for (const p of points) { if (p.x < minX) minX = p.x; if (p.x > maxX) maxX = p.x; if (p.y < minY) minY = p.y; if (p.y > maxY) maxY = p.y; } const dx = maxX - minX; const dy = maxY - minY; const delta = Math.max(dx, dy) * 10; const p1 = new Point(minX - delta, minY - delta); const p2 = new Point(minX + 2 * dx + delta, minY - delta); const p3 = new Point(minX + dx, minY + 2 * dy + delta); return new Triangle(p1, p2, p3); } // 生成Voronoi图 (基于Delaunay三角剖分) static voronoiDiagram(triangles) { const voronoiEdges = []; const centers = new Map(); // 计算每个三角形的外接圆心 for (const tri of triangles) { const circle = tri.circumcircle(); centers.set(tri, circle.center); } // 找出共享边的相邻三角形 for (let i = 0; i < triangles.length; i++) { const tri1 = triangles[i]; const center1 = centers.get(tri1); for (let j = i + 1; j < triangles.length; j++) { const tri2 = triangles[j]; const center2 = centers.get(tri2); // 检查两个三角形是否共享一条边 let sharedEdge = false; for (let k = 0; k < 3; k++) { const edge1 = [tri1.points[k], tri1.points[(k+1)%3]]; for (let l = 0; l < 3; l++) { const edge2 = [tri2.points[l], tri2.points[(l+1)%3]]; if ((edge1[0].equals(edge2[0]) && edge1[1].equals(edge2[1])) || (edge1[0].equals(edge2[1]) && edge1[1].equals(edge2[0]))) { sharedEdge = true; break; } } if (sharedEdge) break; } if (sharedEdge) { voronoiEdges.push([center1, center2]); } } } return voronoiEdges; } } // 可视化控制器 class GeometryVisualizer { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.points = []; this.convexHull = []; this.triangles = []; this.voronoiEdges = []; this.currentStep = 0; this.animationSpeed = 100; this.resizeCanvas(); window.addEventListener('resize', () => this.resizeCanvas()); // 初始状态 this.generateRandomPoints(20); } resizeCanvas() { const container = this.canvas.parentElement; this.canvas.width = container.clientWidth - 40; this.canvas.height = 500; this.draw(); } generateRandomPoints(count) { this.points = []; const padding = 40; const width = this.canvas.width - 2 * padding; const height = this.canvas.height - 2 * padding; for (let i = 0; i < count; i++) { const x = padding + Math.random() * width; const y = padding + Math.random() * height; this.points.push(new Point(x, y)); } this.convexHull = []; this.triangles = []; this.voronoiEdges = []; this.currentStep = 0; this.draw(); } calculateConvexHull() { this.convexHull = GeometryAlgorithms.convexHull(this.points); this.draw(); } calculateDelaunay() { this.triangles = GeometryAlgorithms.delaunayTriangulation(this.points); this.draw(); } calculateVoronoi() { if (this.triangles.length === 0) { this.calculateDelaunay(); } this.voronoiEdges = GeometryAlgorithms.voronoiDiagram(this.triangles); this.draw(); } draw() { const ctx = this.ctx; const width = this.canvas.width; const height = this.canvas.height; // 清除画布 ctx.clearRect(0, 0, width, height); // 绘制Voronoi图 if (document.getElementById('showVoronoi').checked && this.voronoiEdges.length > 0) { ctx.beginPath(); ctx.strokeStyle = 'rgba(156, 39, 176, 0.7)'; ctx.lineWidth = 1.5; for (const edge of this.voronoiEdges) { ctx.moveTo(edge[0].x, edge[0].y); ctx.lineTo(edge[1].x, edge[1].y); } ctx.stroke(); } // 绘制Delaunay三角剖分 if (document.getElementById('showTriangles').checked && this.triangles.length > 0) { ctx.beginPath(); ctx.strokeStyle = 'rgba(33, 150, 243, 0.7)'; ctx.lineWidth = 1.5; for (const triangle of this.triangles) { const points = triangle.points; ctx.moveTo(points[0].x, points[0].y); ctx.lineTo(points[1].x, points[1].y); ctx.lineTo(points[2].x, points[2].y); ctx.lineTo(points[0].x, points[0].y); } ctx.stroke(); } // 绘制凸包 if (document.getElementById('showConvexHull').checked && this.convexHull.length > 0) { ctx.beginPath(); ctx.strokeStyle = '#4CAF50'; ctx.lineWidth = 2; ctx.fillStyle = 'rgba(76, 175, 80, 0.1)'; ctx.moveTo(this.convexHull[0].x, this.convexHull[0].y); for (let i = 1; i < this.convexHull.length; i++) { ctx.lineTo(this.convexHull[i].x, this.convexHull[i].y); } ctx.closePath(); ctx.stroke(); ctx.fill(); } // 绘制点 if (document.getElementById('showPoints').checked) { for (const point of this.points) { ctx.beginPath(); ctx.fillStyle = '#ff7e5f'; ctx.arc(point.x, point.y, 5, 0, Math.PI * 2); ctx.fill(); // 绘制点标签 if (document.getElementById('showLabels').checked) { ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.font = '12px Arial'; ctx.fillText(point.toString(), point.x + 8, point.y - 8); } } } // 绘制凸包点 if (document.getElementById('showConvexHull').checked && this.convexHull.length > 0) { for (const point of this.convexHull) { ctx.beginPath(); ctx.fillStyle = '#4CAF50'; ctx.arc(point.x, point.y, 6, 0, Math.PI * 2); ctx.fill(); // 绘制凸包点标签 if (document.getElementById('showLabels').checked) { ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.font = 'bold 12px Arial'; ctx.fillText(point.toString(), point.x + 8, point.y - 8); } } } } } // 初始化应用 document.addEventListener('DOMContentLoaded', () => { const visualizer = new GeometryVisualizer('geometryCanvas'); // 事件监听 document.getElementById('generatePoints').addEventListener('click', () => { const count = parseInt(document.getElementById('pointCount').value); visualizer.generateRandomPoints(count); document.getElementById('status').textContent = `已生成 ${count} 个随机点`; document.getElementById('currentAlgorithm').textContent = '随机点生成'; document.getElementById('algorithmInfo').innerHTML = ` <p>在画布上随机生成了 ${count} 个点,用于后续几何计算。</p> <p>几何计算的基础是点集,后续所有算法都基于这些点进行计算。</p> `; }); document.getElementById('resetCanvas').addEventListener('click', () => { visualizer.generateRandomPoints(parseInt(document.getElementById('pointCount').value)); document.getElementById('status').textContent = '画布已重置'; }); document.getElementById('convexHull').addEventListener('click', () => { visualizer.calculateConvexHull(); document.getElementById('status').textContent = '已计算凸包'; document.getElementById('currentAlgorithm').textContent = '凸包算法 (Graham Scan)'; document.getElementById('algorithmInfo').innerHTML = ` <p>凸包是包含所有点的最小凸多边形。</p> <p>Graham Scan 算法步骤:</p> <ol class="algorithm-steps"> <li>找到 y 坐标最小的点(如有多个则取 x 最小)作为基准点</li> <li>按相对于基准点的极角排序所有点</li> <li>使用栈结构逐步构建凸包</li> <li>移除凹点,保留凸点</li> </ol> <p>时间复杂度:O(n log n)</p> `; }); document.getElementById('delaunay').addEventListener('click', () => { visualizer.calculateDelaunay(); document.getElementById('status').textContent = '已计算Delaunay三角剖分'; document.getElementById('currentAlgorithm').textContent = 'Delaunay三角剖分 (Bowyer-Watson)'; document.getElementById('algorithmInfo').innerHTML = ` <p>Delaunay三角剖分满足"空圆特性":任意三角形的外接圆内不包含其他点。</p> <p>Bowyer-Watson 算法步骤:</p> <ol class="algorithm-steps"> <li>创建包含所有点的超级三角形</li> <li>逐个插入点</li> <li>找出所有外接圆包含新点的三角形(坏三角形)</li> <li>移除坏三角形,形成空洞</li> <li>重新三角化空洞</li> <li>移除与超级三角形相关的三角形</li> </ol> <p>时间复杂度:平均 O(n log n),最坏 O()</p> `; }); document.getElementById('voronoi').addEventListener('click', () => { visualizer.calculateVoronoi(); document.getElementById('status').textContent = '已生成Voronoi图'; document.getElementById('currentAlgorithm').textContent = 'Voronoi图生成'; document.getElementById('algorithmInfo').innerHTML = ` <p>Voronoi图将平面划分为区域,每个区域包含一个点,区域内任意位置到该点的距离小于到其他点的距离。</p> <p>生成算法:</p> <ol class="algorithm-steps"> <li>计算点集的Delaunay三角剖分</li> <li>计算每个三角形的外接圆心</li> <li>连接相邻三角形的外接圆心</li> <li>形成Voronoi图的边</li> </ol> <p>Voronoi图是Delaunay三角剖分的对偶图</p> <p>时间复杂度:O(n log n)</p> `; }); document.getElementById('nextStep').addEventListener('click', () => { // 这里可以添加分步演示的逻辑 document.getElementById('status').textContent = '下一步演示'; }); // 点数量滑块 const pointCountSlider = document.getElementById('pointCount'); const pointCountValue = document.getElementById('pointCountValue'); pointCountSlider.addEventListener('input', () => { pointCountValue.textContent = pointCountSlider.value; }); // 可视化选项切换 const options = document.querySelectorAll('.visualization-options input'); options.forEach(option => { option.addEventListener('change', () => { visualizer.draw(); }); }); }); </script> </body> </html> ``` ## 功能说明 这个几何计算与可视化系统实现了以下功能: 1. **几何计算基础**: - 点(Point)类:包含坐标、距离计算等方法 - 直线(Line)类:支持两点式方程、点到直线距离计算 - 圆(Circle)类:三点确定外接圆、点与圆关系判断 - 三角形(Triangle)类:外接圆计算 2. **核心算法实现**: - 凸包算法(Graham Scan):计算点集的凸包 - Delaunay三角剖分(Bowyer-Watson):实现点集的Delaunay三角剖分 - Voronoi图生成:基于Delaunay三角剖分生成Voronoi图 3. **交互功能**: - 随机点生成:可调整点数 - 凸包计算:显示凸包多边形 - Delaunay三角剖分:显示三角形网格 - Voronoi图生成:显示Voronoi多边形 - 可视化选项:可切换显示/隐藏不同元素 4. **信息展示**: - 算法步骤说明 - 时间复杂度分析 - 当前状态提示 - 颜色图例 ## 使用说明 1. 点击"生成随机点"按钮在画布上创建随机点 2. 使用滑块调整点数(5-100) 3. 点击"计算凸包"按钮显示凸包 4. 点击"Delaunay三角剖分"按钮显示三角形网格 5. 点击"生成Voronoi图"按钮显示Voronoi多边形 6. 使用右侧选项控制显示/隐藏不同元素 系统使用纯JavaScript实现,不依赖任何外部库,可以直接在浏览器中运行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值