Array Indexing

Array Indexing from http://acm.nudt.edu.cn/~twcourse/ArrayIndexing.html

「索引」可說是電腦的奇技!一個元素存放到陣列之後,不論是在陣列的哪個地方,只要利用索引值( index ),就能在一瞬間找到元素。

大多數的演算法都運用了「索引」的技巧,讓程式執行速度更快。

以下介紹索引的幾種運用方式。是我自己歸類整理的,並不是標準。

一、定位

概念為:將物件放入陣列中, array[ 位置 ] = 物件。

當元素很多時,我們可以放到陣列裡。我們只要記錄索引值,依舊可以常數時間得到元素。

範例:求最大值。將元素連續地放入陣列,若想紀錄一元素,僅需一索引值。

求最大值

void find_maximum() // 用變數直接記最大值
{
    int array[5] = {3, 6, 9, 8, 1};

    int max = -10000;
    for (int i=0; i<5; i++)
        if (array[i] > max)
            max = array[i];

    cout << "最大值為" << max;
}

void find_maximum() // 用索引值紀錄最大值位置
{
    int array[5] = {3, 6, 9, 8, 1};

    int p = 0;  // 最大值的索引值
    for (int i=1; i<5; i++)
        if (array[i] > array[p])
            p = i;

    cout << "最大值為" << array[p];
}

範例:求子字串。將元素連續地放入陣列,若想紀錄一區間,僅需頭尾的索引值。

求子字串

void substring()
{
    char s[10] = "Hello, world!";
    char t[10];

    int i, j;
    for (i=2, j=0; i<6; i++, j++)
        t[j] = s[i];
    t[j] = '\0';

    cout << "s的子字串[2,6)是" << t;
}

範例:連續數字和。將元素連續地放入陣列,利用問題本身的數學性質以及索引值,快速得到答案。

連續數字和

void consecutive_sum()
{
    int array[5] = {3, 6, 9, 8, 1};
    int sum[5] = {0};

    sum[0] = array[0];
    for (int i=1; i<5; i++)
        sum[i] = sum[i-1] + array[i];

    cout << "區間[2,4]的連續數字和是" << sum[4] - sum[2-1];
}

範例:求中位數。將元素依照大小順序並連續地放入陣列,利用索引值得到位於中間的元素。

求中位數

void median()
{
    int array[5] = {1, 3, 6, 8, 9}; // 由小到大排序
    cout << "中位數是" << array[5/2];
}

範例:二分搜尋法( Binary Search )。將元素依照大小順序並連續地放入陣列,然後夾擠索引值。如果使用的是鏈接串列,因為元素們都沒有索引值,就無法使用二分搜尋法。

二分搜尋法(Binary Search)

void binary_search()
{
    int array[5] = {1, 3, 6, 8, 9}; // 由小到大排序

    int key = 3;                    // 搜尋陣列裡有沒有3
    int left = 0, right = 5;        // 索引值範圍
    while (left < right)
    {
        int mid = (left + right) / 2;
        if (array[mid] < pivot)
            left = mid + 1;
        else if (array[mid] > pivot)
            right = mid - 1;
        else if (array[mid] == pivot)
            return mid;
    }
    return left;
}

範例:二元樹( Binary Tree )。元素的索引值對應到樹的結構,是一種特殊的定位。

二元樹(Binary Tree)

// 得到array[index]的左邊小孩的索引值
int left_child(int index) {return index * 2;}

// 得到array[index]的右邊小孩的索引值
int right_child(int index) {return index * 2 + 1;}

void binary_tree()
{
    int array[5] = {3, 6, 9, 8, 1};
    cout << "根為" << array[0];
    cout << "根的左邊小孩是" << array[left(0)];
    cout << "根的右邊小孩是" << array[right(0)];
}

範例:堆疊( stack )、佇列( queue )。元素連續地放入陣列,然後以改變索引值的方式,來動態增減堆疊及佇列的元素。

堆疊(stack)、佇列(queue)

void stack()
{
    int stack[10];              // 一個堆疊
    int top = 0;                // 索引值

    stack[top++] = 3;           // push
    stack[top++] = 6;           // push

    if (top > 0)                // is empty?
        int n = stack[--top];   // pop
}

void queue()
{
    int stack[10];              // 一個佇列
    int front = 0, rear = 0;    // 索引值

    queue[rear++] = 3;          // push
    queue[rear++] = 6;          // push

    if (front < rear)           // is empty?
        int n = queue[front++]; // pop
}

二、歸類並標記

概念為:物件直接作為陣列的索引值, array[ 物件 ] = 物件的屬性。

範例:正整數集合。物件是:正整數,物件的屬性是:是否在集合裡頭出現。

正整數集合

void add_element()
{
    // 一個有限集合
    bool set[100];

    // 初始化為空集合
    for (int i=0; i<100; i++) set[i] = false;

    // 設定集合元素
    set[5] = true;  // 集合裡有5
    set[3] = true;  // 集合裡有3
    set[3] = false; // 集合裡沒有3
}

範例:統計英文字母出現次數。物件是英文字母,物件的屬性是英文字母的出現次數。

統計英文字母出現次數

void count_letter()
{
    char s[20] = "hi, I am a boy";

    // 歸類並標記
    // 26個字母,字母的ASCII值剛好連續
    int count[26] = {0};
    for (int i=0; s[i] != '\0'; i++)
        if (s[i] >= 'a' && s[i] <= 'z')
            count[ s[i] - 'a' ]++;
        else if (s[i] >= 'A' && s[i] <= 'Z')
            count[ s[i] - 'A' ]++;

    // 印出英文字母的出現次數
    for (int i=0; i<26; i++)
        if (count[i] > 0)
            cout << char('A' + i) << "的個數為" << count[i];
}

範例:計數排序法( Counting Sort )。索引值的大小順序,剛好也是元素的大小順序,故可用於排序。

計數排序法(Counting Sort)

void counting_sort()
{
    // 假設陣列裡數值不重複
    int array[5] = {3, 6, 9, 8, 1};

    // 歸類並標記
    bool count[100] = {false};
    for (int i=0; i<5; i++)
        count[ array[i] ] = true;

    // 索引值的大小順序,剛好也是元素的大小順序。
    for (int i=0, j=0; i<100 && j<5; i++)
        if (count[i])   // 數值不重複
            array[j++] = i;
}

範例:雜湊表( hash table )。元素的索引值由特殊方法決定,是一種特殊的歸類。

雜湊表(hash table)

int hash(int n) // 根據元素的數值來製造一個index
{
    return n * 97 % 100;
}

void hash_table()
{
    int array[5] = {3, 6, 9, 8, 1};

    int table[100];
    for (int i=0; i<5; i++)
    {
        // 替array[i]製造一個index
        int index = hash(array[i]);

        // 將array[i]放入hash table
        table[index] = array[i];
    }
}

三、轉換

概念為: array[ 物件 ] = 另一個物件。類似函數的概念。

範例:取代( Substitution )、移位( Transposition )。取代和移位是密碼學的基礎概念。取代是文字的轉換,移位則是位置的轉換。

取代(Substitution)、移位(Transposition)

void substitution()
{
    char s[10] = "Hello!", t[10] = "";

    // 建立轉換表格
    char crypt[128];
    for (int i=0; i<128; i++) crypt[i] = i;
    crypt['!'] = 'w';
    crypt['H'] = 'Y';

    // 開始轉換
    int n;
    for (n=0; s[n] != '\0'; n++)
        t[n] = crypt[ s[n] ];
    t[n] = '\0';
}

void transposition()
{
    char s[10] = "Hello!", t[10] = "";

    // 建立轉換表格
    int crypt[50];
    for (int i=0; i<50; ++i) crypt[i] = i;
    crypt[2] = 3;
    crypt[3] = 5;
    crypt[5] = 2;

    // 開始轉換
    int n;
    for (n=0; s[n] != '\0'; n++)
        t[n] = crypt[ s[n] ];
    t[n] = '\0';
}

範例: page table 。作業系統的機制,可將虛擬位址轉換成實體位址,是位址的轉換。

page table
程式碼略。
附錄:定址的時間複雜度

當索引值大小為 N 時,有人認為定址的時間複雜度是 O(log2N) ,也有人認為是 O(1) 。這兩種說法都是有其依據的。

以數學的觀點來看: N 共有 log2N 個位元,用二元樹的概念,依照各個位元的數值是 0 或是 1 進行分支,分支到底後就完成定址了。所以定址的時間複雜度是 O(log2N) 。

以電路的觀點來看:一顆中央處理器可以平行處理 32 位元(現在已有 64 位元),只要是介於 0 到 2^32-1 的索引值,都可以在 1 單位時間完成定址,而不必用 32 單位時間來完成定址。所以定址的時間複雜度是 O(1) 。

討論演算法的時間複雜度時,我們傾向假設定址的時間複雜度是 O(1) 。

附錄:定址的範圍

方才提到一顆中央處理器可以平行處理 32 位元,理論上可以定址到 2^32 以內的位址。一個位址一般擁有 1byte 的記憶體大小,所以我們利用定址方式,可以運用的記憶體就有 2^32 * 1byte = 4GB 這麼多。

但是作業系統會保留一些位址、預留一些記憶體空間以維持系統運作,所以使用者實際可以運用的記憶體其實不到 4GB 。

當記憶體沒有插到 4GB 的時候,作業系統利用一種叫做 virtual memory 的技術,以硬碟空間補足記憶體不足 4GB 的部份。

位址是連續不斷的,我們寫程式也都直接假設位址對應到的記憶體空間是連續不斷的,然而實際上並不是連續的。作業系統運用一種叫做 paging 的技術,藉由 page table ,讓記憶體看起來是連續的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值