編程珠璣之第二章:雜耍算法

本文系轉載鏈接 http://www.cnblogs.com/solidblog/archive/2012/07/15/2592009.html

作者在文中的證明思路清晰,不過我只看懂了輾轉相除法的證明,雜耍的證明未看懂,但是仔細模擬雜耍算法的過程會有比較直接的體會。


問題:將大小爲n的數組向左移動i位

基本原理:

     1)先將x[0]移到臨時變量t中
    2)將x[i]移動到x[0]中,x[2i]移動到x[i]中,依次類推
    3)將x中的所有下標都對n取模,直到我們又回到從x[0]中提取元素。不過這時我們從t中提取元素,結束。

     移動過程中不會出現數據丟失的問題,即x[i]已經放到正確的位置後,x[i]就是空位,那麼後面的x[2i]便可以賦值給x[i]。問題的關鍵是理解應該執行幾次上面的操作,證明的結果是重複執行i和n的最大公約數gcd(i, n)次,在紙上隨便寫兩個正整數,按照上面的過程走一遍,就會發現,這樣做事正確的。接下來是鏈接作者的描述,其中輾轉相除的代碼有Bug,已經修改,修改策略可以參考《挑戰程序設計競賽》中的2.6.1節。


#include<stdio.h>

//使用輾轉相除法求最大公約數
int gcd(int a, int b)           //b會不斷變小直到0
{
    if (b == 0)
        return a;
    return gcd(b, a%b);         //a與b的最大公約數 == 較大數a與較小數b的餘數 (如果一開始a小於b,那麼此句會進行交換)
}

//雜耍算法
void rotate(char* a,int n,int i)
{
    int step = gcd(n,i);                    //找到重複執行的次數step
    for(int j = 0; j < step; j++)           //分別以0到step-1作爲起點
    {
        int temp = a[j];
        int current = j;
        while(1)
        {
            int next= (current + i) % n;
            if(next== j)                   //如果要提取(向左移動i位)起點值,則退出
            {
                break;
            }
            a[current] = a[next];          //向左移動i位
            current= next;
        }
        a[current] = temp;                  //將起點的值temp賦給最後空出來的位置(完成向左移動i位的操作)
    }
}

int main()
{
    char a[9] = "abcdefgh";
    rotate(a,8,3);

    printf("%s\n",a);
    return 0;
}

以下按原文格式轉載:

4.輾轉相除法求最大公約數

兩個整數的最大公約數是能夠同時整除它們的最大的正整數。輾轉相除法基於如下原理:

 

兩個整數的最大公約數等於其中較小的數和兩數的差的最大公約數。

 

我們來看一下這個原理的證明:
設兩數爲a、b(b<a),用gcd(a,b)表示a,b的最大公約數。
r=a mod b,r爲a除以b以後的餘數,輾轉相除法即是要證明gcd(a,b)=gcd(b,r)。

證明步驟如下:
1)令c=gcd(a,b),則設a=mc,b=nc
2)r = a mod b,所以r = a - k*b = mc - k*nc = (m - kn) * c。即,r = (m - kn) * c
3)由r = (m - kn) * c 可知:c也是r的因數
4)可以肯定m - kn與n互質(why?)
    假設他們不互質,必然存在大於1的整數d,使得m-kn = xd, n = yd。那麼,m = xd + kyd = (x + ky)d
    那麼,a = mc = (x + ky)dc , b = nc = ydc 。=> a,b的最大公約數爲dc,而不是c。
5)既然m - kn與n互質,所以c = gcd(r,b)。
結論,gcd(a,b)=gcd(b,r)

(輾轉相除法更詳細的描述請參照百度百科:http://baike.baidu.com/view/255668.htm。

5.雜耍算法

雜耍算法中如下兩點我無法理解:

1.爲什麼程序要循環執行gcd(i,n)次
2.這個算法是通過什麼樣的機制就讓所有位置上的元素都移動到了預期的位置

程序的走向我是明白的,但是核心算法始終不得其解。
最終還是需要藉助網絡,搜到了園子內一位朋友寫的文章(關於編程珠璣中習題2.3的一點思考
看完以後,有種豁然開朗的感覺,在此多謝這個朋友了。

不過,那篇文章中有些概念的細節講的不是太清楚,我也是藉助網絡纔有了更清晰的瞭解。
再次,我來總結個精簡吧的吧,寫的不好還請各位包涵!

先從幾個概念開始:

  1. 同餘
    如果兩個整數(a,b)除以同一個整數m,如果得到相同的餘數k。則稱a,b對於模m同餘。
     記作a ≡ b (mod m)
  2. 同餘類
    所謂同餘類是指以某一特定的整數(如m)爲模,按照同餘的方式對全體整數進行的分類。
    對給定的模m,有且恰有m個不同的模m的同餘類。它們是:
       0 mod m,1 mod m,…,(m-1)mod m。
  3. 完全剩餘類
    通過2)我們知道,所有的整數以m爲模可以劃分爲m個沒有交集的集合。
    從每個集合中取一個整數組成一個集合,則這個集合中的m個整數就不存在同餘的整數,這個集合就叫做完全剩餘類。

瞭解了以上三個概念後,我們能夠得出如下的結論:

 

如果i和n互質,那麼序列:
    0   i mod n     2i mod n    3i mod n    …… (n-1)*i mod n
就包括了集合{0,1,2,……n-1}的所有元素

 

我們爲什麼會有這樣的結論呢,下面來證明一下:
前提條件
    對於模n來說,序列0,1,2,……,n-1本身就是一個完全剩餘類(即他們之間兩兩互不同餘)

證明步驟
1)從此序列中任取兩個數字xi,xj(0 <= i,j <= n-1),則有Xi≠Xj (mod n),   注:這裏由於不能打出不同餘字符因此用不等於替代
2)由於i和n是互質的,所以 xi * i ≠ xj * i(mod n)
    =》這就說明,xi從0開始一直取值到n-1,得到的序列0 * i,1 * i,2 *i,……(n-1)*n就是一個完全剩餘類。即集合0,1,2,……n-1}

有了以上的結論,如果i和n互質,下面的賦值過程便能完成所有位置的值的移動:
    t = X[0]
    X[0] = X[i mod n]
    X[i mod n] = X[2i mod n]
    …….
    X[(n-2)*i mod n] = X[(n-1)*i mod n]
    X[ (n-1)*i mod n] = t
以上的賦值操作,賦值操作符的兩邊都得到了一個完全剩餘類,也就是說所有的0 ~ n-1的所有位置都被移動過了
請注意第二個操作,X[0] = X[i mod n]。
        該操作決定了整體的導向,該操作將i mod n位置的值移動到了最開始的位置。
        由於i,2i,……之間的偏移量是相同的,所以整個操作實際上就是講序列向左移動i個位置(超過了開始位置的接到最右邊去)


那麼如果i和n不互質呢?
自然的想法是利用我們已經得到的結論,讓i和n互質,即讓i’ = i/(gcd(i,n)),n’ = n/(gcd(i,n))。
這樣便構造了一對互質的數, i’和n’。這意味着把整個數組的每g=gcd(i,n)個元素組成塊。
由於我們的單位是塊元素,每次相當於把g中的一個元素移到最終位置上,所以總共需要g次移動。


發佈了45 篇原創文章 · 獲贊 15 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章