计算几何系列 —— 扫描线算法

       本系列文章力求以简洁易懂的文字介绍计算几何中的基本概念,使读者快速入门,故不追求难度和深度,仅起到抛砖引玉的作用。     

       在我们的计算几何中,乃至基本的几何学中我们都需要解决一类问题,那就是几何图形的面积交并,周长交并问题,在计算几何中我们也常常遇到这样的问题,在这里我们介绍一种神奇的用来解决这类问题的算法,叫做扫描线(scan line algorithm)算法,是2018年公布的计算机科学技术名词,主要倾向于线段树+离散化这类思想,然后扫描求解过程有点像积分,那么我们开始讲解扫描线

      我们先来看一个最基础的问题: 

      看到n的数据范围是:

      看到这个以后,我们是要去打ACM的话,肯定只考虑100%的数据,因此O(n^2)的算法直接被我们抛弃,这样子主要考虑O(n)或者O(nlogn)的算法,那么就是我们今天的算法:扫描线算法,时间复杂度是O(nlogn)的,接下来讲一下这个算法的运作过程。

扫描线:假设有一条扫描线从一个图形的下方扫向上方(或者左方扫到右方),那么通过分析扫描线被图形截得的线段就能获得所要的结果(相当于是每次算出每个单位长度上的微分,最后积起来这样的一个过程)。该过程可以用线段树进行加速。

       由于都是矩形,因此运用扫描线以后,面积的求法其实可以简化为 ∑截线段长度×扫过的高度,这也是扫描线算法最基础的应用。

  问题在于如何才能模拟扫描线从下向上扫过图形,并且快速计算出当前扫描线被截得的长度。

  现在假设,扫描线每次会在碰到横边的时候停下来,如图👇:

      简单来说,可对图形面积产生影响的元素,也就是这些横边左右端点的坐标。那么还有一个很严重的问题就是,如何加边减边的问题,就是在某个区间怎么判断这条边还在吗?这个很简单,我们只需要在一个矩形的上下两边加上不同的权值,按照扫描方向,假如方向是从下往上,那么下方的边权就是1,上方的边权就是-1,这样子就解决了边的存在问题了:       然后把所有的横边按照矩形y坐标升序排序。这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边。那么就能保证扫描线所截的长度永远非负了。这样操作以后,就可以和线段树扯上关系,我们只需要在输入的时候将每个矩形的横坐标剥离出来,放在x轴上进行离散化。(其实就是把所有点的横坐标存到X[]里,然后升序排个序,最后去重。)

       👆:在这个例子中,4个端点的纵投影总共把x轴切割成了5部分。取中间的3部分线段,建立一棵线段树,其每个端点维护一条线段(也就是一个区间)的信息:

  1. 该线段被覆盖了多少次(被多少个矩形所覆盖)。
  2. 该线段内被整个图形所截的长度是多少。

      显然,只要一条线段被覆盖,那么它肯定被图形所截。所以,整个问题就转化为了一个区间查询问题,即:每次将当前扫描线扫到的边对应的信息按照之前赋上的权值更新,然后再查询线段树根节点的信息,最后得到当前扫描线扫过的面积。这就可以用线段树来实现了(线段树:顾名思义,就是树上的结点为一条条线段~),接下来我们来简单看一下模拟过程:

       1.初始化,取点存线段,建立线段树:

       2:扫描第一条边:

         3:扫描第二条边:

          4:扫描第三条边:

          5:扫描第四条边:

      那么对于这样的一个基本情况就是这么简单的模拟,我们只需要建立一棵线段树来存储这些线段就OK了,但是问题来了,我们应该存储线段的那些信息?这些线段应该怎么存?(开区间 or 闭区间 or 半开半闭)

      所以为了保证线段之间的兼容性,我们一般这么考虑:考虑把线段树每个节点x对应的区间(tree[x].l,tree[x].r)不变,改变区间和横边的映射关系,具体为:节点x对应[X[tree[x].l],X[tree[x].r + 1]这条横边,如果不这样想可能会出现线段之间的端点重合问题,实在是细腻啊~,所以我们建树这样子建(保存的信息还是一样,提取的时候搞一下操作):

        然后我们只需要在更新线段树的时候做操作~:

    OK了😄,算法思路和细节方面的问题我们都解决了,接下来就看一下这个算法的板子:

#include<bits/stdc++.h>
#include<algorithm>
#define ll long long
using namespace std;
const int MAXN = 1e6 + 10;
int n,i,x1,x2,yy,y2;
int X[MAXN << 1];
inline int read1(){char ch;
	while((ch = getchar()) < '0' || ch > '9'); int res = ch - 48;
	while((ch = getchar()) >= '0' && ch <= '9') res = res * 10 + ch - 48;
	return res;}
inline long long read2(){char ch;
	while((ch = getchar()) < '0' || ch > '9'); long long res = ch - 48;
	while((ch = getchar()) >= '0' && ch <= '9') res = res * 10 + ch - 48;
	return res;}
struct ScanLine{ 
    long long l,r,h; 
	int mark; 
    bool operator<(const ScanLine &rhs) const {return h < rhs.h;}
}line[MAXN << 1];
struct SegTree{int l,r,sum; long long len; }Tree[MAXN << 2];
void Build_Tree(int k,int l,int r)
{
	Tree[k].l = l,Tree[k].r = r,Tree[k].sum = 0,Tree[k].len = 0;
	if(l == r) return;
	int mid = (l + r) >> 1;
	Build_Tree(k << 1,l,mid);
	Build_Tree(k << 1 | 1,mid + 1,r);
	return;
}
void Push_Up(int k)
{
	int l = Tree[k].l,r = Tree[k].r;
	if(Tree[k].sum) Tree[k].len = X[r + 1] - X[l];
	   else Tree[k].len = Tree[k << 1].len + Tree[k << 1 | 1].len;
}
void Update_Tree(int k,long long L,long long R,int c)
{
	int l = Tree[k].l,r = Tree[k].r;
	if(X[r + 1] <= L || X[l] >= R) return;
	if(X[l] >= L && X[r + 1] <= R)
	{
		Tree[k].sum = Tree[k].sum + c;
		Push_Up(k);
		return;
	}
	int mid = (l + r) >> 1;
	Update_Tree(k << 1,L,R,c);
	Update_Tree(k << 1 | 1,L,R,c);
	Push_Up(k);
}
int main()
{
	n = read1();
    memset(X,0,sizeof(X));
	for(int i = 1;i <= n;i++)
	{
		{x1 = read2();yy = read2();x2 = read2();y2 = read2();}
		X[2 * i - 1] = x1,X[2 * i] = x2;
		line[2 * i - 1] = (ScanLine) {x1,x2,yy,1};
		line[2 * i] = (ScanLine) {x1,x2,y2,-1};
	}
	n = n << 1;
	sort(line + 1,line + n + 1);
	sort(X + 1,X + n + 1);
	int tot = unique(X + 1,X + n + 1) - X - 1;
	Build_Tree(1,1,tot - 1);
	long long ans = 0;
	for(int i = 1;i < n;i++)
	{
		Update_Tree(1,line[i].l,line[i].r,line[i].mark);
		ans = ans + Tree[1].len * (line[i + 1].h - line[i].h);
	}
	cout<<ans<<endl;
	return 0;
}

好,我们趁热打铁(当然不是那个打铁~),再来看一道比较模板的扫描线题🤔:

     刚刚算过了矩形面积并之后,我们第一反应就可以想到截取线段,然后建立线段树维护,那么单单从下到上只能算出宽,不能算出高,所以很简单,我们再从左到右做一次同样的操作就行了~一样的道理,每次得到的贡献需要和上一次做差,表示这次新增或删除的贡献~✌

      由于每次扫描线扫描的过程和求面积并是相似的,所以这次我们不手动模拟了,那个之前忘记讲了,这个存放矩形其实也有一个结构体E(l,r,d,h):

      👆:感觉不是特别重要,但是这个计算几何的知识体系好歹完整一点😄

        OK,我们先来看看这一种做两次扫描线算法能不能再做简化,也就是只做一次扫描线,毕竟矩形的高与宽之间的联系还是非常大的,所以我们通过画图计算可以发现以下结论:

        1.纵边总长度=∑2×被截得的线段条数×扫过的高度

        2.横边总长度=∑∣上次截得的总长−现在截得的总长∣

          👆:大家可以自己模拟计算一下~

       那么事情看起来就简单很多了,我们只需要在原来线段树中存储的信息中再加上一个“线段的条数”,然后纵边和横边分开计算即可,代码如下😄:

#include<bits/stdc++.h>
#include <algorithm>
#define lson (x << 1)
#define rson (x << 1 | 1)
using namespace std;
#define Temp template<typename T>
Temp inline void read(T &x)
{
	x = 0;
	T w = 1,ch = getchar();
	while(! isdigit(ch) && ! ch == '-')
	    ch = getchar();
	if(ch == '-')
	    w = -1,ch = getchar();
	while(isdigit(ch))
	    x = (x << 3) + (x << 1) + (ch ^ '0'),ch = getchar();
	x = x * w;
}
const int MAXN = 1e5 + 10;
int n,X[MAXN << 1];
int x1,yy,x2,y2,pre = 0;
struct ScanLine{
	int l, r, h, mark;
	if(h == rhs.h) return mark > rhs.mark;
    return h < rhs.h;
}line[MAXN << 1];
struct SegTree{
	int l,r,sum,len,c;
    bool lc,rc;
}tree[MAXN << 2];
void Build_Tree(int x, int l, int r) 
{
	tree[x].l = l;
	tree[x].r = r;
	tree[x].lc = tree[x].rc = false;
	tree[x].sum = tree[x].len = 0;
	tree[x].c = 0;
	if(l == r)
		return;
	int mid = (l + r) >> 1;
	Build_Tree(lson, l, mid);
	Build_Tree(rson, mid + 1, r);
}
void Push_Up(int x) 
{
	int l = tree[x].l,r = tree[x].r;
	if(tree[x].sum) 
	{
		tree[x].len = X[r + 1] - X[l];
		tree[x].lc = tree[x].rc = true;
		tree[x].c = 1;
	}
	else 
	{
		tree[x].len = tree[lson].len + tree[rson].len;
		tree[x].lc = tree[lson].lc, tree[x].rc = tree[rson].rc;
		tree[x].c = tree[lson].c + tree[rson].c;
		if(tree[lson].rc && tree[rson].lc)
			tree[x].c -= 1;
	}
}

void Update_Tree(int x, int L, int R, int c) 
{
	int l = tree[x].l, r = tree[x].r;
	if(X[l] >= R || X[r + 1] <= L) return;
	if(L <= X[l] && X[r + 1] <= R) 
	{
		tree[x].sum += c;
		Push_Up(x);
		return;
	}
	Update_Tree(lson, L, R, c);
	Update_Tree(rson, L, R, c);
	Push_Up(x);
}

ScanLine make_line(int l, int r, int h, int mark) 
{
	ScanLine res;
	res.l = l, res.r = r,
	res.h = h, res.mark = mark;
	return res;
}
int main() 
{
	read(n);
	for(int i = 1; i <= n; i++) 
	{
		read(x1),read(yy),read(x2),read(y2);
		line[i * 2 - 1] = make_line(x1, x2, yy, 1);
		line[i * 2] = make_line(x1, x2, y2, -1);
		X[i * 2 - 1] = x1, X[i * 2] = x2;
	}
	n = n << 1;
	sort(line + 1, line + n + 1);
	sort(X + 1, X + n + 1);
	int tot = unique(X + 1, X + n + 1) - X - 1;
	Build_Tree(1, 1, tot - 1);
	int res = 0;
	for(int i = 1; i < n; i++) 
	{
		Update_Tree(1, line[i].l, line[i].r, line[i].mark);
		res = res + abs(pre - tree[1].len);
		pre = tree[1].len;
		res = res + 2 * tree[1].c * (line[i + 1].h - line[i].h);
	}
	res = res + line[n].r - line[n].l;
	cout<<res<<endl;
	return 0;
}

     👆:只能说上面求周长并的代码跟面积并的代码非常相似,可以相互比较理解一下

         那么简单的扫描线算法到这里为止,我们已经会使用扫描线算法来求得多个矩形的面积并和周长并 —— 非常基础的一些计算几何问题,当然这里还涉及到了有关线段树加速的知识,如果不了解线段树的同学通过这个算法也可以加深对线段树的理解[doge]。

         OK,上点强度~✌,接下来让我们想想除了矩形,三角形,梯形,圆这些几何图形的面积并是否也可以用扫描线求出?🤔

         👆:等腰三角形的扫描线模型

          👆:圆离散后的扫描线模型

     我们仔细看一看上面的两种模型,都是离散+扫描,经典的扫描线算法啊,可行性映入眼帘了, 所以~

         ————  答案是可以的,因为之前提过扫描线算法的思想其实接近于微积分,即求出每个单位内的微分作积,而求任何几何图形的面积,在数学里我们使用的最多的就是微积分的方法,计算几何中,我们也常常使用自适应辛普森积分公式,格林公式等微积分的方法来求相关值,所以扫描线算法这种模拟微分也是可以的,并且精度方面占点优势:

             👇:辛普森积分公式

                👇:格林公式

        这边以圆的面积并为例,我们采用三种方法,一种是采用扫描线+辛普森公式,一种是圆的离散化,还有一种是基本的几何方法~,👇选择性理解一下叭,这个还是有点难度~:

           1.圆的离散化👇:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 55;
const int MAXN = maxn * maxn + 3 * maxn;
const double Eps = 1e-10;
 
#define Temp template<typename T>
Temp inline void read(T &x)
{
    x = 0;
	T w = 1,ch = getchar();
    while(!isdigit(ch) && ch != '-')
	    ch = getchar();
    if(ch == '-')
	    w = -1,ch = getchar();
    while(isdigit(ch))
	    x = (x << 3) + (x << 1) + (ch ^ '0'),ch = getchar();
    x = x * w;
}
 
int Sign(double x) { if(fabs(x) < Eps) return 0;if(x > 0) return 1;if(x < 0) return -1;}
 
int Dcmp(double x,double y) { if(fabs(x - y) < Eps) return 0;if(x > y) return 1;if(x < y) return -1;}
 
double sqr(double x) { return x * x;}
 
struct Point{
	double x,y;
	Point(double x = 0,double y = 0) : x(x) , y(y) {};
}point[MAXN];
 
typedef Point Vector;
 
Vector operator + (Vector Alpha,Vector Beta) { return Vector(Alpha.x + Beta.x,Alpha.y + Beta.y);}
 
Vector operator - (Vector Alpha,Vector Beta) { return Vector(Alpha.x - Beta.x,Alpha.y - Beta.y);}
 
Vector operator * (Vector Alpha,double x) { return Vector(Alpha.x * x,Alpha.y * x);}
 
Vector operator / (Vector Alpha,double x) { return Vector(Alpha.x / x,Alpha.y / x);}
 
double Dis(Point Alpha,Point Beta)
{
	return sqrt(sqr(Alpha.x - Beta.x) + sqr(Alpha.y - Beta.y));
}
 
struct Tcir{
	double r;
	Point o;
};
 
struct Tinterval
{
	double x,y,Area,mid;
	int type;
	Tcir owner;
	void area(double l,double r)
	{
		double len = sqrt(sqr(l - r) + sqr(x - y));
		double d = sqrt(sqr(owner.r) - sqr(len) / 4.0);
		double angle = atan(len / 2.0 / d);
		Area = fabs(angle * sqr(owner.r) - d * len / 2.0);
	}
}inter[maxn];
double x[MAXN],l,r;
int n,N,Nn;
 
bool cmpR(Tcir Alpha,Tcir Beta)
{
	return Alpha.r > Beta.r;
}
 
void Get(Tcir owner,double x,double &l,double &r)
{
	double y = fabs(owner.o.x - x);
	double d = sqrt(fabs(sqr(owner.r) - sqr(y)));
	l = owner.o.y + d;
	r = owner.o.y - d;
}
 
void Get_Interval(Tcir owner,double l,double r)
{
	Get(owner,l,inter[Nn].x,inter[Nn + 1].x);
	Get(owner,r,inter[Nn].y,inter[Nn + 1].y);
	Get(owner,(l + r) / 2.0,inter[Nn].mid,inter[Nn + 1].mid);
	inter[Nn].owner = inter[Nn + 1].owner = owner;
	inter[Nn].area(l,r),inter[Nn + 1].area(l,r);
	inter[Nn].type = 1,inter[Nn + 1].type = -1;
	Nn = Nn + 2;
}
bool cmp(Tinterval Alpha,Tinterval Beta)
{
	return Alpha.mid > Beta.mid + Eps;
}
void Add(double xx)
{
	x[N] = xx;
	N = N + 1;
}
double dist2(Point Alpha,Point Beta)
{
	return sqr(Dis(Alpha,Beta));
}
Point Rotate(Point p,double cost,double sint)
{
	double x = p.x,y = p.y;
	return Point(x * cost - y * sint,x * sint + y * cost);
}
pair<Point,Point>crosspoint(Point ap,double ar,Point bp,double br)
{
	double d = (ap - bp).norm();
	double cost = (ar * ar + d * d - br * br) / (2 * ar * d);
	double sint = sqrt(1. - cost * cost);
	Point v = (bp - ap) / (bp - ap).norm() * ar;
	return make_pair(ap + Rotate(v,cost,-sint),ap + Rotate(v,cost,sint));
}
double getUnion(int n,Tcir a[])
{
	int p = 0;
	sort(a,a + n,cmpR);
	for(int i = 0;i < n;i++)
	{
		bool f = true;
		for(int j = 0;j < i;j++)
		if(dist2(a[i].o,a[j].o) <= sqr(a[i].r - a[j].r) + Eps)
		{
			f = false;
			break;
		}
		if(f == true)
		{
			a[p] = a[i];
			p = p + 1;
		}
	}
	n = p;
    N = 0;
    for(int i = 0;i < n;i++)
    {
    	Add(a[i].o.x - a[i].r);
        Add(a[i].o.x + a[i].r);
        Add(a[i].o.x);
        for(int j = i + 1;j < n;j++)
        if(dist2(a[i].o,a[j].o) <= sqr(a[i].r + a[j].r) + Eps)
		{
			pair<Point,Point> cross=crosspoint(a[i].o,a[i].r,a[j].o,a[j].r);
			Add(cross.first.x);
			Add(cross.second.x);
		} 
	}
	sort(x,x + N);
	p = 0;
	for(int i = 0;i < N;i++)
	if(! i || fabs(x[i] - x[i - 1]) > Eps) 
	{
		x[p] = x[i];
		p = p + 1;
	}
	N = p;
	
	double ans = 0;
	for(int i = 0;i + 1 < N;i++)
	{
		l = x[i],r = x[i + 1];
		Nn = 0;
		for(int j = 0;j < n;j++)
		if(fabs(a[j].o.x - l) < a[j].r + Eps && fabs(a[j].o.x - r) < a[j].r + Eps)
		    Get_Interval(a[j],l,r);
		if(Nn)
		{
			sort(inter,inter + Nn,cmp);
			int cnt = 0;
			for(int i = 0;i < Nn;i++)
			{
				if(cnt > 0)
				{
					ans = ans + (fabs(inter[i - 1].x - inter[i].x)
					          + fabs(inter[i - 1].y - inter[i].y)) * (r - l) / 2.0;
					ans = ans + inter[i - 1].type * inter[i - 1].Area;
					ans = ans - inter[i].type * inter[i].Area;
				}
				cnt = cnt + inter[i].type;
			}
		}
	}
	return ans;
}
 
int main()
{
	read(n);
	Tcir Circle[MAXN];
	for(int i = 0;i < n;i++)
	{
		double xxx,yyy,rrr;
		scanf("%lf%lf%lf",&xxx,&yyy,&rrr);
		Circle[i].o.x = xxx,Circle[i].o.y = yyy;
		Circle[i].r = rrr;
	}
	printf("%.10f\n",getUnion(n,Circle));
	return 0;
}

         2:👇基本的几何方法:

    1.如图所示,将圆的面积剖分成若干个多边形的面积与若干个弓形的面积。多边形的边就是圆的交点构成的不被其他圆覆盖的弦。计算有向面积的话,可以看到中间“洞”的面积恰好被顺时针的多边形包围,因此会被减去。请注意,必须去重复的圆,否则答案会有重复计算的面积

        代码:

double Cross(Point Alpha,Point Beta)
{
	return Alpha.x * Beta.y - Alpha.y * Beta.x;
}
struct Circle{
	Point p;
	double r;
	bool operator < (Circle o)
	{
		if(Sign(r - o.r) != 0) return Sign(r - o.r) == -1;
		if(Sign(p.x - o.p.x) != 0)
		{
			return Sign(p.x - o.p.x) == -1;
 		}
		return Sign(p.y - o.p.y) == -1;
	}
	bool operator == (Circle o)
	{
		return Sign(r - o.r) == 0 && Sign(p.x - o.p.x) == 0 && Sign(p.y - o.p.y) == 0;
	}
}; 
inline pair<Point,Point>crosspoint(Circle Alpha,Circle Beta)
{
	return crosspoint(Alpha.p,Alpha.r,Beta.p,Beta.r);
}
Circle c[1000],tc[1000];
int n,m;
struct Node{
	Point p;
	double a;
	int d;
	Node(Point p,double a,int d) : p(p) , a(a) , d(d) {}
	bool operator < (Node o) 
	{
		return a < o.a;
	}
};
double arg(Point p)
{
	return arg(complex<double>(p.x,p.y));
}
double solve()
{
	sort(tc,tc + m);
	m = unique(tc,tc + m) - tc;
	for(int i = m - 1;i >= 0;i--)
	{
		bool ok = true;
		for(int j = i + 1;j < m;j++)
		{
			double d = (tc[i].p - tc[j].p).norm();
			if(Sign(d - abs(tc[i].r - tc[j].r)) <= 0)
			{
				ok = false;
				break;
			}
		}
		if(ok == true) 
		{
			c[n] = tc[i];
			n = n + 1;
		}
	}
	double ans = 0;
	for(int i = 0;i < n;i++)
	{
		vector<Node> event;
		Point boundary = c[i].p + Point(-c[i].r,0);
		event.push_back(Node(boundary,-pi,0));
		event.push_back(Node(boundary,pi,0));
		for(int j = 0;j < n;j++)
		{
			if(i == j) continue;
			double d = (c[i].p - c[j].p).norm();
			if(Sign(d - (c[i].r + c[j].r)) < 0)
			{
				pair<Point,Point> ret = crosspoint(c[i],c[j]);
				double x = arg(ret.first - c[i].p);
				double y = arg(ret.second - c[i].p);
				if(Sign(x - y) > 0)
				{
					event.push_back(Node(ret.first,x,1));
					event.push_back(Node(boundary,pi,-1));
					event.push_back(Node(boundary,-pi,1));
					event.push_back(Node(ret.second,y,-1));
				}else
				{
					event.push_back(Node(ret.first,x,1));
					event.push_back(Node(ret.second,y,-1));
				}
			}
		}
		sort(event.begin(),event.end());
		int sum = event[0].d;
		for(int j = 1;j <(int)event.size();j++)
		{
			if(sum == 0)
			{
				ans = ans + cross(event[j - 1].p,event[j].p) / 2;
				double x = event[j - 1].a;
				double y = event[j].a;
				double area = c[i].r * c[i].r * (y - x) / 2;
				Point v1 = event[j - 1].p - c[i].p;
				Point v2 = event[j].p - c[i].p;
				area = area - Cross(v1,v2) / 2;
				ans = ans + area;
			}
		}
	}
	return ans;
}

          3:辛普森积分👇:

 

      那么小有难度的圆的面积并也被我们轻松解决了,ok,这篇博文实在是有点长了,再讲三角形和梯形就没必要了毕竟大家思路都懂了对叭~[doge] ,所以给几道可以去试试的例题:

           三角形 - 洛谷

           [HNOI2012] 三角形覆盖问题 - 洛谷 

           POJ 1177

           POJ 1151 

    这个扫描线算法小有难度但是真的很有趣啊,跟旋转卡壳一样有趣不是吗[doge],我要好好学旋转卡壳辣,今天就先到这里~

  • 56
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
计算机图形学作业题 1. 计算机中由图形的形状参数(方程或分析表达式的系数,线段的端点坐标等)加属性参数 (颜色、线型等)来表示图形称图形的参数表示;枚举出图形中所有的点称图形的点阵 表示,简称为图像(数字图像) 2. 什么是计算机图形学?计算机图形学有哪些研究内容? 3. 计算机图形学有哪些应用领域? 4. 计算机图形学有哪些相关学科分支?它们的相互关系是怎样的? 5. 图形系统的软件系统由哪些软件组成?举例说明。 6. 了解计算机图形系统的硬件。 7. 什么是显示器的分辨率、纵横比、刷新率? 8. 什么是像素、分辨率、颜色数?分辨率、颜色数与显存的关系? 分辨率M(N、颜色个数K与显存大小V的关系: 例:分辨率为1024像素/行(768行/帧,每像素24位(bit)颜色(224种颜色)的显示 器,所需的显存为:1024(768(24位(bit)=1024(768(24/8=2359296字节(byte)。 或:每像素16777216种颜色(24位真彩色),1024(768的分辨率,所需显存为:102 4(768(log216777216位显存=2359296字节显存。 9. 什么是图元的生成?分别列举两种直线和圆扫描转换算法。 10. OpenGL由核心库GL(Graphics Library)和实用函数库GLU(Graphics Library Utilities)两个库组成。 11. 区域填充算法要求区域是连通的,因为只有在连通区域中,才可能将种子点的颜色扩 展到区域内的其它点。 区域可分为 向连通区域和 向连通区域。区域填充算法有 填充算法和 填充算法。 12. 字符生成有哪两种方式? 点阵式(bitmap fonts点阵字——raster光栅方法):采用逐位映射的方式得到字符的点阵和编码——字模位 点阵。 笔画式(outline fonts笔画字——stroke方法):将字符笔画分解为线段,以线段端点坐标为字符字模的编 码。 13. 图形信息包含图形的 和 。 14. 什么是图形变换?图形变换只改变图形的 不改变图形的 。图形变换包括 和 ( )。 15. 熟练掌握二维图形的齐次坐标表示、平移、比例、旋转、对称变换以及复合变换的方 法和原则。 16. 图形的几何变换包括 、 、 、 、 ;图形连续作一次以上的几何变换称 变换。 17. 试写出图示多边形绕点A(xo,yo)旋转的变换矩阵。要求写出求解过程及结果。 18. 试写出针对固定参考点、任意方向的比例变换矩阵。 19. 试写出对任意直线y=mx+b的对称变换矩阵。 20. 什么是窗口?什么是视区?什么是观察变换? 21. 简述二维观察变换的流程。 22. 试述窗口到视区的变换步骤,并推出变换矩阵。 23. 已知w1=10,w2=20,w3=40,w4=80, v1=80,v2=110,v3=10,v4=130, 窗口中一点P(15,60),求视区中的映射点P'? 24. 在观察变换前必须确定图形的哪部分在窗口内,那些部分在窗口外,这个选择处理过 程称为 。 25. 使用Open GL的变换函数,若程序中先后调用的几个变换函数所定义的矩阵及顺序为L, M, N,其作用顺序为: 。 26. 试列举你所知的直线和多边形裁剪算法。 27. 简述Cohen-Sutherland(代码)线段裁剪算法。 28. 窗口和多边形如下图,应用Sutherland- Hodgman算法(逐边裁剪算法),对多边形进行裁剪。请以左、上、右、下的顺序列出 窗口各边裁剪多边形后所得的多边形顶点表。 29. 任何满足欧拉公式的形体称为 形体。 30. 超二次曲面通过将额外的参数插入 曲面方程而形成。 31. 在曲线、曲面的表示上,参数方程有何优点? 32. 要变换参数曲线曲面可以直接变换它的 ,而对于非参数形式则必须变换 。 33. 欧几里得曲线是 物体,沿三维曲线路径的位置可用 参数描述。 34. 规格化参变量 t [0, 1] 使得曲线曲面的 容易确定。 35. 什么是插值?什么是逼近?什么是拟合? 36. 给定一组有序的数据点 Pi ,i =0, 1, …, n,称为控制点,构造一条曲线顺序通过每个控制点,称为对这组控制点进行 ,所构造的曲线称为 。 37. 构造一条曲线使之在某种意义下最接近给定的数据点,而不要求通过其中任何一个点 ,称为对这些数据点进行 ,所构造的曲线为 曲线。 38. 拟合(Fitting)是 和 的统称。 39. 对于一组有序的型值点,确定一种参数分割,称之对这组型值点 。确定某个单参数矢函数,即确定参数曲线方程,称为曲线的 。 40. 参数域中所有节点构成的序列称为 矢量。 41. 什么是参数化?什么是参数区间的规格化? 42. 什么是参数连续性? 二条曲线P
目录 一.数论 4 1.阶乘最后非零位 4 2. 模线性方程(组) 4 3. 素数表 6 4. 素数随机判定(miller_rabin) 6 5. 质因数分解 7 6. 最大公约数欧拉函数 8 二.图论_匹配 9 1. 二分图最大匹配(hungary邻接表形式) 9 2. 二分图最大匹配(hungary邻接表形式,邻接阵接口) 10 3. 二分图最大匹配(hungary邻接阵形式) 10 4. 二分图最大匹配(hungary正向表形式) 11 5. 二分图最佳匹配(kuhn_munkras邻接阵形式) 11 6. 一般图匹配(邻接表形式) 12 7. 一般图匹配(邻接表形式,邻接阵接口) 13 8. 一般图匹配(邻接阵形式) 14 9. 一般图匹配(正向表形式) 15 三.图论_生成树 16 1. 最小生成树(kruskal邻接表形式) 16 2. 最小生成树(kruskal正向表形式) 17 3. 最小生成树(prim+binary_heap邻接表形式) 19 4. 最小生成树(prim+binary_heap正向表形式) 20 5. 最小生成树(prim+mapped_heap邻接表形式) 21 6. 最小生成树(prim+mapped_heap正向表形式) 22 7. 最小生成树(prim邻接阵形式) 23 8. 最小树形图(邻接阵形式) 24 四.图论_网络流 25 1. 上下界最大流(邻接表形式) 25 2. 上下界最大流(邻接阵形式) 26 3. 上下界最小流(邻接表形式) 27 4. 上下界最小流(邻接阵形式) 29 5. 最大流(邻接表形式) 30 6. 最大流(邻接表形式,邻接阵接口) 31 7. 最大流(邻接阵形式) 32 8. 最大流无流量(邻接阵形式) 32 9. 最小费用最大流(邻接阵形式) 33 五. 图论_最短路径 34 1. 最短路径(单源bellman_ford邻接阵形式) 34 2. 最短路径(单源dijkstra_bfs邻接表形式) 35 3. 最短路径(单源dijkstra_bfs正向表形式) 35 4. 最短路径(单源dijkstra+binary_heap邻接表形式) 36 5. 最短路径(单源dijkstra+binary_heap正向表形式) 37 6. 最短路径(单源dijkstra+mapped_heap邻接表形式) 38 7. 最短路径(单源dijkstra+mapped_heap正向表形式) 39 8. 最短路径(单源dijkstra邻接阵形式) 40 9. 最短路径(多源floyd_warshall邻接阵形式) 40 六. 图论_连通性 41 1. 无向图关键边(dfs邻接阵形式) 41 2. 无向图关键点(dfs邻接阵形式) 42 3. 无向图块(bfs邻接阵形式) 43 4. 无向图连通分支(bfs邻接阵形式) 43 5. 无向图连通分支(dfs邻接阵形式) 44 6. 有向图强连通分支(bfs邻接阵形式) 44 7. 有向图强连通分支(dfs邻接阵形式) 45 8. 有向图最小点基(邻接阵形式) 46 七. 图论_应用 46 1.欧拉回路(邻接阵形式) 46 2. 前序表转化 47 3. 树的优化算法 48 4. 拓扑排序(邻接阵形式). 49 5. 最佳边割集 50 6. 最佳顶点割集 51 7. 最小边割集 52 8. 最小顶点割集 53 9. 最小路径覆盖 55 八. 图论_NP搜索 55 1. 最大团(n小于64)(faster) 55 2. 最大团 58 九. 组合 59 1. 排列组合生成 59 2. 生成gray码 60 3. 置换(polya) 61 4. 字典序全排列 61 5. 字典序组合 62 6. 组合公式 62 十. 数值计算 63 1. 定积分计算(Romberg) 63 2. 多项式求根(牛顿法) 64 3. 周期性方程(追赶法) 66 十一. 几何 67 1. 多边形 67 2. 多边形切割 70 3. 浮点函数 71 4. 几何公式 76 5. 面积 78 6. 球面 79 7. 三角形 79 8. 三维几何 81 9. 凸包(graham) 89 10. 网格(pick) 91 11. 圆 92 12. 整数函数 94 13. 注意 96 十二. 结构 97 1. 并查集 97 2. 并查集扩展(friend_enemy) 98 3. 堆(binary) 98 4. 堆(mapped) 99 5. 矩形切割 99 6. 线段树 100 7. 线段树扩展 102 8. 线段树应用 105 9. 子段和 105 10. 子阵和 105 十三. 其他 106 1. 分数 106 2. 矩阵 108 3. 日期 110 4. 线性方程组(gauss) 111 5. 线性相关 113 十四. 应用 114 1. joseph 114 2. N皇后构造解 115 3. 布尔母函数 115 4. 第k元素 116 5. 幻方构造 116 6. 模式匹配(kmp) 118 7. 逆序对数 118 8. 字符串最小表示 119 9. 最长公共单调子序列 119 10. 最长子序列 120 11. 最大子串匹配 121 12. 最大子段和 122 13. 最大子阵和 123 常用源代码 包括很多经典算法 数学问题: 1.精度计算——大数阶乘 2.精度计算——乘法(大数乘小数) 3.精度计算——乘法(大数乘大数) 4.精度计算——加法 5.精度计算——减法 6.任意进制转换 7.最大公约数、最小公倍数 8.组合序列 9.快速傅立叶变换(FFT) 10.Ronberg算法计算积分 11.行列式计算 12.求排列组合数 字符串处理: 1.字符串替换 2.字符串查找 3.字符串截取 计算几何: 1.叉乘法求任意多边形面积 2.求三角形面积 3.两矢量间角度 4.两点距离(2D、3D) 5.射向法判断点是否在多边形内部 6.判断点是否在线段上 7.判断两线段是否相交 8.判断线段与直线是否相交 9.点到线段最短距离 10.求两直线的交点 11.判断一个封闭图形是凹集还是凸集 12.Graham扫描法寻找凸包 数论: 1.x的二进制长度 2.返回x的二进制表示中从低到高的第i位 3.模取幂运算 4.求解模线性方程 5.求解模线性方程组(中国余数定理) 6.筛法素数产生器 7.判断一个数是否素数 图论: 1.Prim算法求最小生成树 2.Dijkstra算法求单源最短路径 3.Bellman-ford算法求单源最短路径 4.Floyd算法求每对节点间最短路径 排序/查找: 1.快速排序 2.希尔排序 3.选择法排序 4.二分查找 数据结构: 1.顺序队列 2.顺序栈 3.链表 4.链栈 5.二叉树
数学问题: 1.精度计算——大数阶乘 2.精度计算——乘法(大数乘小数) 3.精度计算——乘法(大数乘大数) 4.精度计算——加法 5.精度计算——减法 6.任意进制转换 7.最大公约数、最小公倍数 8.组合序列 9.快速傅立叶变换(FFT) 10.Ronberg算法计算积分 11.行列式计算 12.求排列组合数 13.求某一天星期几 字符串处理: 1.字符串替换 2.字符串查找 3.字符串截取 4.LCS—最大公共子串长度 5.LCS-生成最大公共子串 6.数字转化为字符 计算几何: 1.叉乘法求任意多边形面积 2.求三角形面积 3.两矢量间角度 4.两点距离(2D、3D) 5.射向法判断点是否在多边形内部 6.判断点是否在线段上 7.判断两线段是否相交 8.判断线段与直线是否相交 9.点到线段最短距离 10.求两直线的交点 11.判断一个封闭图形是凹集还是凸集 12.Graham扫描法寻找凸包 13.求两条线段的交点 数论: 1.x的二进制长度 2.返回x的二进制表示中从低到高的第i位 3.模取幂运算 4.求解模线性方程 5.求解模线性方程组(中国余数定理) 6.筛法素数产生器 7.判断一个数是否素数 8.求子距阵最大和 9.求一个数每一位之和 10.质因数分解 11.高斯消元法解线性方程组 图论: 1.Prim算法求最小生成树 2.Dijkstra算法求单源最短路径 3.Bellman-ford算法求单源最短路径 4.Floyd算法求每对节点间最短路径 5.解欧拉图 排序/查找: 1.快速排序 2.希尔排序 3.选择法排序 4.二分查找 高精度运算专题: 1.本专题公共函数说明 2.高精度比较 3.高精度加法 4.高精度减法 5.高精度乘10 6.高精度乘单精度 7.高精度乘高精度 8.高精度除单精度 9.高精度除高精度
Matlab中的扫描线算法是一种用于填充多边形区域的算法。该算法的基本思想是从给定的种子点开始,沿着扫描线向左右两个方向填充位于给定区域内的像素。具体步骤如下: 1. 初始化一个空的栈,将种子像素(x, y)入栈。 2. 当栈非空时,重复执行以下步骤: a. 栈顶像素出栈。 b. 沿着扫描线对出栈像素的左右像素进行填充,直到遇到边界像素为止。 c. 将上述区间内最左和最右的像素分别记为xLeft和xRight。 d. 在区间\[xLeft, xRight\]内检查与当前扫描线相邻的上下两条扫描线是否全为边界像素或已填充的像素,若为非边界和未填充,则将每个区间的最右像素xRight作为种子像素压入栈中,重复步骤(2)。 3. 重复步骤(2)直到填充结束。 这个算法可以用于填充任意形状的多边形区域。在Matlab中,可以使用Bresenham线算法来计算扫描线上的像素点,然后使用plot函数将这些像素点绘制出来,实现填充效果。 请注意,以上是对Matlab中扫描线算法的一般描述,具体实现可能会有一些细微的差异。 #### 引用[.reference_title] - *1* *2* [【CV系列扫描线算法区域填充](https://blog.csdn.net/SoaringLee_fighting/article/details/90322682)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [MATLAB等距线扫描程序实现思路与源程序](https://blog.csdn.net/weixin_36296063/article/details/115851906)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值