導航:首頁 > 源碼編譯 > 差分進化演算法代碼

差分進化演算法代碼

發布時間:2024-04-24 18:20:51

㈠ 多目標差分進化演算法

差分進化演算法(Differential Evolution, DE)是一種基於群體差異的啟發式隨機搜索演算法,該演算法是由R.Storn和K.Price為求解Chebyshev多項式而提出的。是一種用於最佳化問題的後設啟發式演算法。本質上說,它是一種基於實數編碼的具有保優思想的貪婪遺傳演算法。

將問題的求解表示成"染色體"的適者生存過程,通過"染色體"群的一代代不斷進化,包括復制、交叉和變異等操作,最終收斂到"最適應環境"的個體,從而求得問題的最優解或滿意解。

差分進化演算法類似遺傳演算法,包含變異,交叉操作,淘汰機制,而差分進化演算法與遺傳演算法不同之處,在於變異的部分是隨選兩個解成員變數的差異,經過伸縮後加入當前解成員的變數上,因此差分進化演算法無須使用概率分布產生下一代解成員。最優化方法分為傳統優化方法和啟發式優化方法兩大類。傳統的優化方法大多數都是利用目標函數的導數求解;而啟發式優化方法以仿生演算法為主,通過啟發式搜索策略實現求解優化。啟發式搜索演算法不要求目標函數連續、可微等信息,具有較好的全局尋優能力,成為最優化領域的研究熱點。

在人工智慧領域中,演化演算法是演化計算的一個分支。它是一種基於群體的元啟發式優化演算法,具有自適應、自搜索、自組織和隱並行性等特點。近年來,很多學者將演化演算法應用到優化領域中,取得了很大的成功,並已引起了人們的廣泛關注。越來越多的研究者加入到演化優化的研究之中,並對演化演算法作了許多改進,使其更適合各種優化問題。目前,演化演算法已廣泛應用於求解無約束函數優化、約束函數優化、組合優化、多目標優化等多種優化問題中。

㈡ 進化演算法的差分演算法

差分進化演算法(Differential Evolution, DE)是一種新興的進化計算技術,或稱為差分演化演算法、微分進化演算法、微分演化演算法、差異演化演算法。它是由Storn等人於1995年提出的,最初的設想是用於解決切比雪夫多項式問題,後來發現DE也是解決復雜優化問題的有效技術。DE與人工生命,特別是進化演算法有著極為特殊的聯系。
差分進化演算法是基於群體智能理論的優化演算法,通過群體內個體間的合作與競爭產生的群體智能指導優化搜索。但相比於進化演算法,DE保留了基於種群的全局搜索策略,採用實數編碼基於差分的簡單變異操作和一對一的競爭生存策略,降低了遺傳操作的復雜性。同時,DE特有的記憶能力使其可以動態跟蹤當前的搜索情況,以調整其搜索策略,具有較強的全局收斂能力和魯棒性,且不需要藉助問題的特徵信息,適於求解一些利用常規的數學規劃方法所無法求解的復雜環境中的優化問題。
差分進化演算法是一種基於群體進化的演算法,具有記憶個體最優解和種群內信息共享的特點,即通過種群內個體間的合作與競爭來實現對優化問題的求解,其本質是一種基於實數編碼的具有保優思想的貪婪遺傳演算法。
DE是一種用於優化問題的啟發式演算法。本質上說,它是一種基於實數編碼的具有保優思想的貪婪遺傳演算法 。同遺傳演算法一樣,DE包含變異和交叉操作,但同時相較於遺傳演算法的選擇操作,DE採用一對一的淘汰機制來更新種群。由於DE在連續域優化問題的優勢已獲得廣泛應用,並引發進化演算法研究領域的熱潮。
DE由Storn 以及Price提出,演算法的原理採用對個體進行方向擾動,以達到對個體的函數值進行下降的目的,同其他進化演算法一樣,DE不利用目標函數的梯度信息,因此對目標的可導性甚至連續性沒有要求,適用性很強。同時,演算法與粒子群優化有相通之處 ,但因為DE在一定程度上考慮了多變數間的相關性,因此相較於粒子群優化在變數耦合問題上有很大的優勢。演算法的實現參考實現代碼部分。

㈢ 誰有關於進化演算法的程序用C語言寫的!急用!

/************************************************/
文件名:Classifier.h

#ifndef CLASSIFIER_H
#define CLASSIFIER_H

#include <iostream>
#include <stdio.h>

#define SELF 0
#define NONSELF 1
#define MASKVALUE 2

// detector class
class Detector
{
public:
Detector(const unsigned int length);
Detector::~Detector(void);

unsigned int length;
unsigned int *value;
double threshold;
unsigned int type;

void save(FILE *outputStream);
void show(void) { save(stdout); };
};

#endif

/**********************************************/
//文件名:Classifier.cpp

#include "Classifier.h"

// detector class public methods

Detector::Detector(const unsigned int length)
{
this->length = length;
threshold = 0.0;
value = new unsigned int [length];
type = 0;
}

Detector::~Detector(void)
{
delete [] value;
}

void Detector::save(FILE *outputStream)
{
register unsigned int i;

fprintf(outputStream, \
"%-3d %-.10f %-1d\n", \
length, \
threshold, \
type \
);

for(i = 0; i < length; i++)
fprintf(outputStream, "%-1d ", value[i]);
fprintf(outputStream, "\n");

fflush(outputStream);
}

/**********************************************/
//文件名:EvolutionaryAlgorithm.h

#ifndef EVOLUTIONARYALGORITHM_H
#define EVOLUTIONARYALGORITHM_H

#include "Classifier.h"
#include <stdio.h>

// genome class
class Genome
{
public:
Genome(const unsigned int length);
~Genome(void);

unsigned int size;
unsigned int *locus;
unsigned int type;
double mutationProbability;
double crossoverProbability;
double fitness, scaledFitness;
unsigned int thresholdLength, patternLength;
double generalityBias;
double typeBias;

void Genome(Genome *genome);
void uniformCrossover(Genome *genome1, Genome *genome2);
void mutateBinary(void);
void randomiseBinary(void);
void setDetector(Detector *detector);
void save(FILE *outputStream);
void show(void) { save(stdout); };
};

// species class
class Species
{
public:
Species(const unsigned int speciesSize, const unsigned int genomeLength);
~Species(void);

unsigned int speciesSize;
Genome **genome;
unsigned int fittestIndivial;
double speciesScaledFitnessSum;
double meanSpeciesFitness;

Genome *FPSelection(void);
void randomise(void);
void Species(Species *species);
void save(FILE *outputStream);
void show(void) { save(stdout); };
};

#endif

/**********************************************************/
//文件名:EvolutionaryAlgorithm.cpp

#include "EvolutionaryAlgorithm.h"
#include <stdlib.h>

// genome class public methods
Genome::Genome(const unsigned int length)
{
thresholdLength = 8;
patternLength = length;
size = thresholdLength + 2 * patternLength;
locus = new unsigned int [size];
mutationProbability = 2.0 / double(size);
crossoverProbability = 0.6;
generalityBias = typeBias = 0.5;
fitness = 0.0;
type = 0;
}

Genome::~Genome(void)
{
delete [] locus;
}

void Genome::Genome(Genome *genome)
{
register unsigned int i = size;
register unsigned int *from = genome->locus;
register unsigned int *to = locus;

while(i--)
to[i] = from[i];
mutationProbability = genome->mutationProbability;
crossoverProbability = genome->crossoverProbability;
fitness = genome->fitness;
scaledFitness = genome->scaledFitness;
generalityBias = genome->generalityBias;
size = genome->size;
patternLength = genome->patternLength;
thresholdLength = genome->thresholdLength;
type = genome->type;
}

void Genome::uniformCrossover(Genome *genome1, Genome *genome2)
{
register unsigned int i = size;
register unsigned int *from1 = genome1->locus;
register unsigned int *from2 = genome2->locus;
register unsigned int *to = locus;
register double cp = crossoverProbability;

while(i--)
{
if(drand48() < cp)
to[i] = from1[i];
else
to[i] = from2[i];
}
if(drand48() < cp)
type = genome1->type;
else
type = genome2->type;
}

void Genome::mutateBinary(void)
{
register unsigned int i = size;
register unsigned int *loci = locus;
register double mp = mutationProbability;

while(i--)
if(drand48() < mp)
loci[i] = 1 - loci[i];
if(drand48() < mp)
type = 1 - type;
}

void Genome::randomiseBinary(void)
{
register unsigned int index, i;

index = 0;

i = thresholdLength;
while(i--)
locus[index++] = int((double(rand()) * 2.0) / double(RAND_MAX + 1.0));
i = patternLength;
while(i--)
locus[index++] = int((double(rand()) * 2.0) / double(RAND_MAX + 1.0));
i = patternLength;
while(i--)
if(drand48() < generalityBias)
locus[index++] = 0;
else
locus[index++] = 1;
if(drand48() < typeBias)
type = SELF;
else
type = NONSELF;
}

void Genome::save(FILE *outputStream)
{
register unsigned int i;
Detector *detector = new Detector(patternLength);

fprintf(outputStream, \
"%-3d %-3d %-3d %-1d %-10f %-10f %-10f %-10f %-10f %-10f\n", \
size, \
thresholdLength, \
patternLength, \
type, \
fitness, \
scaledFitness, \
mutationProbability, \
crossoverProbability, \
generalityBias, \
typeBias \
);

for(i = 0; i < size; i++)
fprintf(outputStream, "%-2d ", locus[i]);
fprintf(outputStream, "\n");

setDetector(detector);
detector->save(outputStream);

delete detector;

fflush(outputStream);
}

void Genome::setDetector(Detector *detector)
{
register unsigned int i, loci = 0, sum, lastLoci;

// set activation threshold
// gray coding for threshold gene
sum = lastLoci = locus[loci++];
while(loci < thresholdLength)
{
sum = (sum << 1) | (lastLoci ^ locus[loci]);
lastLoci = locus[loci++];
}
detector->threshold = double(sum) / 255.0; // !!!!!!!!!!!!!!!!!!!!
for(i = 0; i < patternLength; i++)
detector->value[i] = locus[loci++];
for(i = 0; i < patternLength; i++)
if(!locus[loci++])
detector->value[i] = MASKVALUE;
detector->type = type;
detector->length = patternLength;
}

// species class public methods
Species::Species(const unsigned int speciesSize, \
const unsigned int genomeLength)
{
register unsigned int i = speciesSize;

this->speciesSize = speciesSize;
fittestIndivial = 0;
speciesScaledFitnessSum = meanSpeciesFitness = 0.0;
genome = new Genome * [speciesSize];
while(i--)
genome[i] = new Genome(genomeLength);
}

Species::~Species(void)
{
register unsigned int i = speciesSize;

while(i--)
delete genome[i];
delete genome;
}

Genome *Species::FPSelection(void)
{
register unsigned int i = 0;
register double dtmp1, dtmp2;

dtmp1 = drand48() * speciesScaledFitnessSum;
dtmp2 = 0.0;
while((i < speciesSize) && ((dtmp2 = dtmp2 + genome[i]->scaledFitness) \
< dtmp1))
i++;
return((i < speciesSize) ? genome[i] : genome[i - 1]);
}

void Species::randomise(void)
{
register unsigned int i = speciesSize;

while(i--)
genome[i]->randomiseBinary();
}

void Species::save(FILE *outputStream)
{
fprintf(outputStream, \
"%-4d %-4d %-5.10f %-.10f\n", \
speciesSize, \
fittestIndivial, \
speciesScaledFitnessSum, \
meanSpeciesFitness \
);

genome[fittestIndivial]->save(outputStream);

fflush(outputStream);
}

void Species::Species(Species *species)
{
register unsigned int i = species->speciesSize;

speciesSize = i;
while(i--)
genome[i]->Genome(species->genome[i]);
fittestIndivial = species->fittestIndivial;
speciesScaledFitnessSum = species->speciesScaledFitnessSum;
meanSpeciesFitness = species->meanSpeciesFitness;
}

//補充了下,剛才少了個Classifier.cpp。

㈣ 優化演算法筆記(二)優化演算法的分類

(以下描述,均不是學術用語,僅供大家快樂的閱讀)

在分類之前,我們先列舉一下常見的優化演算法(不然我們拿什麼分類呢?)。
1遺傳演算法Genetic algorithm
2粒子群優化演算法Particle Swarm Optimization
3差分進化演算法Differential Evolution
4人工蜂群演算法Artificial Bee Colony
5蟻群演算法Ant Colony Optimization
6人工魚群演算法Artificial Fish Swarm Algorithm
7杜鵑搜索演算法Cuckoo Search
8螢火蟲演算法Firefly Algorithm
9灰狼演算法Grey Wolf Optimizer
10鯨魚演算法Whale Optimization Algorithm
11群搜索演算法Group search optimizer
12混合蛙跳演算法Shuffled Frog Leaping Algorithm
13煙花演算法fireworks algorithm
14菌群優化演算法Bacterial Foraging Optimization
以上優化演算法是我所接觸過的演算法,沒接觸過的演算法不能隨便下結論,知之為知之,不知為不知。其實到目前為止優化演算法可能已經有幾百種了,我們不可能也不需要全面的了解所有的演算法,而且優化演算法之間也有較大的共性,深入研究幾個之後再看其他優化演算法上手速度會灰常的快。
優化演算法從提出到現在不過50-60年(遺傳演算法1975年提出),雖種類繁多但大多較為相似,不過這也很正常,比較香蕉和人的基因相似度也有50%-60%。當然演算法之間的相似度要比香蕉和人的相似度更大,畢竟人家都是優化演算法,有著相同的目標,只是實現方式不同。就像條條大路通羅馬,我們可以走去,可以坐汽車去,可以坐火車去,也可以坐飛機去,不管使用何種方式,我們都在去往羅馬的路上,也不會說坐飛機去要比走去更好,交通工具只是一個工具,最終的方案還是要看我們的選擇。

上面列舉了一些常見的演算法,即使你一個都沒見過也沒關系,後面會對它們進行詳細的介紹,但是對後面的分類可能會有些許影響,不過問題不大,就先當總結看了。
再對優化演算法分類之前,先介紹一下演算法的模型,在筆記(一)中繪制了優化演算法的流程,不過那是個較為簡單的模型,此處的模型會更加復雜。上面說了優化演算法有較大的相似性,這些相似性主要體現在演算法的運行流程中。
優化演算法的求解過程可以看做是一個群體的生存過程。

有一群原始人,他們要在野外中尋找食物,一個原始人是這個群體中的最小單元,他們的最終目標是尋找這個環境中最容易獲取食物的位置,即最易存活下來的位置。每個原始人都去獨自尋找食物,他們每個人每天獲取食物的策略只有採集果實、製作陷阱或者守株待兔,即在一天之中他們不會改變他們的位置。在下一天他們會根據自己的策略變更自己的位置。到了某一天他們又聚在了一起,選擇了他們到過的最容易獲取食物的位置定居。
一群原始人=優化演算法中的種群、群體;
一個原始人=優化演算法中的個體;
一個原始人的位置=優化演算法中個體的位置、基因等屬性;
原始人變更位置=優化演算法中總群的更新操作;
該位置獲取食物的難易程度=優化演算法中的適應度函數;
一天=優化演算法中的一個迭代;
這群原始人最終的定居位置=優化演算法所得的解。
優化演算法的流程圖如下:

對優化演算法分類得有個標准,按照不同的標准分類也會得到不一樣的結果。首先說一下我所使用的分類標准(動態更新,有了新的感悟再加):

按由來分類比較好理解,就是該演算法受何種現象啟發而發明,本質是對現象分類。

可以看出演算法根據由來可以大致分為有人類的理論創造而來,向生物學習而來,受物理現象啟發。其中向生物學習而來的演算法最多,其他類別由於舉例有偏差,不是很准確,而且物理現象也經過人類總結,有些與人類現象相交叉,但仍將其獨立出來。
類別分好了,那麼為什麼要這么分類呢?

當然是因為要湊字數啦,啊呸,當然是為了更好的理解學習這些演算法的原理及特點。
向動物生存學習而來的演算法一定是一種行之有效的方法,能夠保證演算法的效率和准確性,因為,如果使用該策略的動物無法存活到我們可以對其進行研究,我們也無法得知其生存策略。(而這也是一種倖存者偏差,我們只能看到行之有效的策略,但並不是我們沒看到的策略都是垃圾,畢竟也發生過小行星撞地球這種小概率毀滅性事件。講個冷笑話開cou心一shu下:一隻小恐龍對他的小夥伴說,好開心,我最喜歡的那顆星星越來越亮了(完)。)但是由於生物的局限性,人們所創造出的演算法也會有局限性:我們所熟知的生物都生存在三維空間,在這些環境中,影響生物生存的條件比較有限,反應到演算法中就是這些演算法在解決較低維度的問題時效果很好,當遇到超高維(維度>500)問題時,結果可能不容樂觀,沒做過實驗,我也不敢亂說。

按更新過程分類相對復雜一點,主要是根據優化演算法流程中更新位置操作的方式來進行分類。更新位置的操作按我的理解可大致分為兩類:1.跟隨最優解;2.不跟隨最優解。
還是上面原始人的例子,每天他有一次去往其他位置狩獵的機會,他們採用何種方式來決定今天自己應該去哪裡呢?
如果他們的策略是「跟隨最優解」,那麼他們選取位置的方式就是按一定的策略向群體已知的最佳狩獵位置(歷史最佳)或者是當前群體中的最佳狩獵位置(今天最佳)靠近,至於是直線跑過去還是蛇皮走位繞過去,這個要看他們群體的策略。當然,他們的目的不是在最佳狩獵位置集合,他們的目的是在過去的途中看是否能發現更加好的狩獵位置,去往已經到過的狩獵地點再次狩獵是沒有意義的,因為每個位置獲取食物的難易程度是固定的。有了目標,大家都會朝著目標前進,總有一日,大家會在謀個位置附近相聚,相聚雖好但不利於後續的覓食容易陷入局部最優。
什麼是局部最優呢?假設在當前環境中有一「桃花源」,擁有上帝視角的我們知道這個地方就是最適合原始人們生存的,但是此地入口隱蔽「山有小口,彷彿若有光」、「初極狹,才通人。」,是一個難以發現的地方。如果沒有任何一個原始人到達了這里,大家向著已知的最優位置靠近時,也難以發現這個「桃源之地」,而當大家越聚越攏之後,「桃源」被發現的可能性越來越低。雖然原始人們得到了他們的解,但這並不是我們所求的「桃源」,他們聚集之後失去了尋求「桃源」的可能,這群原始人便陷入了局部最優。

如果他們的策略是「不跟隨最優解」,那麼他們的策略是什麼呢?我也不知道,這個應該他們自己決定。畢竟「是什麼」比「不是什麼」的范圍要小的多。總之不跟隨最優解時,演算法會有自己特定的步驟來更新個體的位置,有可能是隨機在自己附近找,也有可能是隨機向別人學習。不跟隨最優解時,原始人們應該不會快速聚集到某一處,這樣一來他們的選擇更具多樣性。
按照更新過程對上面的演算法分類結果如下

可以看出上面不跟隨最優解的演算法只有遺傳演算法和差分進化演算法,他們的更新策略是與進化和基因的重組有關。因此這些不跟隨最優解的演算法,他們大多依據進化理論更新位置(基因)我把他們叫做進化演算法,而那些跟隨群體最優解的演算法,他們則大多依賴群體的配合協作,我把這些演算法叫做群智能演算法。

目前我只總結了這兩種,分類方法,如果你有更加優秀的分類方法,我們可以交流一下:

目錄
上一篇 優化演算法筆記(一)優化演算法的介紹
下一篇 優化演算法筆記(三)粒子群演算法(1)

㈤ sko搴撲腑鐨勫嚑縐嶄紭鍖栫畻娉


璁╂垜浠娣卞叆鎺㈣╯ko搴撲腑鐨勫嚑縐嶄紭鍖栫畻娉曪紝榪欎簺綆楁硶鐘瑰傛帰緔㈡湭鐭ラ嗗煙鐨勬帰闄╁訛紝鑷村姏浜庡繪壘鍑芥暟涓鐨勬渶浼樿В銆傞栧厛錛屾垜浠瀵煎叆蹇呰佺殑宸ュ叿:


import sko
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

瀹氫箟涓涓閽堢姸鍑芥暟錛屽畠鐨勭洰鏍囨槸瀵繪壘鍦0鍒100鐨勬f柟褰㈠尯鍩熷唴鐨勬渶灝忓礆紝榪欎釜鍑芥暟鍏鋒湁涓涓闅愯棌鐨勬寫鎴樼偣錛(50, 50)銆傛垜浠鐨勪換鍔℃槸鎵懼埌涓涓鎺ヨ繎榪欎釜鐐圭殑瑙:


def pin_func(p):
x, y = p
r = np.square((x - 50)**2 + (y - 50)**2) + np.exp(1)
t = np.sin(r) / r + 1
return -1 * t

鎺ヤ笅鏉ワ紝鎴戜滑鐢╯ko搴撲腑鐨勫嚑縐嶄紭鍖栫畻娉曟潵瑙e喅榪欎釜闂棰:


1. 閬椾紶綆楁硶


from sko.GA import GA
o_GA = GA(func=pin_func, n_dim=2, size_pop=50, max_iter=800, prob_mut=0.001, lb=[0, 0], ub=[100, 100], precision=1e-7)

閫氳繃閬椾紶綆楁硶錛屾垜浠瀵繪壘鐩鏍囧嚱鏁扮殑鏈灝忓礆紝寰楀埌涓緇勬帴榪戞渶浼樿В鐨(x, y)鍧愭爣:


A_GA_x, A_GA_y = o_GA.run()
print(f"閬椾紶綆楁硶鏈灝忓: X = {A_GA_x}, Y = {A_GA_y}")

2. 綺掑瓙緹ょ畻娉


from sko.PSO import PSO
A_pso = PSO(func=pin_func, dim=2, pop=400, max_iter=200, w=1, c1=2, c2=2)
A_pos_x, A_pos_y = A_pso.run()
print(f"綺掑瓙緹ょ畻娉曡В: x = {A_pos_x}, y = {A_pos_y}")

3. 宸鍒嗚繘鍖栫畻娉


from sko.DE import DE
A_DE = DE(func=pin_func, n_dim=2, size_pop=50, max_iter=800, prob_mut=0.3)
A_DE_x, A_DE_y = A_DE.run()
print(f"宸鍒嗚繘鍖栫畻娉曡В: x = {A_DE_x}, y = {A_DE_y}")

4. 妯℃嫙閫鐏綆楁硶


from sko.SA import SA
A_sa = SA(func=pin_func, x0=[1, 1], T_max=1, T_min=1e-9, L=300, max_stay_counter=150, lb=[0, 0], ub=[100, 100])
A_sa_x, A_sa_y = A_sa.run()
print(f"妯℃嫙閫鐏綆楁硶瑙: x = {A_sa_x}, y = {A_sa_y}")

5. 楸肩兢綆楁硶


from sko.AFSA import AFSA
afsa = AFSA(func=pin_func, n_dim=2, size_pop=50, max_iter=300, max_try_num=100, step=5, visual=0.3, q=0.98, delta=1)
best_x, best_y = afsa.run()
print(f"楸肩兢綆楁硶鏈浼樿В: x = {best_x}, y = {best_y}")

姣忕嶇畻娉曢兘浠ョ嫭鐗圭殑鏂瑰紡鎺㈢儲鍑芥暟絀洪棿錛屾彮紺哄嚭闅愯棌鐨勬渶浼樿В錛屽睍紺轟簡sko搴撶殑寮哄ぇ浼樺寲鑳藉姏銆傞氳繃榪欎簺綆楁硶錛屾垜浠鍙浠ユ洿娣卞叆鍦扮悊瑙e備綍鍦ㄥ嶆潅闂棰樹腑鎵懼埌鏈浣崇瓥鐣ャ


閱讀全文

與差分進化演算法代碼相關的資料

熱點內容
java判斷半形 瀏覽:878
java判斷正負 瀏覽:318
刷頭條程序員的日常 瀏覽:102
吉林程序員吐槽 瀏覽:243
單片機溫度范圍 瀏覽:419
程序員為什麼素質低 瀏覽:897
可愛的程序員小姐姐 瀏覽:145
伺服器上網站的地址 瀏覽:798
蘋果平板如何找到app資源庫 瀏覽:321
阿里雲可以雲伺服器地址 瀏覽:249
熊貓繪畫app如何導入圖片 瀏覽:555
如何自己編輯手機app 瀏覽:924
程序員那麼可愛帶的項鏈 瀏覽:532
安卓系統導航mic什麼意思 瀏覽:192
編譯sdk如何輸出bin文件 瀏覽:677
如何用html5開發app 瀏覽:142
怎麼隱藏蘋果的app 瀏覽:326
上海捷豹空氣壓縮機 瀏覽:457
51單片機換行 瀏覽:737
哪裡可以快速學看建築圖紙app 瀏覽:503