在这一系列, 将阅读和编码算法导论的内容, 参考书籍为第四版的 《Introduction to Algorithms》

将使用 C++ 完成算法的具体实现.

C++ 中 std::vector 容器以及相关操作

#include <iostream>
#include <vector>

int main() {

  /** Define number of elements */
  int n = 10;

  /** Initialize vector nums with values 1 through 10 */
  std::vector<int> nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  /** Create sub-vector containing the first 3 elements of nums */
  std::vector<int> sub(nums.begin(), nums.begin() + 3);
  /** Define an empty vector */
  std::vector<int> empty{};

  /** Output length of sub */
  std::cout << "length of sub is " << sub.size() << std::endl;
  /** Output elements of sub */
  std::cout << "elements of sub are:" << std::endl;
  /** Output size of empty vector */
  std::cout << "size of empty arr is " << empty.size() << std::endl;
  /** Accessing empty[0] would be undefined behavior, so it's commented out */
  /** std::cout << "first element of empty arr is " << empty[0] << std::endl; */

  /** Loop through and print each element in sub */
  for (int i = 0; i < sub.size(); i++) {
    std::cout << sub[i] << std::endl;
  }

  /** Swap the first two elements of nums */
  std::swap(nums[0], nums[1]);

  /** Append element 11 to the end of nums */
  nums.push_back(11);

  /** Remove the element at index 3 (fourth element) from nums */
  nums.erase(nums.begin() + 3);

  /** Modify the last element of nums to 100 */
  nums.back() = 100;

  /** Print all elements of nums */
  std::cout << "elements of nums are:" << std::endl;
  for (int i = 0; i < nums.size(); i++) {
    std::cout << nums[i] << std::endl;
  }

  return 0;
}

插入排序

对插入排序伪代码的实现以及调整

本书中的第一个算法即为插入排序, 伪代码见教材 P.19, 按照这个伪代码, 算法的实现如下:

#include <iostream>
#include <vector>

void InsertionSort(std::vector<int> &arr);


void InsertionSort(std::vector<int> &arr) {
  for (int i = 1; i < arr.size(); i++) {

    int key = arr[i];
    int j = i - 1;

    while (j >= 0 && key < arr[j]) {
      /* Move the previous key into the current box and continue to compare */
      arr[j + 1] = arr[j];
      j -= 1;
    }

    /* The case key in previous box is smaller than current one */
    arr[j + 1] = key;
  }
}


int main() {
  std::vector<int> nums = {5, 2, 4, 6, 1, 3};
  InsertionSort(nums);

  std::cout << "array after sort" << ": ";
  for (int i = 0; i < nums.size(); i++) {
    std::cout << nums[i] << ", ";
  }
  return 0;
}

上面代码完全按照书中伪代码实现,实际进行微调, 使代码更顺眼, 调整如下

void InsertionSort(std::vector<int> &arr) {
  const size_t n = arr.size();
  for (size_t i = 1; i < n; ++i) {
    int key = arr[i];
    size_t j = i; /* track the position of the current key */
    while (j > 0 && key < arr[j - 1]) { /** Move arries **/
      arr[j] = arr[j - 1];
      --j;
    }
    if (j != i) { /* Check if the array has been moved */
      arr[j] = key;
    }
  }
}

在新的代码中做出了以下几点修改:

  1. n, i, j, 这些变量从 int转换为了无符号整型, 使得能够表示更大的数据(虽然此处只是一个玩具模型).
  2. 在最后增加了一个判断, 只有确实进行过插入排序, 才让arr[j] = key, 否则当前位置的元素从未移动过, 使用 arr[j] = key 的操作多余.

2.1 节课后练习选做

2.1-2

Rewrite the INSERTION-SORT procedure to sort into monotonically decreasing in- stead of monotonically increasing order.

即写一个反向的插入排序, 简单修改代码即可

void ReverseInsertionSort(std::vector<int> &arr) {
  const size_t n = arr.size();
  for (size_t i = 1; i < n; ++i) {
    int key = arr[i];
    size_t j = i; /* track the position of the current key */
    while (j > 0 && key > arr[j - 1]) { /** Move arries **/
      arr[j] = arr[j - 1];
      --j;
    }
    if (j != i) { /* Check if the array has been moved */
      arr[j] = key;
    }
  }
}

2.1-4

Consider the searching problem:

Input: A sequence of \(n\) numbers \(\{ a_1, \cdots, a_n\}\) stored in array A[1:n] and a value x.

Output An index i such that x equals A[i] or the special value NIL if x does not appear in A.

尝试自己编写的代码

#include <iostream>
#include <vector>

/** Code of the Searching problem
 * in the assignment of 2.1-4 Ch2.1,
 * introduction of algorithms
 * Input: A sequence of n numbers <a1, ..., an>
 * stored in array A[1:n] and a value x
 *
 * Output: An index i such that x equals A[i]
 * or special value NIL if x does not appear in A **/

size_t LinearSearch(std::vector<int> arr, int target);

size_t LinearSearch(std::vector<int> arr, int target) {
  const size_t n = arr.size();
  for (size_t i = 0; i < n; ++i) {
    if (arr[i] == target) {
      return i;
    }
  }
  return n;
}

int main() {
  std::vector<int> nums = {1, 8, 8, 2, 3, 4};
  int target = 2;
  size_t res = LinearSearch(nums, target);
  (res == nums.size()) ? std::cout << "not found" << std::endl
                       : std::cout << res << std::endl;
  return 0;
}

使用AI查找代码的不足:

  • 对于数组,应该传入引用,避免数组的重新拷贝,此外, 针对此例, 应该对数组进行const 修饰, 避免在函数中修改了数组的内容.
  • 返回值建议使用std::nullopt, 这类, 但是本文中全部使用C++11 标准编写, 因此暂时先不管这个.

修改后的代码如下

size_t LinearSearch(const std::vector<int> &arr, int target) {
  const size_t n = arr.size();
  for (size_t i = 0; i < n; ++i) {
    if (arr[i] == target) {
      return i;
    }
  }
  return n;
}

2.1-5

Consider the problem of adding two n-bit binary integers a and b, stored in two \(n\)-element arrays A[0:n-1] and B[0:n-1], where each element is either 0 or 1, \(a = \sum_{i=0}^{n-1} A[i] \cdot 2^i\), and \(b = \sum_{i=0}^{n-1} B[i] \cdot 2^i\). The sum \(c = a + b\) of the two integers should be stored in binary form in an \((n+1)\)-element array C[0:n], where \(c = \sum_{i=0}^n C[i] \cdot 2^i\). Write a procedure ADD-BINARY-INTEGERS that takes as input arrays A and B, along with the length n, and returns array C holding the sum.

这个问题比较简单, 最简单的思路就是将二进制转换为十进制, 做整数的十进制加法, 将结果转换为二进制, 但是这样会有一个问题, 他要求输出的长度比输入的两个数组多一,这样算出来的结果如果没有进位,输出数组可能会和输入数组一样长。

还有一种思路就是直接做二进制计算,这样可以直接制定输出数组的长度。

#include <iostream>
#include <vector>

/** Code of the Add Binary Integers
 * in the assignment of 2.1-5 Ch2.1,
 * introduction of algorithms
 * Input:
 * A binary sequence of n numbers <a0, ..., an-1>
 * B binary sequence of n numbers <b0, ..., bn-1>
 *
 * Output:
 * C binary sequence of n+1 numbers <c0, ..., cn> **/

/* A naive implementation, change A, B into intger, add to get c, finally change c to binary C */
int Bin2Int(const std::vector<bool> &A);
std::vector<bool> Int2Bin(int a);
std::vector<bool> AddBinaryIntegersNaive(const std::vector<bool> &A, const std::vector<bool> &B);

/* Get the result by adding A and B straightforwardly */
std::vector<bool> AddBinaryIntegers(const std::vector<bool> &A, const std::vector<bool> &B);

int Bin2Int(const std::vector<bool>& A) {
  int n = A.size();
  int res = 0.0;
  for (int i = n - 1; i > -1; --i) {
    res += A[i] * (1 << (n - 1 - i));
  }
  return res;
}

std::vector<bool> Int2Bin(int a) {
  std::vector<bool> A;
  if (a == 0) {
    A.push_back(a);
    return A;
  }
  while (a > 0) {
    A.push_back(a & 1);
    a >>= 1;
  }
  std::reverse(A.begin(), A.end()); /** This is stupid, in the future, we will use stack to avoid reverse **/
  return A;
}

std::vector<bool> AddBinaryIntegerNaive(const std::vector<bool> &A, const std::vector<bool> &B) {
  int a = Bin2Int(A);
  int b = Bin2Int(B);
  return Int2Bin(a + b);
}

std::vector<bool> AddBinaryInteger(const std::vector<bool> &A, const std::vector<bool> &B) {
  int n = A.size();
  std::vector<bool> C(n+1, 0); /* Initialize an empty array with size n+1 and all the values are zero */
  int carry = 0;
  /*
   * Current digital = carray + a + b 
   * Last digital = carray */
  for (int i = n - 1; i > -1; --i) {
    int a = A[i];
    int b = B[i];
    int sum = a + b + carry;
    C[i+1] = sum & 1;
    carry = sum >> 1;
  }
  C[0] = carry;
  return C;
}

int main() {
  std::vector<bool> A = {1, 1, 0};
  std::vector<bool> B = {1, 1, 0};
  // std::vector<bool> C = AddBinaryIntegerNaive(A, B);
  std::vector<bool> C = AddBinaryInteger(A, B);

  for (int i = 0; i < C.size(); i++) {
    std::cout << C[i];
  }

  /*
  std::vector<bool> A = {1, 1, 0};
  std::cout << Bin2Int(A) << std::endl;

  std::vector<bool> res = Int2Bin(2);
  std::cout << "length of res is " << res.size() << std::endl; 

  std::vector<bool> bin_vec;
  A = Int2Bin(6);
  for (int i; i < A.size(); i++){
    std::cout << A[i];
  } 
  */
  return 0;
}