开发高效算法之初窥

算法时间复杂度的常用递推关系

递归关系对于分析算法时间复杂度非常有用,下表总结了常用的地推关系:

递推关系 复杂度结果 示例
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);
  }
}

posted @ 2021-06-27 22:15  geeks_reign  阅读(184)  评论(0编辑  收藏  举报