開發高效演算法之初窺

geeks_reign發表於2021-06-27

演算法時間複雜度的常用遞推關係

遞迴關係對於分析演算法時間複雜度非常有用,下表總結了常用的地推關係:

遞推關係 複雜度結果 示例
T(n)=T(n/2) + O(1) T(n) = O(logn) 二分查詢
T(n)=T(n-1) + O(1) T(n) = O(n) 線性查詢
T(n)=2T(n/2) + O(1) T(n) = O(n) 尋最值的二分遞迴查詢
T(n)=2T(n/2) + O(n) T(n) = O(nlogn) 歸併排序
T(n)=T(n-1) + O(n) T(n) = O(n^2) 選擇排序
T(n)=2T(n-1) + O(1) T(n) = O(2^n) 漢諾塔

不同複雜度函式的比較關係如下:

O(1) < O(log n) < O(n) < O(nlogn) < O(n^2) < O(2^n)

演算法示例

字串的最長非遞減子串

public static String maxConsecutiveSortedSubstring(String s) {
      int maxSize = 1;
      int tmpbeg = 0;
      int beg = 0;
      int tmpMax = 1;
      for (int i=1; i < s.length(); i++) {
         if (s.charAt(i) >= s.charAt(i-1)) {
             tmpMax++;
             if (tmpMax > maxSize) {
                maxSize = tmpMax;
                beg = tmpbeg;
             }
                
         } else {
             tmpMax = 1;
             tmpbeg = i;
         }
         
      }
      return s.substring(beg, beg + maxSize);
  }

動態規劃求斐波那契序列

public class Fibnacci {
  public static long fib(long n) {
    long f0 = 0;
    long f1 = 1;
    long f2 = 1;
    if (n==0)
      return f0;
    else if (n==1)
      return f1;
    else if (n==2)
      return f2;
    for (int i=3; i <=n; i++) {
      f0 = f1;
      f1 = f2;
      f2 = f0 + f1;
    }
    return f2;
  }
}

動態規劃通過解決子問題,然後將子問題的結果結合來獲得整個問題的解的過程。這自然地引向遞迴的解答。然而,使用遞迴效率不高,因為子問題相互重疊了。動態規劃的關鍵思想只解決子問題一次,並將子問題的結果以備後用,從而避免了重複的子問題求解。

求最大公約數的歐幾里得演算法

public class EuclidGCD {
  public static int gcd(int m, int n) {
    if (m % n == 0)
      return n;
    else
      return gcd(n, m%n);
  }
}

埃拉托色尼篩選法求素數

import java.util.Scanner;

public class SieveOfEratosthenes {
  public static void main(String[] args) {
    System.out.println("Find all prime numbers <= n.");
    int n = input.nextInt();
    
    boolean[] primes = new boolean[n+1];
    
    for (int i=0; i < primes.length; i++) {
      primes[i] = true;
    }
    for (int k = 2; k <= n/k; k++) {
      if (primes[k]) {
        for (int i = k; i<=n/k; i++) {
          primes[k*i] = false; // k*i is not prime
        }
      }
    }
    int count = 0;
    for (int i=2; i<primes.length; i++) {
      if (primes[i]) 
        count++;
    }
    System.out.println("\n" + count + " prime(s) less than or equal to " + n);
  }
}

分治方法與最近點對問題

分而治之(divide-and-conquer),這個方法將問題分解為子問題,解決子問題,然後將子問題的解答合併從而獲得整個問題的解答。和動態規劃方法不同的是,分而治之方法中的子問題不會交叉。子問題類似初始問題,但具有更小的規模,因此可以應用遞迴來解決這樣的問題。事實上,所有的遞迴的解答都遵循分而治之方法。

步驟1: 以x座標的升序對點進行排序,對於x座標一樣的點,按它的y座標排序,這樣就得到一個排好序的點構成的線性表S;

步驟2: 使用排好序的線性表的中點將S劃分為兩個大小相等的子集S1和S2,讓中點位於S1。遞迴地找到S1和S2中的最近點對,設d1和d2分別表示兩個子集中最近點對點距離。

步驟3: 找出S1和S2中點點之間距離最近點點對,它們之間的距離用d3表示,則最近的點對距離為min(d1, d2, d3)的點對。

import java.util.Arrays;
import java.util.ArrayList;

public class ClosedPair {
  public static Pair getClosestPair(double[][] points) {
    Point[] pointsOrderedOnX = new Point[points.length];

      for (int i = 0; i < pointsOrderedOnX.length; i++)
        pointsOrderedOnX[i] = new Point(points[i][0], points[i][1]);

      return getClosestPair(pointsOrderedOnX);
  }
  
  public static Pair getClosetPair(Point[] points) {
    Arrays.sort(points);

      // Locate the identical points if exists
      Pair pair = checkIdentical(points);
      if (pair != null)
        return pair; // The distance between the identical points is 0

      Point[] pointsOrderedOnY = points.clone();
      Arrays.sort(pointsOrderedOnY, new CompareY());

      return distance(points, 0, points.length - 1, pointsOrderedOnY);
  }
  /** Locate the identical points if exist */
  public static Pair checkIdentical(Point[] pointsOrderedOnX) {
    Pair pair = new Pair();
    for (int i = 0; i < pointsOrderedOnX.length - 1; i++) {
      if (pointsOrderedOnX[i].compareTo(pointsOrderedOnX[i + 1]) == 0) {
        pair.p1 = pointsOrderedOnX[i];
        pair.p2 = pointsOrderedOnX[i + 1];
        return pair;
      }
    }
    return null;
  }
  
public static Pair distance(Point[] pointOrderedOnX, int low, int high, Point[] pointsOrderedOnY) { 
    if (low >= high) // Zero or one point in the set
      return null;
    else if (low + 1 == high) {
      // Two points in the set
      Pair pair = new Pair();
      pair.p1 = pointsOrderedOnX[low];
      pair.p2 = pointsOrderedOnX[high];
      return pair;
    }

    // Step 2
    int mid = (low + high) / 2;
    Pair pair1 = distance(pointsOrderedOnX, low, mid, pointsOrderedOnY);
    Pair pair2 = distance(pointsOrderedOnX, mid + 1, high, pointsOrderedOnY);

    double d;
    Pair pair = null;
    if (pair1 == null && pair2 == null) {
      d = Double.MAX_VALUE;
    } else if (pair1 == null) {
      d = pair2.getDistance();
      pair = pair2;
    } else if (pair2 == null) {
      d = pair1.getDistance();
      pair = pair1;
    } else {
      d = Math.min(pair1.getDistance(), pair2.getDistance());
      if (pair1.getDistance() <= pair2.getDistance())
        pair = pair1;
      else
        pair = pair2;
    }

    // Obtain stripL and stripR
    ArrayList<Point> stripL = new ArrayList<Point>();
    ArrayList<Point> stripR = new ArrayList<Point>();
    for (int i = 0; i < pointsOrderedOnY.length; i++) {
      if (pointsOrderedOnY[i].x <= pointsOrderedOnX[mid].x
          && pointsOrderedOnY[i].x >= pointsOrderedOnX[mid].x - d)
        stripL.add(pointsOrderedOnY[i]);
      else if (pointsOrderedOnY[i].x > pointsOrderedOnX[mid].x
          && pointsOrderedOnY[i].x <= pointsOrderedOnX[mid].x + d)
        stripR.add(pointsOrderedOnY[i]);
    }

    // Step 3: Find the closest pair for a point in stripL and
    // a point in stripR
    double d3 = d;
    int r = 0;
    for (int i = 0; i < stripL.size(); i++) {
      while (r < stripR.size() && stripL.get(i).y > stripR.get(r).y + d)
        r++;

      // Compare a point in stripL with at most six points in stripR
      int r1 = r; // Start from r1 up in stripR
      while (r1 < stripR.size() && stripR.get(r1).y <= stripL.get(i).y + d) {
        if (d3 > distance(stripL.get(i), stripR.get(r1))) {
          d3 = distance(stripL.get(i), stripR.get(r1));
          pair.p1 = stripL.get(i);
          pair.p2 = stripR.get(r1);
        }
        r1++;
      }
    }
    return pair;
  }
  
public static double distance(Point p1, Point p2) {
    return distance(p1.x, p1.y, p2.x, p2.y);
  }
  
  public static double distance(double x1, double y1, double x2, double y2) {
    return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
  }
  /** Define a class for a point with x- and y- coordinates */
  static class Point implements Comparable<Point> {
    double x;
    double y;

    Point(double x, double y) {
      this.x = x;
      this.y = y;
    }

    @Override
    public int compareTo(Point p2) {
      if (this.x < p2.x)
        return -1;
      else if (this.x == p2.x) {
        // Secondary order on y-coordinates
        if (this.y < p2.y)
          return -1;
        else if (this.y == p2.y)
          return 0;
        else
          return 1;
      } else
        return 1;
    }
  }

  /**
   * A comparator for comparing points on their y-coordinates. If y-coordinates
   * are the same, compare their x-coordinator.
   */
  static class CompareY implements java.util.Comparator<Point> {
    public int compare(Point p1, Point p2) {
      if (p1.y < p2.y)
        return -1;
      else if (p1.y == p2.y) {
        // Secondary order on x-coordinates
        if (p1.x < p2.x)
          return -1;
        else if (p1.x == p2.x)
          return 0;
        else
          return 1;
      } else
        return 1;
    }
  }

  /** A class to represent two points */
  public static class Pair {
    Point p1;
    Point p2;

    public double getDistance() {
/*      if (p1 == null || p2 == null)
        return Double.MAX_VALUE;
      else */
        return distance(p1, p2);
    }

    @Override
    public String toString() {
      return "(" + p1.x + ", " + p1.y + ") and (" + p2.x + ", " + p2.y + ")";
    }
  }
}

回溯法與八皇后問題

回溯法(backtyacking)漸進地尋找一個備選方案,一旦確定該被選方案不可能是一個有效方案的時候則放棄掉,繼而尋找一個新的備選方案。

public class EightQueens {
  private static final int SIZE = 8;
  private int[] queens = {-1, -1, -1, -1, -1, -1, -1, -1};
  
  // check if a queen can be placed at row i and column j
  private boolean isValid(int row, int column) {
    if (queens[row-i] == column  // check column
       || queens[row-i] == column - i // check upleft diagonal
       || queens[row-i] == column + i) //check upright diagonal
      return false;  // there is a conflict
    return true;
  }
  
  private int findPosition(int k) {
    int start = queens[k] + 1;  // search for a new placement
    for (int j = start; j < SIZE; j++) {
      if (isValid(k, j))
        return j;  // (k, j) is the place to put the queen now.
    }
    return -1;
  }
  
  public boolean search() {
    int k = 0;
    while (k >= 0 && k < SIZE) {
      int j = findPosition(k);
      if (j < 0) {
        queens[k] = -1;
        k--;
      } else {
        queens[k] = j;
        k++;
      }
    }
    return (k==-1)? false:true;
  }
}

附:遞迴解法

public class EightQueens {
  private static final int SIZE = 8;
  private int[] queens = new int[SIZE];		// queen positions.
  
  // check if a queen can be placed at row i and column j
  private boolean isValid(int row, int column) {
    for (int i==1; i <= row; i++)
      if (queens[row-i] == column  // check column
         || queens[row-i] == column - i // check upleft diagonal
         || queens[row-i] == column + i) // check upright diagonal
        return false;  // there is a conflict
    return true;
  }
  
  private boolean search(int row) {
    if (row == SIZE)  // stop condition
      return true; // a solution found to place 8 queens in 8 rows.
    
    for (int column=0; column<SIZE; column++) {
      queens[row] = column;  // place q queen at (row, column)
      if (isValid(row, column) && search(row+1))
        return true;  // found, thus return to exit for loop
    }
    // no solution for a queen placed at any column of this row
    return false;
  }
  
  public boolean search() {
    return search(0);
  }
}

相關文章