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

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

作者:吳燦銘、胡昭民

所讀版本:博碩文化

搜尋方法

  • 基於資料量大小:

    • 資料量大:外部搜尋

    • 資料量小:內部搜尋

  • 基於被搜尋的數據是否異動:

    • 否:靜態搜尋

    • 是:動態搜尋

搜尋算法

線性搜尋法
  • 從頭到尾搜索全部數據

  • 優點:搜索前不對資料作任何處理

  • 缺點:搜尋速度較慢

  • 時間複雜度:

    • 如果數據沒有重覆,找到數據可中止搜索,最差情況為O(n)

    • 平均情況,數據出現機率相等,需進行(n+1)/2次比較

    • 數據量很大是不適合用線性搜尋

  • 線性搜尋

    • public static bool SequentialSearch(int[] arr, int target)
      {
         foreach (int item in arr)
        {
             if(item == target) { return true; }
        }
         return false;
      }
二分搜尋法
  • 搜尋對象需要事先排序好,而且數據量必須能直接在記憶體中執行。

  • 適用於靜態數據

  • 將數據分割成兩等份,比較目標值和中間值的大小:

    • 目標值>中間值:目標值在右邊

    • 目標值<中間值:目標值在左邊

    • 目標值==中間值:找到目標值

  • 時間複雜度:每次搜尋都會比上次少一半的範圍 -> O(log n)

  • 二分查找

    • public static int BinarySearch(int[] arr, int target)
      {
         int left = 0;
         int right = arr.Length - 1;
         int mid = 0;

         while(left <= right)
        {
             mid = (right - left) / 2 + left;
             if(arr[mid] == target) { return mid; }
             else if(arr[mid] < target)
            {
                 left = mid + 1;
            }
             else
            {
                 right = mid - 1;
            }
        }
         return -1;
      }
內插搜尋法
  • 依照數據位置的分布,利用公式預測數據的所在位置,再以二分法的方式漸漸逼近。

  • 內插法假設數據平均分布在陣列中,而每一筆數據的差距相當接近/有一定比例的距離

    • 內插法公式:mid = left + ((target – arr[left]) / (arr[right] – arr[left])) * (right – left)

  • 時間複雜度:平均而言優於O(log n)

  • 須先排序數據

  • 內插搜尋

    • public static int InterpolationSearch(int[] arr, int target)
      {
         int left = 0;
         int right = arr.Length - 1;
         int interpolate = 0;

         while(left <= right && interpolate != -1)
        {
             interpolate = (int)((float)(target - arr[left]) * (right - left) / (arr[right] - arr[left]));
             int mid = left + interpolate;

             if(mid > right + 1 || mid < -1) { return -1; }
             else if(interpolate > arr[left] && interpolate > arr[right]) { return -1; }

             if(arr[mid] == target)
            {
                 return mid;
            }
             else if (arr[mid] < target)
            {
                 left = mid + 1;
            }
             else
            {
                 right = mid - 1;
            }
        }

         return -1;
      }
費氏搜尋法
  • 以費氏級數分割數據,比二分法好的是只用到加減法而不需用到乘除法

  • 費氏搜尋樹

    • 左右子樹均為費氏樹

    • 根節點(k)的費氏級數基於數據數量(n)決定:F(k + 1) >= n+1

    • 子節點與父節點的差值絕對值為費氏數

    • 當k >= 2

      • 費氏樹的樹根為Fib(k)

      • 左子樹為(k – 1)階費氏樹(樹根為Fib(k – 1))

      • 右子樹為數(k – 2)階費氏樹(樹根為Fib(k) + Fib(k – 2))

    • 若n + 1 != 費氏數的值,則找出存在一個m 使用 Fib(k + 1) – m = n + 1,m = Fib(k + 1) – (n + 1),再依費氏樹的建立原則完成費氏樹的建立,最後將樹的各節點減去m,把值<1的節點去掉。

  • target取值範圍

    • target < Fib(k) => target在1 到 Fib(k) – 1之間

    • target == Fib(k) => 找到數據

    • target > Fib(k) => target在 Fib(k) + 1 到 Fib(k+1) – 1之間

  • 時間複雜度:

    • 平均情況:O(log2N)

    • 最壞情況比二分查找慢

  • 算法複雜,需額外產生費氏樹

  • 費氏搜尋

    • public static int Fib_Search(int[] arr, int target)
      {
         //F(根節點 + 1) = 數組元素數量 + 1
         int idx = 2;
         while (Fib(idx) <= arr.Length) { idx++; }
         idx--;

         int rootNode = Fib(idx); //定義根節點
         int diff1 = Fib(idx - 1); //上一個費氏數
         int diff2 = rootNode - diff1; //上二個費氏數
         rootNode--;

         while (true)
        {
             if (target == arr[rootNode]) { return rootNode; }
             else
            {
                 if (idx == 2) { return arr.Length; }
                 if (target < arr[rootNode])
                {
                     rootNode = rootNode - diff2;
                     int temp = diff1;
                     diff1 = diff2;
                     diff2 = temp - diff2;
                     idx = idx - 1;
                }
                 else
                {
                     if (idx == 3) { return arr.Length; }
                     rootNode = rootNode + diff2;
                     diff1 = diff1 - diff2;
                     diff2 = diff2 - diff1;
                     idx = idx - 2;
                }
            }
        }
      }
      static int Fib(int n)
      {
         if (n == 1 || n == 0) { return n; }
         else { return Fib(n - 1) + Fib(n - 2); }
      }