# ขั้นตอนวิธีแบบกรวย
ขั้นตอนวิธีแบบกรวย  เป็นขั้นตอนวิธีที่รวดเร็ว ซึ่งสามารถระบุได้อย่างแม่นยำว่าอนุภาคใดบ้างที่อยู่บนพื้นผิวที่มีลักษณะเป็นก้อนเล็กๆ ใน 3 มิติ จากการคำนวณโดยทั่วไปทางเรขาคณิต แสดงให้เห็นว่าขั้นตอนวิธีแบบนี้มีประโยชน์อย่างยิ่งกับวิทยาศาสตร์ที่ที่มีคำนวณหาพื้นที่ผิว และวิทยาศาสตร์ที่ศึกษาเกี่ยวกับเรื่องของนาโนเทคโนโลยี

 แนวคิดพื้นฐาน 
thumb|ตัวอย่างของกลุ่มก้อนเล็กๆ ที่เราศึกษาในเรื่องขั้นตอนวิธีแบบกรวย เป็นดังรูปข้างบนนี้ วงกลมสีเขียวเป็นอนุภาคที่อยู่บนพื้นผิว และสีเหลืองคือ อนุภาคที่อยู่ภายใน

สำหรับกลุ่มก้อนสามมิติที่ให้มาในรูปด้านขวามือ ซึ่งประกอบด้วยชุดของอนุภาค เราจะบอกได้อย่างไรว่าอนุภาคใดบ้างอยู่บนพื้นผิว? เหมือนจะง่ายถ้ามองจากตาของมนุษย์ เกณฑ์การมองเห็นอาจถูกอธิบายได้ เช่นถ้าเรากำหนดว่า อนุภาคจะอยู่บนพื้นผิวได้ก็ต่อเมื่ออนุภาคนั้นไม่ได้ถูกล้อมรอบด้วยอนุภาคอื่นๆ ทุกทิศทาง หรือ เราจะบอกว่ามันเป็นอนุภาคพื้นผิวถ้ามันไม่ถูกฝั่งลงไปในของกลุ่มก้อนนั้น เกณฑ์เหล่านี้ทำให้เราสามารถตัดสินใจว่ามันเป็นอนุภาคพื้นผิวหรือไม่ แต่เพื่อที่จะบอกคอมพิวเตอร์ให้เก็บข้อมูลของอนุภาคที่อยู่พื้นผิว เช่นตำแหน่งของอนุภาคเหล่านั้น เราก็ใช้หลักการที่จะอธิบายเหมือนกับหลักเกณฑ์ที่กล่าวมาก่อนหน้านี้

thumb|อาณาเขตรูปกรวย

จุดหลายจุดที่เห็นในภาพด้านขวามือนั้นจะอยู่ในอนุภาค ซึ่งอนุภาคจะติดต่อกับอาณาเขตรูปกรวย ขอบเขตรูปกรวยนี้ถูกกำหนดขนาดพื้นที่ด้วยความยาว และมุมของมัน และเราเรียกอาณาเขตรูปกรวยที่ไม่มีอนุภาคใดๆ อยู่ภายในว่า "กรวยกลวง" (hollow cone) เรากำหนดให้อนุภาคอยู่บนพื้นผิวถ้าจุดอย่างน้อยหนึ่งจุดบนอนุภาคติดต่อกับกรวยกลวง

สำหรับกรวย เรามีตัวแปรที่เปลี่ยนแปลงได้ 2 ตัว คือ ความยาวของกรวย และขนาดของมุมกรวย ผลลัพธ์ที่แม่นยำหรือผลลัพธ์ที่น่าพอใจจะเกิดขึ้นได้ก็ขึ้นอยู่กับตัวแปร 2 ตัวนี้ ขั้นตอนวิธีแบบนี้สามารถปรับใช้ได้อย่างรวดเร็วกับสถานการณ์ที่มีโมเลกุลหลายโมเลกุลในโครงสร้าง หรือสถานการณ์ที่โมเลกุลนั้นมีรูอยู่ภายใน

 การทำงาน 
thumb|แบบจำลอง 2 มิติ ที่แสดงอนุภาคภายใน
ใช้วิธีการดัชนีเซลล์เพื่อเร่งให้เห็นอนุภาคข้างเคียง ความยาวด้านข้างของเซลล์กว้างกว่าความยาวด้านข้างของกรวยเล็กน้อย ซึ่งความยาวที่แตกต่างกันนี้จะถูกสังเกตและตัดออกไปในภายหลัง และอนุภาคข้างเคียงจะถูกระบุให้อยู่ภายในระยะที่ถูกตัดออกไปนั้น

ในการที่จะดูว่าอนุภาคที่เราสนใจมีกรวยกลวงหรือไม่ เราจะวนหาค่าผลรวมของอนุภาคข้างเคียง 3 อนุภาค เพื่อหาว่ามีอนุภาคข้างเคียงอื่นอยู่อีกหรือไม่ ผลรวมเชิงสเกล่าร์ของเวกเตอร์ที่เชื่อมต่อกับอนุภาคต่างๆ เหล่านี้ถูกใช้ในการพิจารณาว่าถ้าอาณาเขตรูปกรวยขยายไป 3 ช่วงอนุภาคแล้ว จะมีพื้นที่กว้างพอที่จะใส่ตัวแปร 2 ตัวหรือไม่ และมีอนุภาคอื่นอยู่ในอาณาเขตเขตรูปกรวยหรือไม่

ในการที่จะหาอนุภาคพื้นผิว ก็เพียงแค่หาอนุภาคที่มีกรวยกลวงอยู่ แม้จะมีกรวยกลวงเพียงอันเดียวก็จะถือว่าเป็นอนุภาคพื้นผิว และการวนเพื่อหาคำตอบจะสิ้นสุดลง
ในทางกลับกันเพื่อที่จะหาอนุภาคภายในพื้นผิวจำเป็นต้องค้นหาทุกๆ ค่าผลรวมของอนุภาคข้างเคียง 3 อนุภาค เพื่อที่จะยืนยันว่าไม่มีกรวยกลวงอยู่จริงๆ

แม้วิธีการง่ายๆ นี้จะเสร็จสมบูรณ์แต่เวลานั้นถูกใช้จนหมด ดังนั้นด้านล่างนี้จะกล่าวถึงวิธีอื่นๆ ที่ทำให้ความเร็วในการคำนวณของโปรแกรมดีขึ้นอย่างเห็นได้ชัด เพื่อจะลดเวลาการทำงานไม่ให้เวลาถูกใช้จนหมดไป

thumb|กลุ่มก้อนรูปทรงกลม จะมีโอกาสที่จะเจอกรวยกลวงได้มากขึ้น

 ในความเป็นจริง เราสามารถระบุหาตำแหน่งของอนุภาคภายในส่วนใหญ่ที่ห่างจากพื้นผิวได้อย่างง่ายดาย ตามนิยามที่ว่าพวกมันถูกล้อมรอบด้วยอนุภาคอื่นๆ จากรูปแสดงภาพจำลอง 2 มิติ เซลล์สีแดงเป็นอนุภาคภายใน เพราะถูกล้อมรอบด้วยเซลล์ข้างเคียง (สีน้ำเงิน) ถ้าเรามีจำนวนอนุภาคเยอะกว่านี้ต้องใช้แบบจำลอง 3 มิติในการพิจารณา
 อนุภาคพื้นผิวบางอนุภาคสามารถหาได้ง่ายโดยวิธีทางเรขาคณิตที่ใช้คำนวณกลุ่มก้อนที่เป็นรูปทรงกลม ตัวอย่างเช่น ถ้าสมมุติให้กลุ่มก้อนเป็นรูปทรงกลม จะมีโอกาสที่จะเจอกรวยกลวง ได้มากขึ้น โดยการเลือกเวอร์เตอร์จากศูนย์กลางของกลุ่มก้อนถึงอนุภาคที่เราสนใจ ตามกฎทางเรขาคณิตของกรวย

 การเกิดชั้นย่อยของขั้นตอนวิธีแบบกรวย 
thumb|left|การเกิดชั้นย่อยที่เกิดในกลุ่มก้อน
การเรียกใช้ขั้นตอนวิธีแบบกรวยหลายๆ ครั้งทำให้เกิดชั้นของพื้นผิวย่อยๆ ที่แตกต่างกันหลายชั้น โปรแกรมภาษาซีนั้นเป็นโปรแกรมที่ง่ายสำหรับการวิเคราะห์ชั้นพื้นผิวย่อยเหล่านี้ ซึ่งเกิดอยู่ในก้อนสีทองรูปหกเหลี่ยม (icosahedral gold cluster) ที่ประกอบด้วยอะตอม 2,624 อะตอม รูปนี้แสดงให้เห็นถึงชั้นย่อยของพื้นผิวในก้อนหกเหลี่ยม (แต่ละอะตอมในก้อนหกเหลี่ยมนี้เป็นอะตอมสีทองเหมือนกัน แต่ถูกระบายสีที่ต่างกันตามชั้นที่พวกมันสังกัดอยู่)

 การเขียนโปรแกรมของขั้นตอนวิธีแบบกรวย 
แม้ว่าภาษาซี (ซึ่งมีโครงสร้างภาษาแบบเชิงวัตถุ) จะเป็นภาษาที่เหมาะจะใช้เขียนขั้นตอนวิธีแบบนี้ แต่การโปรแกรมนั้นสามารถใช้กับโครงสร้างภาษาได้หลากหลายมากกว่าโครงสร้างแบบเชิงวัตถุอย่างเดียวการเขียนโปรแกรมควรจะสามารถใชักับข้อมูลขาเข้าและขาออกที่มีมาตรฐานตั้งแต่น้อยๆไปจนมีมาตรฐานมากๆได้ source code ของโปรแกรมควรจะอ่านง่าย และควรจะคอมเมนต์ที่เพียงพอเพื่อที่จะทำให้อ่านเข้าใจ

 รหัสในภาษาซี 
 การเรียกใช้ฟังก์ชัน 
# include <iostream>
# include <math.h>
# include <stdlib.h>
# include <libgen.h>
# include "vector.h"
using namespace std;
 การประกาศตัวแปรคลาส 
double cosine; // cosine of the angle of the die cone
double cutoff2; // =cutoff*cutoff, cutoff is the side length of the die cone
double bin; // side length of cell = 1.0001 * cutoff
int num; // number of particles
Vector *q; // particle positions
int *d; // d[i]: 1--surface, -1--internal, 0--undetermined
int ***cell; // head particle of cell chain
int ***curr; // current-position pointer of cell chain
int nx, ny, nz; // cell numbers in 3 dimensions
int *list; // single chain of the particle list int the cells, end with -1
const double TINY = 1.0e-20; // zero value
void readConf;
void initCell;
void end;
void writeXYZ;
void markCell;
void markSurface;
int cmCheck(const int p, const int i, const int j, const int k);
int cmCone(const int p, const int np);
int cone(const Vector &v, const int p, const int np);
int realCheck(const int p, const int i, const int j, const int k);
bool realCone(const int p, const int np1, const int np2,
	       const int np3, const int *nbr, const int nnbr);
bool coneVector(const int p, const int np1, const int np2, const int np3,
		 Vector &v);
bool inLine(const int p, const int p1, const int p2);
 ฟังก์ชันต่างๆ 
 //---------------------------function readConf--------------------------
 // Read one configuration of a molecule from the standard input.
 // The input data should be in .xyz format.
void readConf{
  cin >> num;
  char s[1024];
  cin.getline(s, 1024);       // comment line
  cin.getline(s, 1024);
  d =new int[num];
  list = new int[num];
  for(int i=0; i<num; i++){
    d[i] = 0;
    list[i] = -1;
  }
  q = new Vector[num];
  for(int i=0; i<num; i++) cin >> s >> q[i];
} 
 //------------------------------function initCell--------------------------------
 // Find the boundary of the simulation box and locate particles into cells.
void initCell{
  // Create cell array
  Vector min(q[0].x, q[0].y, q[0].z);
  Vector max(min);
  for(int i=1; i<num; i++){
    if(q[i].x < min.x) min.x = q[i].x;
    if(q[i].x > max.x) max.x = q[i].x;
    if(q[i].y < min.y) min.y = q[i].y;
    if(q[i].y > max.y) max.y = q[i].y;
    if(q[i].z < min.z) min.z = q[i].z;
    if(q[i].z > max.z) max.z = q[i].z;
  }
  nx = (int)ceil((max.x-min.x)/bin);
  ny = (int)ceil((max.y-min.y)/bin);
  nz = (int)ceil((max.z-min.z)/bin);
  cell = new int **[nx];
  curr = new int **[nx];
  for(int i=0; i<nx; i++){
    cell[i] = new int *[ny];
    curr[i] = new int *[ny];
    for(int j=0; j<ny; j++){
      cell[i][j] = new int[nz];
      curr[i][j] = new int[nz];
    }
  }
  // Locate particles into cells
  for(int i=0; i<nx; i++)
    for(int j=0; j<ny; j++)
      for(int k=0; k<nz; k++){
         cell[i][j][k] = -1;
         curr[i][j][k] = -1;
        }
  for(int i=0; i<num; i++){
    Vector vi = (q[i] - min) / bin;
    int x = (int)vi.x;   int y = (int)vi.y;   int z = (int)vi.z;
    if(x>=nx || y>=ny || z>=nz ||x<0 || y<0 || z<0){
      cerr << "Out of boundary: " << endl;
      cerr << "ix=" << x << " iy=" << y << " iz=" << z << endl;
      cerr << "bin=" << bin << endl;
      cerr << "x[i]=" << q[i].x << " y[i]=" << q[i].y
	   << " z[i]=" << q[i].z << endl;
      cerr << "xmin=" << min.x << " ymin=" << min.y
	   << " zmin=" << min.z << endl;
      exit(1);
     }
    if(cell[x][y][z] == -1) {  // add the head of the cell chain
      cell[x][y][z] = curr[x][y][z] = i;
    }
    else {  // add an element at the tail of the chain and move the pointer
      list[ curr[x][y][z] ] = i;
      curr[x][y][z] = i;
    }
  }
}
 //------------------------- function end----------------------------
 // Finishing job.
void end{
  delete []q; delete []d;   delete []list;
  for(int i=0; i<nx; i++){
    for(int j=0; j<ny; j++) {
      delete [](cell[i][j]);
      delete [](curr[i][j]);
    }
    delete [](cell[i]);
    delete [](curr[i]);
   }
  delete []cell;
  delete []curr;
 } 
 //-------------------------- function writeXYZ---------------------------
 // Write out the configuration with the surface particles as Cd, the internal
 // particles as Au. If any particles left undermined, they are maked as Ag.
void writeXYZ{
  cout << num << endl << endl;
  for(int i=0; i<num; i++){
    if(d[i]== -1) cout << "Au ";
    else if(d[i]==1) cout << "Cd ";
    else cout << "Ag ";
    cout << q[i] << endl;
  }
} 
 //---------------------------function markCell--------------------------
 // Mark the particles in the internal cells. If a cell is internal is
 // determined by looking around the designated neighbor cells to see if
 // they all contain particles. A batch way to find the most interior particles.
void markCell{
  for(int i=2; i<nx-2; i++)
    for(int j=2; j<ny-2; j++)
      for(int k=2; k<nz-2; k++){
	if(cell[i][j][k] != -1 &&
	    cell[i-2][j][k] != -1 && cell[i+2][j][k] != -1 &&
	    cell[i][j-2][k] != -1 && cell[i][j+2][k] != -1 &&
	    cell[i][j][k-2] != -1 && cell[i][j][k+2] != -1 &&
	    cell[i-1][j-1][k-1] != -1 && cell[i-1][j-1][k+1] != -1 &&
	    cell[i-1][j+1][k-1] != -1 && cell[i-1][j+1][k+1] != -1 &&
	    cell[i+1][j-1][k-1] != -1 && cell[i+1][j-1][k+1] != -1 &&
	    cell[i+1][j+1][k-1] != -1 && cell[i+1][j+1][k+1] != -1){
	  // mark the particles in this cell to be internal
	  int p = cell[i][j][k];
	  while(p != -1){
	    d[ p ] = -1;
	    p = list[p];
	  }
	  // mark this cell to be checked to avoid further investigation
	  curr[i][j][k] = -1;
	}
      }
}
 //---------------------------function markSurface--------------------------
 // Mark the surface particles by looking at the bond angles with the neighbor
 // particles and trying to find a hollow (without particles) cone region with
 // angle larger than the designated threshold.
void markSurface{
  // First round quick check for surface particles by center-of-mass normal
  // vector criterion.
  for(int i=0; i<nx; i++)
    for(int j=0; j<ny; j++)
      for(int k=0; k<nz; k++) {
	if(curr[i][j][k] != -1){ // -1 means either no particles or checked
	                          // to be all internal
	  int p = cell[i][j][k];
	  while(p != -1) {
	    d[p] = cmCheck(p, i, j, k);
	    p = list[ p ];
	  }
	}
      }
  // Complete searching for a satisfied cone region to vanish all the
  // undetermined particles.
  for(int i=0; i<nx; i++)
    for(int j=0; j<ny; j++)
      for(int k=0; k<nz; k++){
	if(curr[i][j][k] != -1){ // -1 means either no particles or checked
	                          // to be all internal
	  int p = cell[i][j][k];
	  while(p != -1){
	    if(d[p] == 0)  {  // undermined particles
	      d[p] = realCheck(p, i, j, k);
	    }
	    p = list[ p ];
	  }
	  curr[i][j][k] = -1;    // this cell finished
	}
      }
}
 //-------------------------function cmCheck----------------------------
 // Check if a given particle is on the surface by cmCone.
int cmCheck(const int p, const int i, const int j, const int k){
// Loop over all particles in this cell and the neighboring cells
  for(int ni=i-1; ni<=i+1; ni++){
    if(ni<0 || ni>=nx) continue;
    for(int nj=j-1; nj<=j+1; nj++){
      if(nj<0 || nj>=ny) continue;
      for(int nk=k-1; nk<=k+1; nk++){
	if(nk<0 || nk>=nz) continue;
	// internal particles are definitely out of the cone region
	if(curr[ni][nj][nk] == -1) continue;
	int np = cell[ni][nj][nk];
	while(np != -1){
	  if(cmCone(p, np) == 1) return 0;
	  np = list[ np ];
	}
      }
    }
  }
  return 1;        // surface particle
}
 //--------------------------- function cmCone--------------------------
 // Check if np is in the cone region of p with the vector from center of mass
 // to p and angle by cosine. If so, return true. Otherwise, return false.
 // A quick way to pick up some surface particles before complete searching algorithm.
int cmCone(const int p, const int np){
  return cone(q[p].normalize, p, np);
}//-------------------------- function cone---------------------------
// Check if a given particle np is in the cone region centered at particle p
// with center vector of (nx,ny,nz) and cone angle theta
// with cos(theta)=cosine. Cone center vector must be normalized.
// Return 0 if the particle is not inside the region(s), 1 if in the cone
// region, -1 if in the inverse cone region.
int cone(const Vector &v, const int p, const int np){
  Vector d = (q[np] - q[p]).normalize;
  double s = v*d;
  if(s > cosine) return 1;    // less than the angle
  else if(s<-cosine) return -1;
  else return 0;
}
 //-------------------------- function realCheck---------------------------
 // Check if the given particle is a surface particle without ambiguity by complete
 // searching for a cone region larger than the die.
int realCheck(const int p, const int i, const int j, const int k){
  int *nbr = new int[1000];   // list of neighboring particles
  int nnbr=0;    // number of neighbor particles
  int *snbr = new int[1000];  // neighboring surface particles
  int snnbr = 0;
  int *unbr = new int[1000];  // neighboring undetermined particles
  int unnbr = 0;
  // Loop over all particles in this cell and the neighboring cells to make
  // a list of neighboring particles.
  for(int ni=i-1; ni<=i+1; ni++) {
    if(ni<0 || ni>=nx) continue;
    for(int nj=j-1; nj<=j+1; nj++){
      if(nj<0 || nj>=ny) continue;
      for(int nk=k-1; nk<=k+1; nk++){
	if(nk<0 || nk>=nz) continue;
	int np = cell[ni][nj][nk];
	while(np != -1){
	  if(np != p)  {
	    // check if np is inside the cutoff region
	    Vector v = q[np] - q[p];
	    if(v.r2 < cutoff2) {
	      nnbr++;
	      nbr[ nnbr-1 ] = np;
	      if(d[np] == 1) { // surface particle
		snnbr++;
		snbr[ snnbr-1 ] = np;
	      }
	      else if(d[np] == 0) // undertermined particle
	      {
		unnbr++;
		unbr[ unnbr-1 ] = np;
	      }
	    }
	  }
	  np = list[ np ];
	}
      }
    }
  }
  if(nnbr <= 3) return 1;   // surface particle
  // Check the combinations of 3 neighboring particles to see if they span
  // a hollow cone region.
  // Internal particles are excluded from the loop of the combinations.
  // Combinations made by surface particles are checked first to improve the
  // chance of hitting a target earlier.
  // 3 surface particles
  for(int i=0; i<snnbr-2; i++)
    for(int j=i+1; j<snnbr-1; j++)
      for(int k=j+1; k<snnbr; k++)
	if(realCone(p, snbr[i], snbr[j], snbr[k], nbr, nnbr)){ // hollow
	  delete []nbr; delete []snbr; delete []unbr;
	  return 1;   // surface particle
	}
  // 2 surface particles, 1 undetermined particle
  for(int i=0; i<snnbr-1; i++)
    for(int j=i+1; j<snnbr; j++)
      for(int k=0; k<unnbr; k++){
	if(realCone(p, snbr[i], snbr[j], unbr[k], nbr, nnbr)){
	  delete []nbr; delete []snbr; delete []unbr;
	  return 1;
	}
      }
  // 1 surface particle, 2 undetermined particles
  for(int i=0; i<snnbr; i++)
    for(int j=0; j<unnbr-1; j++)
      for(int k=j+1; k<unnbr; k++){
	if(realCone(p, snbr[i], unbr[j], unbr[k], nbr, nnbr)){
	  delete []nbr; delete []snbr; delete []unbr;
	  return 1;
	}
      }
  // 3 undetermined particles
  for(int i=0; i<unnbr-2; i++)
    for(int j=i+1; j<unnbr-1; j++)
      for(int k=j+1; k<unnbr; k++)
	if(realCone(p, unbr[i], unbr[j], unbr[k], nbr, nnbr)) {// hollow
	  delete []nbr; delete []snbr; delete []unbr;
	  return 1;   // surface particle
	}
  delete []nbr; delete []snbr; delete []unbr;
  return -1;   // internal particle
}
 //-------------------------- function realCone---------------------------
 // Real cone searching to find out if the cone region defined by
 // (np1-p), (np2-p), (np3-p) or its inverse is hollow.
 // Return true if at least one is hollow.
bool realCone(const int p, const int np1, const int np2,
	       const int np3, const int *nbr, const int nnbr){
  Vector v;
  // Find the center vector of the cone spanned by (np1, np2, np3)
  // centered at p.
  if(!coneVector(p, np1, np2, np3, v)) return false;
  // If any particles inside the cone region and the inverse cone region.
  bool has = false;
  bool hasInv = false;
  for(int m=0; m<nnbr; m++){
    int nn = nbr[m];
    if(nn==np1 || nn==np2 || nn==np3) continue;
    int h = cone(v, p, nn);
    if(h == 1) {
      has = true;
      if(hasInv) break;
    }
    else if(h==-1) {
      hasInv = true;
      if(has) break;
    }
  }
  if(!has || !hasInv) return true;   // there is a hollow cone
  else return false;
}
 //--------------------------function coneVector---------------------------
 // Find the center vector of the cone defined by the three vectors
 // (np1-p), (np2-p), (np3-p).
 // Return false if any error or the angle spanned is not big enough.
bool coneVector(const int p, const int np1, const int np2, const int np3,
		 Vector &v){
  const Vector v1 = (q[np1] - q[p]).normalize;
  const Vector v2 = (q[np2] - q[p]).normalize;
  const Vector v3 = (q[np3] - q[p]).normalize;
  // it's the normal vector of plane spanned by v1, v2 and v3
  v = (v1-v2)^(v2-v3);
  // check if three points are on one line
  double r = sqrt(v.r2);
  if(r < TINY)    return false;
  v = v/r;
  if(fabs(v*v1) > cosine) // smaller angle
    return false;
  return true;
}
 การเรียกใช้ฟังก์ชันหลัก 
int main(int argc, char *argv[]){
  if(argc < 3){
    cerr << "Usage: " << basename(argv[0]) << " cutoff cosine" << endl;
    exit(0);
  }
  double cutoff = atof(argv[1]);
  cutoff2 = cutoff*cutoff;
  bin = 1.0001 * cutoff;
  cosine = atof(argv[2]);
  readConf;
  initCell;
  markCell;
  markSurface;
  writeXYZ;
  end;
  return 0;
}
 อ้างอิง 
 Cone Algorithm By Yanting Wang

กขั้นตอนวิธีแบบกรวย
กขั้นตอนวิธีแบบกรวย