對大整數N開平方,求其整數部分,有什麼好的演算法嗎?
N&>&>2^64,很大的整數。
首先,我們定義整數開平方為非負整數映射至非負整數的函數:可利用乘法線性搜尋或二分搜尋,得到最大而平方不超過 的根。通過完全平方數(square number)數列,我們還可以在線性搜尋中只用加法,因為兩個完全平方數的差為奇數數列:
uint32_t isqrt0(uint32_t n) {
uint32_t delta = 3;
for (uint32_t square = 1; square &<= n; delta += 2)
square += delta;
return delta / 2 - 1;
}
因為問題是關於大整數的,我們要把大整數的位數()也考慮在內。線性搜尋需要次迭代,每次迭代的加法需時間,合共。而二分搜尋最壞情況需要次迭代,每次的乘法需時間,合共。
而一些數值方法(如牛頓迭代)只適合計算近似值,而且當中也涉及除法。我們換一個思路,參考 Integer Square Roots 這篇文章,開平方可以用類似長除法的方式計算,在二進位中只需要用比較和減法,32位無號整數的C實現如下:uint32_t isqrt1(uint32_t n) {
uint32_t remainder = 0, root = 0, divisor;
for (size_t i = 0; i &< 16; i++) {
root &<&<= 1;
remainder &<&<= 2;
remainder |= n &>&> 30; n &<&<= 2; // Extract 2 MSB from n
divisor = (root &<&< 1) + 1;
if (divisor &<= remainder) {
remainder -= divisor;
++root;
}
}
return root;
}
uint32_t isqrt2(uint32_t n) {
uint32_t remainder = 0, root = 0;
for (size_t i = 0; i &< 16; i++) {
root &<&<= 1;
++root;
remainder &<&<= 2;
remainder |= n &>&> 30; n &<&<= 2; // Extract 2 MSB from n
if (root &<= remainder) {
remainder -= root;
++root;
}
else
--root;
}
return root &>&>= 1;
}
template &
T isqrt(const T n) {
T remainder{}, root{};
auto bitCount = isqrt_traits&
for (size_t i = bitCount; i &> 0; ) {
i -= 2;
root &<&<= 1;
++root;
remainder &<&<= 2;
remainder |= isqrt_traits&
if (root &<= remainder) {
remainder -= root;
++root;
}
else
--root;
}
return root &>&>= 1;
}
template &
struct isqrt_traits {
static_assert(std::is_unsigned&
// Number of bits in multiples of two
static size_t bitCount(const T n) {
T a(n);
size_t count = 0;
while (a &> 0) {
a &>&>= 2;
count += 2;
}
return count;
}
// Extract the i+1, i bits
static uint8_t extractTwoBitsAt(const T n, size_t i) {
return static_cast&
}
};
在 isqrt2 的每個迭代中,我們是通過移位來取得 的兩個位,而在 isqrt&
這裡的 bitCount() 其實可簡單返回 sizeof(T) * 8,但這裡加上簡單優化,循環找出最高的非零兩位。
然後,我們只需要設計一個支持上述操作的大整數類型,以 std::vector& 儲存,U 一般可設置為 uint32_t 或 uint64_t,並加入十六進位流輸出:template &
class biguint {
public:
biguint() : v{0} {}
biguint(std::initializer_list& init) : v(init) {}
biguint operator&<&<=(size_t shift) {
assert(shift &<= unitBitCount);
U inBits = 0;
for (auto x : v) {
U outBits = x &>&> (unitBitCount - shift);
x = (x &<&< shift) | inBits;
inBits = outBits;
}
if (inBits)
v.push_back(inBits);
return *this;
}
biguint operator&>&>=(size_t shift) {
assert(shift &<= unitBitCount);
U inBits = 0;
for (auto itr = v.rbegin(); itr != v.rend(); ++itr) {
U outBits = *itr &<&< (unitBitCount - shift);
*itr = (*itr &>&> shift) | inBits;
inBits = outBits;
}
if (v.back() == 0)
v.pop_back();
return *this;
}
biguint operator|=(uint8_t rhs) {
v[0] |= rhs;
return *this;
}
biguint operator-=(const biguint rhs) {
assert(rhs &<= *this);
U inBorrow = 0;
for (size_t i = 0; i &< v.size(); i++) {
U r = i &< rhs.v.size() ? rhs.v[i] : 0;
U previous = v[i];
v[i] -= r + inBorrow;
inBorrow = v[i] &> previous ? 1 : 0;
}
assert(inBorrow == 0);
while (v.size() &> 1 v.back() == 0)
v.pop_back();
return *this;
}
biguint operator++() {
for (auto x : v)
if (++x != 0)
return *this;
v.push_back(1);
return *this;
}
biguint operator--() {
assert(!(v.size() == 1 v[0] == 0)); // non-zero
for (auto x : v)
if (x-- != 0)
return *this;
return *this;
}
bool operator&<=(const biguint rhs) const {
if (v.size() == rhs.v.size()) {
for (auto i = v.size(); i-- &> 0; )
if (v[i] &< rhs.v[i])
return true;
else if (v[i] &> rhs.v[i])
return false;
return true;
}
else
return v.size() &< rhs.v.size();
}
friend std::ostream operator&<&<(std::ostream os, const biguint x) {
auto f(os.flags());
os &<&< "0x" &<&< std::hex;
for (auto itr = x.v.rbegin(); itr != x.v.rend(); ++itr)
os &<&< *itr;
os.flags(f);
return os;
}
friend struct isqrt_traits&
private:
static const size_t unitBitCount = sizeof(U) * 8;
std::vector& v;
};
template&
struct isqrt_traits&
static size_t bitCount(const biguint& n) {
return biguint&::unitBitCount * (n.v.size() - 1) + isqrt_traits&::bitCount(n.v.back());
}
static uint8_t extractTwoBitsAt(const biguint& n, size_t i) {
return static_cast&
}
};
我簡單測試了一下 45765 和 50! 的開平方:
int main() {
// floor(sqrt(45765)) = 213
std::cout &<&< isqrt1(45765) &<&< std::endl;
std::cout &<&< isqrt2(45765) &<&< std::endl;
std::cout &<&< isqrt&
$ g++ -std=c++11 -o isqrt isqrt.cpp ./isqrt
213
213
213
0x899310e94a8b185249821ebce70
50! 開平方的結果和
https://www.wolframalpha.com/input/?i=floor(sqrt(50!))+in+hex
吻合(知乎插入 URL 有 bug)。
原整代碼在 Big integer square root ?· GitHub
注意:未經完整測試。
---
更新1:按@算海無涯 的提示,時間複雜度的次序應為
---更新2:isqrt0() 之前有錯,謝 @LOOP 反饋repo/gmp-6.1: e225fb40ca63 mpn/generic/rootrem.c
1. 整數的平方間隔越來越大:
1,4,9,16,25,。。。
所以當n比較大的時候,n^2 和(n+1)^2之間間隔應該很大,而這之間的數的平方根的整數部分都是n。
2. 應該可以採用newton 法Newton"s method,不過你可以放寬收斂的條件,因為你找的是整數部分。
跑個題。
int l;int main(int o,char **O,
int I){char c,*D=O[1];if(o&>0){
for(l=0;D[l ];D[l
++]-=10){D [l++]-=120;D[l]-=
110;while (!main(0,O,l))D[l]
+= 20; putchar((D[l]+1032)
/20 ) ;}putchar(10);}else{
c=o+ (D[I]+82)%10-(I&>l/2)*
(D[I-l+I]+72)/10-9;D[I]+=I&<0?0
:!(o=main(c/10,O,I-1))*((c+999
)%10-(D[I]+92)%10);}return o;}
來自IOCCC 2001年的獲獎作品。
摘下眼鏡,風味更佳。大整數開方 | Tom smith"sNOIP2011初賽題,用二分做
看到這題腦袋裡蹦出的是NOIP2011(?可能是,不太確定年份了)提高組初賽試題……
你見過哪些神一樣的優化操作? - 高天的回答
float fast_sqrt(float x)
{
uint32_t x_bits = 0;x_bits = *((uint32_t*) x);
//猜猜這是什麼?
x_bits = (x_bits &>&> 1) + 532369198;return *((float*) x_bits);
}作者:高天
鏈接:https://www.zhihu.com/question/44213758/answer/97229375
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
各位大豬所說的都是在迭代基礎上,只有使用計算機才能解出的方式,有一種使用二次函數手動計算出解的方法,想必用二次函數原型編程代碼更少更簡單,查遍了百度以及各大博客也沒見到這個表達式子。可惜了了
一般來說,在乘法複雜度為nlogn(n為位長)前提下
開方的複雜度為上限至少可以低至nlogn*logn方法是牛頓迭代,
具體是先牛頓迭代計算平方根倒數,1/sqrt(N)=r.有效精度不少於平方根的位長k位,計算r*N,同樣有效精度不少於平方根位長k位,
則r*N的前k位就是平方根的近似值,誤差只要有效精度不少於k,則誤差至多為1。
其中牛頓迭代時,精度是倍增的,實際計算時可以只保留不少於當前有效精度即可提高速度!
實測C++代碼,開方的速度大約是乘法的1.5至3倍的時間,
平方根倒數的方法其它答主有提供鏈接牛頓迭代,複雜度可以做到lognloglogn
參考折半查找演算法
我的直觀想法:平方根本質和除法一樣,如果只求整數部分,對於n位數只需要O=次運算(一次運算指一個乘法和一個減法)。
實現比二分法複雜,但運算次數永遠是二分法(lg n / lg2)的一半。但二分法也不慢,例如2^1024隻需要300次運算。a=sqrt(n)print int(a)vb編程
牛頓迭代 或者2分 時間複雜度應該都是logn的個人感覺2分好寫一點
我來說個C++實現的思路:
1.數據結構使用bitset和string,方便大數的表示和顯示;2.演算法採用二分搜索;
3.搜索範圍依據。舉個咧子,10 * 10 = 100;100 * 100 = 10000;1000 * 1000 = 1000000...100和10000之間的值在10和100之間搜;10000和1000000之間的值在100和1000之間搜。二進位表示的數,我想也有類似規律。附:感覺有開源代碼,大神們應該早就已經實現了
int sqrt(int x) {
// write your code here
if(x &<= 0 )
return 0;
int left = 1, right = x;
int middle;
while(1) {
middle = left + (right - left) / 2;
if((middle * middle) == x)
return middle;
if(middle &> x / middle) { // 防止溢出啊!
right = middle;
}
else if((middle &<= (x / middle)) (right - middle) == 1) {
return middle;
}
else if((middle &<= (x / middle)) (right - middle) &> 1) {
left = middle;
}
}
}
不是大整數求根,純粹是插隊雷神之錘裡面用到的快速演算法
https://zh.m.wikipedia.org/wiki/平方根倒數速演算法
https://en.m.wikipedia.org/wiki/Fast_inverse_square_root
推薦閱讀:
※有沒有一種加密演算法可以識別不同密鑰加密的同一明文?
※有什麼理論複雜但是實現簡單的演算法?
※如何更好的理解和掌握 KMP 演算法?
※編程語言是怎麼設計出來的?
※Leetcode的weekly contest半小時AC 4題的那些人是怎麼做到的?