假如我們把函數都改成遞迴...

2023-12-20 06:01:07

  學演演算法階段時不時會遇到一些遞迴的應用場景,例如DFS,二元樹等相關的題目,遞迴常常能大展身手。不過有意思的一件事情是,若我們把一些本該迭代的演演算法改成遞迴實現,會是什麼樣的情形。

  這是一個很簡單的矩陣加法的例子。

void matrixAdd(const std::vector<std::vector<int>>& a,
               const std::vector<std::vector<int>>& b,
               std::vector<std::vector<int>>& c)
{
    int n1 = a.size(), m1 = a[0].size();
    int n2 = b.size(), m2 = b[0].size();
    assert(n1 == n2 && m1 == m2);

    for (int i = 0; i < n1; ++i)
    {
        for (int j = 0; j < m1; ++j)
        {
            c[i][j] = a[i][j] + b[i][j];
        }
    }
}

  同樣有遞迴版本,很多時候這兩者都是可以相互轉換的。

void __matrixAdd(const std::vector<std::vector<int>>& a, const std::vector<std::vector<int>>& b,
                 std::vector<std::vector<int>>& c, int row, int col)
{
    if (row == static_cast<int>(a.size()))
        return;
    if (col == static_cast<int>(a[0].size()))
    {
        __matrixAdd(a, b, c, row + 1, 0);
        return;
    }

    c[row][col] = a[row][col] + b[row][col];
    __matrixAdd(a, b, c, row, col + 1);
}

void matrixAdd(const std::vector<std::vector<int>>& a,
               const std::vector<std::vector<int>>& b,
               std::vector<std::vector<int>>& c)
{
    int n1 = a.size(), m1 = a[0].size();
    int n2 = b.size(), m2 = b[0].size();
    assert(n1 == n2 && m1 == m2);
    __matrixAdd(a, b, c, 0, 0);
}

  當row越界的時候,直接return不用再往下操作了;而當col越界的時候,可以往下一行重新進行相加操作,這裡也要return,不然後續的操作會導致越界。可以直觀看到程式碼並沒有用到for迴圈,看起來比較簡練。接下來是氣泡排序。

void bubbleSort(std::vector<int>& arr) {
    int n = arr.size();

    // 進行 n-1 輪的氣泡排序
    for (int i = 0; i < n - 1; i++) {
        // 在每一輪中,比較相鄰的兩個元素,將較大的元素向後移動
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                std::swap(arr[j], arr[j + 1]);
            }
        }
    }
}
void bubbleSortRecursive(std::vector<int>& arr, int n) {
    // 基本情況:如果只剩下一個元素,已經有序
    if (n == 1) {
        return;
    }

    // 一次遍歷,將最大的元素移動到末尾
    for (int i = 0; i < n - 1; i++) {
        if (arr[i] > arr[i + 1]) {
            std::swap(arr[i], arr[i + 1]);
        }
    }

    // 遞迴呼叫,對除了最後一個元素的子陣列進行排序
    bubbleSortRecursive(arr, n - 1);
}

  相比原來的迭代版本少了一個for迴圈,程式碼量相差不大。再來看看斐波那契數列,通常它的遞迴實現是隻保留最後一項的,我們也可以寫一個保留中間計算過程的版本。

int fib(std::vector<int>& arr, int n) {
    if (n <= 1) {
        arr[n] = n;
        return arr[n];
    }

    arr[n] = fib(arr, n - 1) + fib(arr, n - 2);
    return arr[n];
}

  字串翻轉也是很容易實現的。

void reverseString(std::string& str, int left, int right) {
    if (left >= right) {
        return;
    }

    // 交換左右字元
    std::swap(str[left], str[right]);

    // 遞迴翻轉剩餘部分
    reverseString(str, left + 1, right - 1);
}

  先到這裡,有什麼好的想法也可以提一提~