四叉樹上如何求希爾伯特曲線的鄰居 ?

2021-02-10 Cocoa開發者社區


作者|一縷殤流化隱半邊冰霜

關於鄰居的定義,相鄰即為鄰居,那麼鄰居分為2種,邊相鄰和點相鄰。邊相鄰的有4個方向,上下左右。點相鄰的也有4個方向,即4個頂點相鄰的。

如上圖,綠色的區域是一顆四叉樹表示的範圍,四叉樹上面有一個點,圖中黃色區域標明的點。現在想求四叉樹上黃色的點的希爾伯特曲線鄰居。圖中黑色的線就是一顆穿過四叉樹的希爾伯特曲線。希爾伯特曲線的起點0在左上角的方格中,終點63在右上角的方格中。

紅色的四個格子是黃色格子邊相鄰鄰居,藍色的四個格子是黃色格子的頂點相鄰的鄰居,所以黃色格子的鄰居為8個格子,分別表示的點是8,9,54,11,53,30,31,32 。可以看出來這些鄰居在表示的點上面並不是相鄰的。

那麼怎麼求四叉樹上任意一點的希爾伯特曲線鄰居呢?

邊鄰居最直接的想法就是 先拿到中心點的坐標 (i,j) ,然後通過坐標系的關係,拿到與它邊相鄰的 Cell 的坐標 (i + 1,j) , (i - 1,j) , (i,j - 1) , (i,j + 1) 。

實際做法也是如此。不過這裡涉及到需要轉換的地方。這裡需要把希爾伯特曲線上的點轉換成坐標以後才能按照上面的思路來計算邊鄰居。

關於 CellID 的生成與數據結構,見筆者這篇《Google S2 中的 CellID 是如何生成的 ?》

按照上述的思路,實現出來的代碼如下:

func (ci CellID) EdgeNeighbors() [4]CellID {

    level := ci.Level()

    size := sizeIJ(level)

    f, i, j, _ := ci.faceIJOrientation()

    return [4]CellID{

        cellIDFromFaceIJWrap(f, i, j-size).Parent(level),

        cellIDFromFaceIJWrap(f, i+size, j).Parent(level),

        cellIDFromFaceIJWrap(f, i, j+size).Parent(level),

        cellIDFromFaceIJWrap(f, i-size, j).Parent(level),

    }

}

邊按照,下邊,右邊,上邊,左邊,逆時針的方向依次編號0,1,2,3 。

接下來具體分析一下裡面的實現。

func sizeIJ(level int) int {

    return 1 << uint(maxLevel-level)

}

sizeIJ 保存的是當前 Level 的格子邊長大小。這個大小是相對於 Level 30 來說的。比如 level = 29,那麼它的 sizeIJ 就是2,代表 Level 29 的一個格子邊長是由2個 Level 30 的格子組成的,那麼也就是22=4個小格子組成的。如果是 level = 28,那麼邊長就是4,由16個小格子組成。其他都以此類推。

func (ci CellID) faceIJOrientation() (f, i, j, orientation int) {

    f = ci.Face()

    orientation = f & swapMask

    nbits := maxLevel - 7*lookupBits // first iteration

    for k := 7; k >= 0; k-- {

        orientation += (int(uint64(ci)>>uint64(k*2*lookupBits+1)) & ((1 << uint((2 * nbits))) - 1)) << 2

        orientation = lookupIJ[orientation]

        i += (orientation >> (lookupBits + 2)) << uint(k*lookupBits)

        j += ((orientation >> 2) & ((1 << lookupBits) - 1)) << uint(k*lookupBits)

        orientation &= (swapMask | invertMask)

        nbits = lookupBits // following iterations

    }

    // 下面這個判斷詳細解釋

    if ci.lsb()&0x1111111111111110 != 0 {

        orientation ^= swapMask

    }

    return

}

這個方法就是把 CellID 再分解回原來的 i 和 j。這裡具體的過程在筆者這篇《Google S2 中的 CellID 是如何生成的 ?》裡面的 cellIDFromFaceIJ 方法裡面有詳細的敘述,這裡就不再贅述了。cellIDFromFaceIJ 方法和 faceIJOrientation 方法是互為逆方法。cellIDFromFaceIJ 是把 face,i,j 這個當入參傳進去,返回值是 CellID,faceIJOrientation 是把 CellID 分解成 face,i,j,orientation。faceIJOrientation 比 cellIDFromFaceIJ 分解出來多一個 orientation。

這裡需要重點解釋的是 orientation 怎麼計算出來的。

我們知道 CellID 的數據結構是 3位 face + 60位 position + 1位標誌位。那麼對於 Level - n 的非葉子節點,3位 face 之後,一定是有 2 * n 位二進位位,然後緊接著 2*(maxLevel - n) + 1 位以1開頭的,末尾都是0的二進位位。maxLevel = 30 。

例如 Level - 16,中間一定是有32位二進位位,然後緊接著 2*(30 - 16) + 1 = 29位。這29位是首位為1,末尾為0組成的。3 + 32 + 29 = 64 位。64位 CellID 就這樣組成的。

當 n = 30,3 + 60 + 1 = 64,所以末尾的1並沒有起任何作用。當 n = 29,3 + 58 + 3 = 64,於是末尾一定是 100 組成的。10對方向並不起任何作用,最後多的一個0也對方向不起任何作用。關鍵就是看10和0之間有多少個00 。當 n = 28,3 + 56 + 5 = 64,末尾5位是 10000,在10和0之間有一個「00」。「00」是會對方向產生影響,初始的方向應該再異或 01 才能得到。

關於 「00」 會對原始的方向產生影響,這點其實比較好理解。CellID 從最先開始的方向進行四分,每次四分都將帶來一次方向的變換。直到變換到最後一個4個小格子的時候,方向就不會變化了,因為在4個小格子之間就可以唯一確定是哪一個 Cell 被選中。所以這也是上面看到了, Level - 30 和 Level - 29 的方向是不變的,除此以外的 Level 是需要再異或一次 01 ,變換以後得到原始的 orientation。

最後進行轉換,具體代碼實現如下:

func cellIDFromFaceIJWrap(f, i, j int) CellID {

    // 1.

    i = clamp(i, -1, maxSize)

    j = clamp(j, -1, maxSize)

    // 2.

    const scale = 1.0 / maxSize

    limit := math.Nextafter(1, 2)

    u := math.Max(-limit, math.Min(limit, scale*float64((i<<1)+1-maxSize)))

    v := math.Max(-limit, math.Min(limit, scale*float64((j<<1)+1-maxSize)))

    // 3.

    f, u, v = xyzToFaceUV(faceUVToXYZ(f, u, v))

    return cellIDFromFaceIJ(f, stToIJ(0.5*(u+1)), stToIJ(0.5*(v+1)))

}

轉換過程總共分為三步。第一步先處理 i,j 邊界的問題。第二步,將 i,j 轉換成 u,v 。第三步,u,v 轉 xyz,再轉回 u,v,最後轉回 CellID 。

第一步:

func clamp(x, min, max int) int {

    if x < min {

        return min

    }

    if x > max {

        return max

    }

    return x

}

clamp 函數就是用來限定 i , j 的範圍的。i,j 的範圍始終限定在 [-1,maxSize] 之間。

第二步:

最簡單的想法是將(i,j)坐標轉換為(x,y,z)(這個點不在邊界上),然後調用 xyzToFaceUV 方法投影到對應的 face 上。

我們知道在生成 CellID 的時候,stToUV 的時候,用的是一個二次變換:

func stToUV(s float64) float64 {

    if s >= 0.5 {

        return (1 / 3.) * (4*s*s - 1)

    }

    return (1 / 3.) * (1 - 4*(1-s)*(1-s))

}

但是此處,我們用的變換就簡單一點,用的是線性變換。

u = 2 * s - 1

v = 2 * t - 1

u,v 的取值範圍都被限定在 [-1,1] 之間。具體代碼實現:

const scale = 1.0 / maxSize

limit := math.Nextafter(1, 2)

u := math.Max(-limit, math.Min(limit, scale*float64((i<<1)+1-maxSize)))

v := math.Max(-limit, math.Min(limit, scale*float64((j<<1)+1-maxSize)))

第三步:找到葉子節點,把 u,v 轉成 對應 Level 的 CellID。

f, u, v = xyzToFaceUV(faceUVToXYZ(f, u, v))

return cellIDFromFaceIJ(f, stToIJ(0.5*(u+1)), stToIJ(0.5*(v+1)))

這樣就求得了一個 CellID 。

由於邊有4條邊,所以邊鄰居有4個。

    return [4]CellID{

        cellIDFromFaceIJWrap(f, i, j-size).Parent(level),

        cellIDFromFaceIJWrap(f, i+size, j).Parent(level),

        cellIDFromFaceIJWrap(f, i, j+size).Parent(level),

        cellIDFromFaceIJWrap(f, i-size, j).Parent(level),

    }

上面數組裡面分別會裝入當前 CellID 的下邊鄰居,右邊鄰居,上邊鄰居,左邊鄰居。

如果在地圖上顯示出來的話,就是下圖的這樣子。

中間方格的 CellID = 3958610196388904960 , Level 10 。按照上面的方法求出來的邊鄰居,分別是:

3958603599319138304 // 下邊鄰居

3958607997365649408 // 右邊鄰居

3958612395412160512 // 上邊鄰居

3958599201272627200 // 左邊鄰居

在地圖上展示出來:

這裡的共頂點鄰居和文章開始講的頂點鄰居有點區別。並且下面還會有一些看似奇怪的例子,也是筆者在實際編碼中踩過的坑,分享一下。

這裡先說明一種特殊情況,即 Cell 正好在地球的外切立方體的8個頂點上。那麼這個點的頂點鄰居只有3個,而不是4個。因為這8個頂點每個點只有3個面與其連接,所以每個面上有且只有一個 Cell 是它們的頂點鄰居。除去這8個點以外的 Cell 的頂點鄰居都有4個!

j

|

|  (0,1)  (1,1)

|  (0,0)  (1,0)

|

> i

在上述的坐標軸中,i 軸方向如果為1,就落在4個象限的右邊一列上。如果 j 軸方向如果為,就落在4個象限的上面一行上。

假設 Cell Level 不等於 30,即末尾標誌位1後面還有0,那麼這種 Cell 轉換成 i,j 以後,i,j 的末尾就都是1 。

上面的結論可以證明的,因為在 faceIJOrientation 函數拆分 Cell 的時候,如果遇到了都是0的情況,比如 orientation = 11,Cell 末尾都是0,那麼取出末尾8位加上orientation,00000000 11,經過 lookupIJ 轉換以後得到 1111111111 ,於是 i = 1111,j = 1111 ,方向還是 11。Cell 末尾的00還是繼續循環上述的過程,於是 i,j 末尾全是1111 了。

所以我們只需要根據 i,j 判斷入參給的 Level 在哪個象限,就可以把共頂點的鄰居都找到。

假設入參給定的 Level 小,即 Cell 的面積大,那麼就需要判斷當前 Cell (函數調用者) 的共頂點是位於入參 Cell 的4個頂點的哪個頂點上。Cell 是一個矩形,有4個頂點。當前 Cell (函數調用者) 離哪個頂點近,就選那個頂點為公共頂點。再依次求出以公共頂點周圍的4個 Cell 即可。

假設入參給定的 Level 大,即 Cell 的面積小,那麼也需要判斷入參 Cell 的共頂點是位於當前 Cell (函數調用者)的4個頂點的哪個頂點上。Cell 是一個矩形,有4個頂點。入參 Cell 離哪個頂點近,就選那個頂點為公共頂點。再依次求出以公共頂點周圍的4個 Cell 即可。

由於需要判斷位於一個 Cell 的四等分的哪一個,所以需要判斷它的4個孩子的位置情況。即判斷 Level - 1 的孩子的相對位置情況。

    halfSize := sizeIJ(level + 1)

    size := halfSize << 1

    f, i, j, _ := ci.faceIJOrientation()

    var isame, jsame bool

    var ioffset, joffset int

這裡需要拿到 halfSize ,halfSize 其實就是入參 Cell 的孩子的格子的 size 。

    if i&halfSize != 0 {

        // 位於後邊一列,所以偏移量要加上一個格子

        ioffset = size

        isame = (i + size) < maxSize

    } else {

        // 位於左邊一列,所以偏移量要減去一個格子

        ioffset = -size

        isame = (i - size) >= 0

    }

這裡我們根據 halfSize 那一位是否為1來判斷距離矩形的4個頂點哪個頂點近。這裡還需要注意的是,如果 i + size 不能超過 maxSize,如果超過了,就不在同一個 face 上了。同理, i - size 也不能小於 0,小於0頁不在同一個 face 上了。

j 軸判斷原理和 i 完全一致。

    if j&halfSize != 0 {

        // 位於上邊一行,所以偏移量要加上一個格子

        joffset = size

        jsame = (j + size) < maxSize

    } else {

        // 位於下邊一行,所以偏移量要減去一個格子

        joffset = -size

        jsame = (j - size) >= 0

    }

最後計算結果,先把入參的 Cell 先計算出來,然後在把它周圍2個軸上的 Cell 計算出來。

    results := []CellID{

        ci.Parent(level),

        cellIDFromFaceIJSame(f, i+ioffset, j, isame).Parent(level),

        cellIDFromFaceIJSame(f, i, j+joffset, jsame).Parent(level),

    }

如果 i,j 都在同一個 face 上,那麼共頂點就肯定不是位於外切立方體的8個頂點上了。那麼就可以再把第四個共頂點的 Cell 計算出來。

    if isame || jsame {

        results = append(results, cellIDFromFaceIJSame(f, i+ioffset, j+joffset, isame && jsame).Parent(level))

    }

綜上,完整的計算共頂點鄰居的代碼實現如下:

func (ci CellID) VertexNeighbors(level int) []CellID {

    halfSize := sizeIJ(level + 1)

    size := halfSize << 1

    f, i, j, _ := ci.faceIJOrientation()

    fmt.Printf("halfsize 原始的值 = %v-%b", halfSize, halfSize)

    var isame, jsame bool

    var ioffset, joffset int

    if i&halfSize != 0 {

        // 位於後邊一列,所以偏移量要加上一個格子

        ioffset = size

        isame = (i + size) < maxSize

    } else {

        // 位於左邊一列,所以偏移量要減去一個格子

        ioffset = -size

        isame = (i - size) >= 0

    }

    if j&halfSize != 0 {

        // 位於上邊一行,所以偏移量要加上一個格子

        joffset = size

        jsame = (j + size) < maxSize

    } else {

        // 位於下邊一行,所以偏移量要減去一個格子

        joffset = -size

        jsame = (j - size) >= 0

    }

    results := []CellID{

        ci.Parent(level),

        cellIDFromFaceIJSame(f, i+ioffset, j, isame).Parent(level),

        cellIDFromFaceIJSame(f, i, j+joffset, jsame).Parent(level),

    }

    if isame || jsame {

        results = append(results, cellIDFromFaceIJSame(f, i+ioffset, j+joffset, isame && jsame).Parent(level))

    }

    return results

}

下面來舉幾個例子。

第一個例子是相同大小 Cell 。入參和調用者 Cell 都是相同 Level - 10 的。

VertexNeighbors := cellID.Parent(10).VertexNeighbors(10)

// 11011011101111110011110000000000000000000000000000000000000000

3958610196388904960 // 右上角 

3958599201272627200 // 左上角

3958603599319138304 // 右下角

3958601400295882752 // 左下角

第二個例子是不是大小的 Cell 。調用者 Cell 是默認 Level - 30 的。

VertexNeighbors := cellID.VertexNeighbors(10)

// 11011011101111110011110000000000000000000000000000000000000000

3958610196388904960 // 右下角

3958599201272627200 // 左下角

3958612395412160512 // 右上角

3958623390528438272 // 左上角

上面兩個例子可以說明一個問題,同樣是調用 VertexNeighbors(10) 得到的 Cell 都是 Level - 10 的,但是方向和位置是不同的。本質在它們共的頂點是不同的,所以生成出來的4個Cell生成方向也就不同。

在 C++ 的版本中,查找頂點鄰居有一個限制:

DCHECK_LT(level, this->level());

入參的 Level 必須嚴格的比要找的 Cell 的 Level 小才行。也就是說入參的 Cell 的格子面積大小要比 Cell 格子大小更小才行。但是在 Go 的版本實現中並沒有這個要求,入參或大或小都可以。

下面這個舉例,入參比 Cell 的 Level 小。(可以看到成都市已經小成一個點了)

VertexNeighbors := cellID.Parent(10).VertexNeighbors(5)

3957538172551823360 // 右下角

3955286372738138112 // 左下角

3959789972365508608 // 右上角

3962041772179193856 // 左上角

下面這個舉例,入參比 Cell 的 Level 大。(可以看到 Level 15 的面積已經很小了)

VertexNeighbors := cellID.Parent(10).VertexNeighbors(15)

3958610197462646784 // 左下角

3958610195315163136 // 右下角

3958610929754570752 // 左上角

3958609463023239168 // 右上角

最後回來文章開頭問的那個問題中。如何在四叉樹上如何求希爾伯特曲線的鄰居 ?經過前文的一些鋪墊,再來看這個問題,也許讀者心裡已經明白該怎麼做了。

查找全鄰居有一個要求,就是入參的 Level 的面積必須要比調用者 Cell 的小或者相等。即入參 Level 值不能比調用者的 Level 小。因為一旦小了以後,鄰居的 Cell 的面積變得巨大,很可能一個鄰居的 Cell 裡面就裝滿了原來 Cell 的所有鄰居,那這樣的查找並沒有任何意義。

舉個例子,如果入參的 Level 比調用者 Cell 的 Level 小。那麼查找它的全鄰居的時候,查出來會出現如下的情況:

AllNeighbors := cellID.Parent(10).AllNeighbors(5)

這個時候是可以查找到全鄰居的,但是可能會出現重疊 Cell 的情況,為何會出現這樣的現象,下面再分析。

如果入參和調用者 Cell 的 Level 是相同的,那麼查找到的全鄰居就是文章開頭說到的問題了。理想狀態如下:

具體實現如下:

func (ci CellID) AllNeighbors(level int) []CellID {

    var neighbors []CellID

    face, i, j, _ := ci.faceIJOrientation()

    // 尋找最左下角的葉子節點的坐標。我們需要規範 i,j 的坐標。因為入參 Level 有可能比調用者 Cell 的 Level 要大。

    size := sizeIJ(ci.Level())

    i &= -size

    j &= -size

    nbrSize := sizeIJ(level)

    for k := -nbrSize; ; k += nbrSize {

        var sameFace bool

        if k < 0 {

            sameFace = (j+k >= 0)

        } else if k >= size {

            sameFace = (j+k < maxSize)

        } else {

            sameFace = true

            // 上邊鄰居 和 下邊鄰居

            neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j-nbrSize,

                j-size >= 0).Parent(level))

            neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j+size,

                j+size < maxSize).Parent(level))

        }

        // 左邊鄰居,右邊鄰居,以及2個對角線上的頂點鄰居

        neighbors = append(neighbors, cellIDFromFaceIJSame(face, i-nbrSize, j+k,

            sameFace && i-size >= 0).Parent(level))

        neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+size, j+k,

            sameFace && i+size < maxSize).Parent(level))

        // 這裡的判斷條件有2個用途,一是防止32-bit溢出,二是循環的退出條件,大於size以後也就不用再找了

        if k >= size {

            break

        }

    }

    return neighbors

}

上述代碼簡單的思路用注釋寫了。需要講解的部分現在來講解。

首先需要理解的是 nbrSize 和 size 的關係。為何會有 nbrSize ? 因為入參 Level 是可以和調用者 Cell 的 Level 不一樣的。入參的 Level 代表的 Cell 可大可小也可能相等。最終結果是以 nbrSize 格子大小來表示的,所以循環中需要用 nbrSize 來控制格子的大小。而 size 只是原來調用者 Cell 的格子大小。

循環中 K 的變化,當 K = -nbrSize 的時候,這個時候循環只會計算左邊和右邊的鄰居。對角線上的頂點鄰居其實也是左邊鄰居和右邊鄰居的一種特殊情況。接下來 K = 0,就會開始計算上邊鄰居和下邊鄰居了。K 不斷增加,直到最後 K >= size ,最後一次循環內,會先計算一次左邊和右邊鄰居,再 break 退出。

調用者的 Cell 在中間位置,所以想要跳過這個 Cell 到達另外一邊(上下,或者左右),那麼就需要跳過 size 的大小。具體代碼實現是 i + size 和 j + size 。

先看左右鄰居的循環掃描方式。

左鄰居是 i - nbrSize,j + k,k 在循環。這表示的就是左鄰居的生成方式。它生成了左鄰居一列。從左下角開始生成,一直往上生成到左上角。

右鄰居是 i + size,j + k,k 在循環。這表示的就是右鄰居的生成方式。它生成了右鄰居一列。從右下角開始生成,一直往上生成到右上角。

再看上下鄰居的循環掃描方式。

下鄰居是 i + k,j - nbrSize,k 在循環。這表示的就是下鄰居的生成方式。它生成了下鄰居一行。從下鄰居最左邊開始生成,一直往上生成到下鄰居最右邊。

上鄰居是 i + k,j + size,k 在循環。這表示的就是上鄰居的生成方式。它生成了上鄰居一行。從上鄰居最左邊開始生成,一直往上生成到上鄰居最右邊。

舉例:

中間 Cell 的周圍的全鄰居是上圖的 8 的相同 Level 的 Cell。

生成順序用需要標識出來了。1,2,5,6,7,8 都是左右鄰居生成出來的。3,4 是上下鄰居生成出來的。

上面這個例子是都是 Level - 10 的 Cell 生成出來的。全鄰居正好是8個。

AllNeighbors := cellID.Parent(10).AllNeighbors(10)

3958601400295882752,

3958605798342393856,

3958603599319138304,

3958612395412160512,

3958599201272627200,

3958607997365649408,

3958623390528438272,

3958614594435416064

再舉一個 Level 比調用者 Cell 的 Level 大的例子。

AllNeighbors := cellID.Parent(10).AllNeighbors(11)

3958600575662161920,

3958606622976114688,

3958603324441231360,

3958611570778439680,

3958600025906348032,

3958607172731928576,

3958603874197045248,

3958613220045881344,

3958599476150534144,

3958608821999370240,

3958623115650531328,

3958613769801695232

它的全鄰居生成順序如下:

1,2,5,6,9,10,11,12 都是左右鄰居,3,4,7,8 是上下鄰居。我們可以看到左右鄰居是從下往上生成的。上下鄰居是從左往右生成的。

如果 Level 更大,比如 Level - 15 ,就會生成更多的鄰居:

現在再解釋一下如果入參 Level 比調用者 Cell 的 Level 小的情況。

舉例,入參 Level = 9 。

AllNeighbors := cellID.Parent(10).AllNeighbors(9)

3958589305667977216,

3958580509574955008,

3958580509574955008,

3958615693947043840,

3958598101760999424,

3958606897854021632,

3958624490040066048,

3958615693947043840

生成的全鄰居如下:

可以看到本來有8個鄰居的,現在只有6個了。其實生成出來的還是8個,只不過有2個重複了。重複的見圖中深紅色的兩個 Cell。

為何會重疊?

中間調用者的 Level - 10 的 Cell 先畫出來。

因為是 Level - 9 的,所以它是中間那個 Cell 的四分之一。

我們把 Level - 10 的兩個上鄰居也畫出來。

可以看到上鄰居 Up 和頂點鄰居 up-right 都位於同一個 Level - 9 的 Cell 內了。所以上鄰居和右上角的頂點鄰居就都是同一個 Level - 9 的 Cell 。所以重疊了。同理,下鄰居和右下的頂點鄰居也重疊了。所以就會出現2個 Cell 重疊的情況。

而且中間也沒有空出調用者 Cell 的位置。因為 i + size 以後,範圍還在同一個 Level - 9 的 Cell 內。

如果 Level 更小,重疊情況又會發生變化。比如 Level - 5 。

AllNeighbors := cellID.Parent(10).AllNeighbors(5)

3953034572924452864,

3946279173483397120,

3946279173483397120,

3957538172551823360,

3955286372738138112,

3957538172551823360,

3962041772179193856,

3959789972365508608

畫在地圖上就是

重疊的位置也發生了變化。

至此,查找鄰居相關的算法都介紹完了。

空間搜索系列文章:

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: https://halfrost.com/go_s2_Hilbert_neighbor/

相關焦點

  • 教學研討|2.1.2 求曲線的方程
    知識與技能 (1)使學生掌握求曲線的軌跡方程的基本步驟;(2)會用直接法求一些簡單曲線的方程。2.二、教材分析 求曲線方程是解析幾何兩大基本問題之一,課標要求通過具體實例的研究,掌握求曲線方程的一般步驟和一般方法。在直線與圓的章節裡已經滲透過探求曲線方程的初步知識,這些是本節知識方法的生長點。但是學生對如何探求曲線方程僅限於將動點的幾何關係式「翻譯」為代數關係式,缺乏深刻的認識。
  • 高中數學專題——求曲線的軌跡方程
    曲線的軌跡方程在近幾年的高考中考試頻率很高,是高考試題的熱點命題內容,今天我們來看一下求曲線軌跡方程的方法。基礎內容總結:求曲線的軌跡方程的方法求曲線的軌跡方程的方法2、關鍵定義法求軌跡方程的關鍵是弄清各種常見曲線的定義,從而求出曲線的軌跡方程。重點三:相關點法求軌跡方程1、相關點法求曲線方程時一般有兩個動點,一個是主動的,另一個是次動的,要根據題意進行定義。
  • 導數給出曲線切線方程,如何求參數的值,不同的題目一樣的套路
    高中數學,導數,給出曲線切線方程,如何求參數的值,不同的題目一樣的套路。題目內容:若曲線y=x^3+ax+b在點(0,b)處的切線方程為x-y+1=0,求ab的值;已知a∈R,設函數f(x)=x-alnx的圖象在x=1處的切線為L:y=ax+b,求a、b的值。
  • 線性代數4——向量3(叉積)
    叉積的幾何意義  向量的兩個要素是模長和方向,讓我們從這兩個角度考慮叉積的幾何意義。  在模長上,叉積的幾何意義是以兩個向量為邊的平行四邊形的面積:  右手法則很有意思,首先要保持拇指朝上,然後其他四指指向叉積的第一個向量,向內彎曲四指指向另一個向量。如果兩個向量的方向能符合這個手勢,此時拇指的方向就是叉積的方向;如果必須向外彎曲四指,拇指的反方向是叉積的方向。總之,最終能夠以一個舒服的方向豎起拇指就對了。
  • 導數求曲線的公切線,思路其實就這麼簡單
    高考數學,導數,求曲線的公切線,思路其實就這麼簡單。題目內容:若直線L是曲線y=e^x+1的切線,也是曲線y=e^(x+2)的切線,求L的方程。求兩條曲線的公切線,如果同時考慮兩條曲線與直線相切,頭緒會比較亂;為了使思路更清晰,一般是把兩條曲線分開考慮,先分析其中一條曲線與直線相切,再分析另一條曲線與這條直線相切 ,這樣就轉化為簡單的切線問題了,使用平時解決曲線問題的方法就可以順利求出兩條曲線的公切線,只不過分析了兩次相切罷了。
  • 武漢發現叉尾太陽鳥
    武漢晚報訊(記者楊曉雨)11月27日,武漢市觀鳥協會會員鍾永樂在中科院武漢植物園觀測並拍攝到2隻叉尾太陽鳥,我市第420個鳥類新紀錄誕生。  當天,鍾永樂來到植物園進行日常鳥類監測。
  • 巧用Origin從CV曲線中求比電容
  • 希爾伯特
    希爾伯特領導的數學學派是19世紀末20世紀初數學界的一面旗幟,希爾伯特被稱為「數學界的無冕之王」,他是天才中的天才。人物生平希爾伯特在哥廷根的故居希爾伯特出生於東普魯士哥尼斯堡(前蘇聯加裡寧格勒)附近的韋勞,中學時代他就是一名勤奮好學的學生,對於科學特別是數學表現出濃厚的興趣,善於靈活和深刻地掌握以至能應用老師講課的內容。
  • 哥德爾證明(上)-希爾伯特計劃
    19世紀,社會上流行一種不可知論,「We do not know, we shall not know.」1930年,希爾伯特退休前,他在演講中激情澎湃的宣稱:「我們必須知道,我們必將知道!」 希爾伯特去世後,這句話就刻在了他的墓碑上。希爾伯特最廣為人知的是,1900年巴黎國際數學家代表大會上,他發表的著名講演《數學問題》。
  • 基於Matlab的FIR型希爾伯特變換器設計
    摘要:在通信系統中,希爾伯特變換是被廣泛應用的重要變換。為了實現數字解調,通常需要藉助希爾伯特變換器對信號進行分解,利用Matlab設計希爾伯特變換器是一種最為快捷、有效的方法。
  • 四叉樹在碰撞檢測中的應用
    緣起《你被追尾了》中預告了加速碰撞檢測的算法——四叉樹(for 2D),所以本文就來學習一下.分析首先是為什麼要使用四叉樹進行優化,其實《你被追尾了》中已經說了,這裡簡單複習一下,碰撞檢測是一種比較昂貴的操作.
  • 你能否找到一種曲線,來填滿整個空間?
    你能否找到一種曲線,來填滿整個空間呢?這是一個有趣的數學幾何問題,許多數學家都曾研究過它,如希爾伯特,皮亞諾等等,下面就來看看這些曲線的形狀,不得不感嘆數學家頭腦的偉大。這是皮亞諾曲線(首先說明下皮亞諾是著名的數學家,他的著作《數學與猜想》啟發了許多數學大家,非常值得一讀)同樣的形狀不斷變化得到如下圖形這是最早的空間空間填充曲線皮亞諾空間填充曲線是正方形,我們沒必要局限於正方形這是一條簡單的三角形填充曲線
  • 武漢發現叉尾太陽鳥
    11月27日,武漢市觀鳥協會會員鍾永樂在中科院武漢植物園觀測時拍攝到2隻叉尾太陽鳥,我市第420個鳥類新記錄誕生。叉尾太陽鳥。鍾永樂 攝當天,鍾永樂來到植物園進行日常鳥類監測。當時他正在觀測燕雀取食一棵樹上的果實。突然聽到一聲不太一樣的鳥叫,他仔細查找四周,在另一棵樹上發現了兩隻黃色小鳥,他立即拍下了照片。仔細查看照片後,鍾永樂發現這兩隻鳥和以前在外地拍攝過的叉尾太陽鳥很像。他將照片傳給其他會員和鳥類專家,經鑑別,這正是叉尾太陽鳥的雌鳥。
  • 剪叉機構的疲勞壽命分析及結構優化
    圖1 最大應力發生位置2 剪叉機構疲勞壽命估算通過對剪叉機構的靜力分析,得到剪叉機構應力相對集中的構件為下剪叉框架。剪叉機構的材料為Q345鋼,泊松比m = 0.3,彈性模量E = 2×105 MPa,密度r = 7 800 kg/m3,據此得到圖2 所示材料S -N 曲線。
  • 什麼是「希爾伯特空間」?
    希爾伯特1862年出生於哥尼斯堡(今俄羅斯加裡寧格勒),1943年在德國哥廷根逝世。他因為發明了大量的思想觀念(例:不變量理論、公理化幾何、希爾伯特空間)而被尊為偉大的數學家、科學家。最後補充一句:希爾伯特空間(Hilbert space)是有限維歐幾裡得空間(Euclidean space)的一個推廣,使之不局限於實數的情形和有限的維數,但又不失完備性(不像一般的非歐幾裡得空間那樣破壞了完備性)。
  • 如何計算曲線長度?
    對於一條連續的、光滑的曲線,根據定積分的幾何意義,很容易計算曲線與x軸所圍成的區域的面積,但如何計算曲線的長度呢?1.直角坐標曲線曲線f(x)為一條在區間[a,b]上連續且光滑的曲線,如圖1所示。圖1.曲線f(x)示意圖在求曲線的長度前,小編先解釋一個概念。
  • 廣義相對論場方程誕生史:一場愛因斯坦與希爾伯特的究極競賽
    從1905年狹義相對論誕生之後,愛因斯坦開始探尋廣義相對論,但卻遭遇了困難,愛因斯坦研究廣義相對論的目的是要找到描述兩個相互交織過程的數學方程式——引力場如何作用於物質,使之以某種方式進行運動; 物質又如何在時空中產生引力場,使之以某種形式發生彎曲。然而愛因斯坦一直沒有找到完美描述其物理原則的數學表達式。
  • 高考數學,導數,求曲線切線方程的三種重要題型
    高考數學,導數,求曲線切線方程的三種重要題型;主要內容:已知曲線y=2/3 x^3-7x+2/3;求曲線在x=2處的切線方程;考察知識:1、求曲線在某點處的切線方程的解法;2、求曲線過某點的切線方程的解法;3、求兩條曲線的公切線方程的解法。
  • 希爾伯特之夢,以及夢的破滅
    希爾伯特計劃 希爾伯特 | 圖片來源:wikipedia 希爾伯特是一位名副其實的數學大師,有人將他稱為「數學界最後一位全才」,他看待數學的眼光也是相當深刻的。
  • 武漢發現叉尾太陽鳥(圖)
    叉尾太陽鳥。鍾永樂 攝武漢晚報訊(記者楊曉雨)11月27日,武漢市觀鳥協會會員鍾永樂在中科院武漢植物園觀測並拍攝到2隻叉尾太陽鳥,我市第420個鳥類新紀錄誕生。當天,鍾永樂來到植物園進行日常鳥類監測。發現叉尾太陽鳥在一片喬灌混交林內,當時他正在觀測燕雀取食一棵樹上的果實。突然聽到一聲不太一樣的鳥叫,他仔細查找四周,在另一棵樹上發現了兩隻黃色小鳥,他立即拍下了照片。