(转载)equals与==

本文详细解析了Java中==与equals的区别及应用场景,重点介绍了它们在基本类型、引用类型及String类型中的行为差异,强调了equals方法在实际开发中的重要性。

引言:从一个朋友的blog转过来的,里面解决了两个困扰我很久的问题。很有久旱逢甘霖的感觉。

 

概述:

       A.==可用于基本类型和引用类型:当用于基本类型时候,是比较值是否相同;当用于引用类型的时候,是比较对象是否相同。

       B.对于String a = “a”; Integer b = 1;这种类型的特有对象创建方式,==的时候值是相同的。

       C.基本类型没有equals方法,equals只比较值(对象中的内容)是否相同(相同返回true)。

       D.一个类如果没有定义equals方法,它将默认继承Object中的equals方法,返回值与==方法相同。

详述:

       ①==和equals的实质。

       在JAVA中利用"=="比较变量时,系统使用变量在"栈"中所存的值作为比较的依据。

       基本数据类型在"栈"中存的是其内容值,而对象类型在"栈"中存的是地址,这些地址指向"堆"中的对象。

       java.lang包中的Object类有public boolean equals(Object obj)方法,它比较两个对象是否相等。

       其它对象的equals方法仅当被比较的两个引用指向的对象内容相同时,对象的equals()方法返回true。

       总之,"=="和"!="比较的是地址.也可认为"=="和"!="比较的是对象句柄;而equals()比较的是对象内容.或者说,,"=="和"!="比较的是"栈"中的内容,而equals()比较的是"堆"中的内容.

       ②==操作符。专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相当,只能用==操作符。

      Java的基本数据类型为(char,byte,short,int,long,float,double,boolean)。

      如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(对内存),变量本身也占用一块内存,例如Object obj = new Object()变量obj是一个内存,new Object()是一个内存,此时,变量所对应的内存中存储的数据就是对象占用的那块内存的首地址。对于指向对象内存的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。

       ③构造器形成的差别。对于String和Integer来说,由于他们特有的创建对象的方式。使用构造器和不使用构造器得到一个对象,==方法比较所产生的结果是不同的。 String a = “abc”; String b = "abc"; 此时a==b得到结果为true。String a = new String("abc"); String b = new String("abc");此时a==b得到的结果为false。Integer a = 1; Integer b = 1;此时a==b的结果是true。Integer a = new Integer(1); Integer b = new Integer(1);此时a==b得到的结果为false。

       通过这一点其实我们也能够更加容易理解==对内存的实际操作,实际执行的是近似于基本类型比较。

       String对象和字符串连接池:

       引号内包含文本是String类特有创建对象的方式.但是"=="返回的结果是true,为什么呢?因为在JVM内,存在字符串池,其中保存着很多 String对象,并且可以被共享使用,因此它提高了效率.字符串池由String类维护,我们可以调用intern()方法来访问字符串池。当运用引号内包含文本创建对象时,所创建的对象是加入到字符串池里面的.如果要创建下一个字符串对象,JVM首先会到字符串池中寻找,是否存在对应的字符串对象,如果存在,则返回一个己存在对象的对象的引用给要创建的对象引用.如果不存在,才会创建一个新的对象,并返回一个新对象的对象引用给要创建的对象引用.以上这段话理解起来可能比较拗口.用代码理解就是str2和str1是两个对象引用,并指向了同一个对象.所以'=='返回的是true.

       只有引号内包含文本创建对象才会将创建的对象放入到字符串池。String str = new String("abc")这种方法创建的字符串对象是不放入到字符串池的。所以,引号内包含文本创建对象的性能要比后来那种方法创建字符串对象的性能要好。

String str1 = "abc";

String str2 = "abc";

String str3 = str1+str2;   //这种创建方式是不放入字符串池的.

String str4 = str1+"cd";   //这种创建方式是不放入字符串池的.

String str5 = "ab"+str2;   //这种创建方式是不放入字符串池的.

String str6 = "ab"+"cd"; //这种创建方式是放入字符串池的.这种情况实际上是创建了1个对象,abcd"1个对象

String str7 = "abcd";

System.out.println(str1==str2); //返回ture

System.out.println(str6==str7); //返回ture

       另一个问题:

我们首先来看一段 Java代码:

String str=new String("abc");  

紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢?相信大家对这道题并不陌生,答案也是众所周知的,2个。接下来我们就从这道题展开,一起回顾一下与创建String对象相关的一些JAVA知识。

我们可以把上面这行代码分成String str、=、"abc"和new String()四部分来看待。String str只是定义了一个名为str的String类型的变量,因此它并没有创建对象;=是对变量str进行初始化,将某个对象的引用(或者叫句柄)赋值给它,显然也没有创建对象;现在只剩下new String("abc")了。那么,new String("abc")为什么又能被看成"abc"和new String()呢?我们来看一下被我们调用了的String的构造器:

Java代码

public String(String original) {    
//other code ...    
}  
大家都知道,我们常用的创建一个类的实例(对象)的方法有以下两种:
我们正是使用new调用了String类的上面那个构造器方法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正是"abc"。

使用new创建对象是调用Class类的newInstance方法,利用反射机制创建对象。

      equals方法。用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如,对于下面的代码:

String a=new String("foo");

String b=new String("foo");

两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,他们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a==b即返回false,而这两个对象中内容是相同的,所以,表达式a.equals(b)将返回true。

       在实际开发中,我们经常要比较传递进行来的字符串内容是否相等,许多人稍不注意就使用==进行比较了,这是错误的,有大量这样的错误。记住,字符串的比较基本都是使用equals方法。

       ⑤如果一个类没有定义equals方法。它将继承Object类的equals方法,Object类的equals方法的实现代码如下:

boolean equals(Object o){

return this==o;

}

这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object类继承的)就是使用==操作符,也是比较两个变量指向的对象是否是同一个对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可以认为两个对象的内容是相同的。

      

     示例代码

  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         Integer p = 1;  
  4.          Integer q = 1;
  5.         Integer i = new Integer(1);  
  6.          Integer j = new Integer(1);
  7.         if(p == q){  
  8.              System.out.println("integer:p == q"); //实际结果
  9.          }else{  
  10.              System.out.println("integer:p != q");  
  11.          }
  12.         if(p.equals(q)){
  13.              System.out.println("integer:p.equals(q)"); //实际结果
  14.          }else{  
  15.              System.out.println("integer:p.equals(q)");
  16.          }
  17.         if(i == j){  
  18.              System.out.println("int:i == j");  
  19.          }else{  
  20.              System.out.println("int:i != j"); //实际结果
  21.          }  
  22.         if(i.equals(j)){  
  23.              System.out.println("integer:i.equals(j)");//实际结果
  24.          }else{
  25.              System.out.println("integer:!i.equals(j)");
  26.          }  
  27.          String a = "abc";  
  28.          String b = "abc";  
  29.          String c = new String("abc");  
  30.          String d = new String("abc");  
  31.         if(a == b){  
  32.              System.out.println("abc对象相等"); //实际结果
  33.          }else{  
  34.              System.out.println("abc对象不相等");  
  35.          }  
  36.         if(a.equals(b)){  
  37.              System.out.println("ab相等"); //实际结果
  38.          }else{  
  39.              System.out.println("ab不相等");  
  40.          }  
  41.         if(c.equals(d)){  
  42.              System.out.println("cd相等"); //实际结果
  43.          }else{  
  44.              System.out.println("cd不相等");  
  45.          }  
  46.         if(c == d){  
  47.              System.out.println("cd对象相等");  
  48.          }else{  
  49.              System.out.println("cd对象不相等"); //实际结果
  50.          }  
  51.      }  
  52.   
  53. }

中软国际电子政务部Jeff Chi总结,转载请说明出处。

----------------------------------------------------------------------------------

深入探讨equals:

===================
转自硅谷动力

equals方法的重要性毋须多言,只要你想比较两个对象是不是同一对象,你就应该实现equals方法,让对象用你认为相等的条件来进行比较.

下面的内容只是API的规范,没有什么太高深的意义,但我之所以最先把它列在这儿,是因为这些规范在事实中并不是真正能保证得到实现.

1.对于任何引用类型, o.equals(o) == true成立.

2.如果 o.equals(o1) == true 成立,那么o1.equals(o)==true也一定要成立.

3.如果 o.equals(o1) == true 成立且 o.equals(o2) == true 成立,那么

o1.equals(o2) == true 也成立.

4.如果第一次调用o.equals(o1) == true成立,在o和o1没有改变的情况下以后的任何次调用都成立.

5.o.equals(null) == true 任何时间都不成立.

以上几条规则并不是最完整的表述,详细的请参见API文档.对于Object类,它提供了一个最最严密的实现,那就是只有是同一对象时,equals方法才返回true,也就是人们常说的引用比较而不是值比较.这个实现严密得已经没有什么实际的意义, 所以在具体子类(相对于Object来说)中,如果我们要进行对象的值比较,就必须实现自己的equals方法.先来看一下以下这段程序:

public boolean equals(Object obj)

{

if (obj == null) return false;

if (!(obj instanceof FieldPosition))

return false;

FieldPosition other = (FieldPosition) obj;

if (attribute == null) {

if (other.attribute != null) {

return false;

}

}

else if (!attribute.equals(other.attribute)) {

return false;

}

return (beginIndex == other.beginIndex

& endIndex == other.endIndex

&& field == other.field);

}

这是JDK中java.text.FieldPosition的标准实现,似乎没有什么可说的. 我信相大多数或绝大多数程序员认为,这是正确的合法的equals实现.毕竟它是JDK的API实现啊. 还是让我们以事实来说话吧:

package debug

;import java.text.*;

public class Test {

public static void main(String[] args) {

FieldPosition fp = new FieldPosition(10);

FieldPosition fp1 = new MyTest(10);

System.out.println(fp.equals(fp1));

System.out.println(fp1.equals(fp));

}

}

class MyTest extends FieldPosition{

int x = 10;

public MyTest(int x){

super(x);

this.x = x;

}

public boolean equals(Object o){

if(o==null) return false;

if(!(o instanceof MyTest )) return false;

return ((MyTest)o).x == this.x;

}

}

运行一下看看会打印出什么:

System.out.println(fp.equals(fp1));打印true

System.out.println(fp1.equals(fp));打印flase

两个对象,出现了不对称的equals算法.问题出在哪里(脑筋急转弯:当然出在JDK实现的BUG)?我相信有太多的程序员(除了那些根本不知道实现 equals方法的程序员外)在实现equals方法时都用过instanceof运行符来进行短路优化的,实事求是地说很长一段时间我也这么用过。

太多的教程,文档都给了我们这样的误导。而有些稍有了解的程序员可能知道这样的优化可能有些不对但找不出问题的关键。另外一种极端是知道这个技术缺陷的骨灰级专家就提议不要这样应用。我们知道,"通常"要对两个对象进行比较,那么它们"应该"是同一类型。所以首先利用instanceof运算符进行短路优化,如果被比较的对象不和当前对象是同一类型则不用比较返回false。

但事实上,"子类是父类的一个实例",所以如果子类 o instanceof 父类,始终返回true,这时肯定不会发生短路优化,下面的比较有可能出现多种情况,一种是不能造型成父类而抛出异常,另一种是父类的private 成员没有被子类继承而不能进行比较,还有就是形成上面这种不对称比较。可能会出现太多的情况。

那么,是不是就不能用instanceof运算符来进行优化?答案是否定的,JDK中仍然有很多实现是正确的,如果一个class是final的,明知它不可能有子类,为什么不用 instanceof来优化呢?为了维护SUN的开发小组的声誉,我不说明哪个类中,但有一个小组成员在用这个方法优化时在后加加上了加上了这样的注释:

if (this == obj) // quick check

return true;

if (!(obj instanceof XXXXClass)) // (1) same object?

return false;

可能是有些疑问,但不知道如何做(不知道为什么没有打电话给我......)那么对于非final类,如何进行类型的quick check呢?

if(obj.getClass() != XXXClass.class) return false;

用被比较对象的class对象和当前对象的class比较,看起来是没有问题,但是,如果这个类的子类没有重新实现equals方法,那么子类在比较的时候,obj.getClass() 肯定不等于XXXCalss.class, 也就是子类的equals将无效,所以

if(obj.getClass() != this.getClass()) return false;

才是正确的比较。另外一个quick check是if(this==obj) return true;

是否equals方法比较的两个对象一定是要同一类型?上面我用了"通常",这也是绝大多数程序员的愿望,但是有些特殊的情况,我们可以进行不同类型的比较,这并不违反规范。但这种特殊情况是非常罕见的,一个不恰当的例子是,Integer类的equals可以和Sort做比较,比较它们的value是不是同一数学值。(事实上JDK的API中并没有这样做,所以我才说是不恰当的例子)在完成quick check以后,我们就要真正实现你认为的“相等”。对于如果实现对象相等,没有太高的要求,比如你自己实现的“人”类,你可以认为只要name相同即认为它们是相等的,其它的sex, ago都可以 不考虑。这是不完全实现,但是如果是完全实现,即要求所有的属性都是相同的,那么如何实现equals方法?

class Human{

private String name;

private int ago;

private String sex;

....................

public boolean equals(Object obj){

quick check.......

Human other = (Human)ojb;

return this.name.equals(other.name) && this.ago == ohter.ago && this.sex.equals(other.sex);

}

}

这是一个完全实现,但是,有时equals实现是在父类中实现,而要求被子类继承后equals能正确的工

作,这时你并不事实知道子类到底扩展了哪些属性,所以用上面的方法无法使equals得到完全实现。

一个好的方法是利用反射来对equals进行完全实现:

public boolean equals(Object obj){

quick check.......

Class c = this.getClass();

Filed[] fds = c.getDeclaredFields();

for(Filed f:fds){

if(!f.get(this).equals(f.get(obj)))

return false;

}

return true;

}

为了说明的方便,上明的实现省略了异常,这样的实现放在父类中,可以保证你的子类的equals可以按你的愿望正确地工作。关于equals方法的最后一点是:如果你要是自己重写(正确说应该是履盖)了equals方法,那同时就一定要重写hashCode().这是规范,否则.............

我们还是看一下这个例子:

public final class PhoneNumber {

private final int areaCode;

private final int exchange;

private final int extension;

public PhoneNumber(int areaCode, int exchange, int extension) {

rangeCheck(areaCode, 999, "area code");

rangeCheck(exchange, 99999999, "exchange");

rangeCheck(extension, 9999, "extension");

this.areaCode = areaCode;

this.exchange = exchange;

this.extension = extension;

}

private static void rangeCheck(int arg, int max, String name) {

if(arg < 0  ||  arg > max)

throw new IllegalArgumentException(name + ": " + arg);

}

public boolean equals(Object o) {

if(o == this)

return true;

if(!(o instanceof PhoneNumber))

return false;

PhoneNumber pn = (PhoneNumber)o;

return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode;

}

}

注意这个类是final的,所以这个equals实现没有什么问题。我们来测试一下:

public static void main(String[] args) {

Map hm = new HashMap();

PhoneNumber pn = new PhoneNumber(123, 38942, 230);

hm.put(pn, "I love you");

PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);

System.out.println(pn);

System.out.println("pn.equals(pn1) is " + pn.equals(pn1));

System.out.println(hm.get(pn1));

System.out.println(hm.get(pn));

}

既然pn.equals(pn1),那么我put(pn,"I love you")后,get(pn1)为什么是null呢?

答案是因为它们的hashCode不一样,而hashMap就是以hashCode为主键的。所以规范要求,如果两个对象进行equals比较时如果返回true,那么它们的hashcode要求返回相等的值。

转载于:https://www.cnblogs.com/bluewelkin/p/3582035.html

实现几何计算,然后实现凸包、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.csdn.net/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实现,不依赖任何外部库,可以直接在浏览器中运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值