《圖說演算法-使用C#》要點摘錄 – 「常見演算法」

書名:《圖說演算法-使用C#》

作者:吳燦銘、胡昭民

所讀版本:博碩文化

分治法

  • 反覆分割問題,使子問題規模不斷縮小,直到這些子問題足夠簡單到可以解決,最後將各子問題的解合併得到原問題的解答

遞歸

  • 一個函數由自身定義或呼叫

  • 必須要有2個條件

    • 中止遞歸出口

    • 遞歸執行過程

  • 例子:菲波那契數列

    • public static int Fibonacci(int idx)
      {
         if (idx == 0) { return 0; }
         else if (idx == 1) { return 1; }
         else { return Fibonacci(idx - 1) + Fibonacci(idx - 2); }
      }

動態規劃(Dynamic Programming)

  • 類似分治法,但會利用某種數據結構將子問題的解保存起來

  • 當解決一樣的子問題時,就可以直接從數據結構中得到子問題的解並返回,而不需要重新解決一次子問題。

  • 例子:優化的菲波那契數列

    • static Dictionary<int, int> fibonacciResultMemo = new Dictionary<int, int>()
      {
        {0, 0 },
        {1, 1 }
      };

      public static int Fibonacci(int idx)
      {
         if (fibonacciResultMemo.ContainsKey(idx)) { return fibonacciResultMemo[idx]; }
         else
        {
             int res = Fibonacci(idx - 1) + Fibonacci(idx - 2);
             fibonacciResultMemo.Add(idx, res);
             return res;
        }
      }

迭代

  • 結果無法用公式一次求解,需要利用迴圈反覆運算

  • 迴圈結構的三個條件:

    • 標記迴圈起點和進度的變數

    • 循環條件

    • 迴圈變數的增減值

  • 例子:計算階層

    • public static int Stairs(int targetStair)
      {
         int sum = 1;
         for (int i = 0; i <= targetStair; i++)
        {
             sum = 1;
             for (int j = i; j > 0; j--)
            {
                 sum = sum * j;
            }
        }
         return sum;
      }

Pascal’s Triangle

  • rC0 = 1 => 每一列的第0欄的值一定為1

  • rCn = rCn-1 * (r – n + 1)/n => 某欄某列元素值公式

    • r = row

    • n = column

回溯法

  • 窮舉法的一種,但在發現目標不正確後馬上返回上一層,而不遞歸到下一層

    • 作者的例子是只能局限在知道終點在起點的方位(右下角)的前提下才能生效的,但如果不知道起、終點的位置,實際上只能使用DFS進行搜索。

    • 修改版:

      • static void Main(string[] args)
        {
          (int x, int y) start = (1, 1);
          (int x, int y) exit = (8, 2);
           int[,] maze = new int[10, 12]
          {
              {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
              {1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 },
              {1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1 },
              {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1 },
              {1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1 },
              {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1 },
              {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1 },
              {1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1 },
              {1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1 },
              {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
          };

           FindPathInMaze(start, exit, maze);

           Console.WriteLine();
        }
        public static void FindPathInMaze((int x, int y) startPos, (int x, int y) goalPos, int[,] maze)
        {
           Stack<(int x, int y)> pathStack = new Stack<(int x, int y)>();
           pathStack.Push(startPos);

           while (pathStack.Count > 0)
          {
              (int x, int y) currPath = pathStack.Pop();

               //Reach Goal, Break Loop
               if (currPath.x == goalPos.x && currPath.y == goalPos.y)
              {
                   maze[currPath.x, currPath.y] = 2;
                   break;
              }

               //Set Current
               maze[currPath.x, currPath.y] = 2;

               if (maze[currPath.x - 1, currPath.y] == 0)
              {
                   pathStack.Push((currPath.x - 1, currPath.y));
              }
               if (maze[currPath.x + 1, currPath.y] == 0)
              {
                   pathStack.Push((currPath.x + 1, currPath.y));
              }
               if (maze[currPath.x, currPath.y + 1] == 0)
              {
                   pathStack.Push((currPath.x, currPath.y + 1));
              }
               if (maze[currPath.x, currPath.y - 1] == 0)
              {
                   pathStack.Push((currPath.x, currPath.y - 1));
              }
          }
        }

貪心算法

  • 考慮局部而不考慮大局,通過每一次計算都選擇當前最好的選擇,逐步逼近目標

  • 不能保證最後的解是最佳解