普林斯顿·算法·PART I:MergeSort

Mergesort

1.Mergesort

基本思想:

  • 将数组一分为二(Divide array into two halves)
  • 对每部分进行递归式地排序(Recursively sort each half)
  • 合并两个部分(Merge two halves)

归并排序体现的是一种分治思想

实现
public class Merge
{
    private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi)
        {
            assert isSorted(a, lo ,mid);        //检查a[lo..mid]是否有序
            assert isSorted(a, mid + 1, hi);      //检查a[mid+1..hi]是否有序

            for(int k = lo; k <= hi; k++)       //复制数组
                aux[k] = a[k];

            int i = lo, j = mid + 1;
            for(int k = lo; k <= hi; k++)
            {
                if      (i > mid)              a[k] = aux[j++];
                else if (j > hi)               a[k] = aux[i++];
                else if (less(aux[j], aux[i])) a[k] = aux[j++]; //aux[j]<aux[i]
                else                           a[k] = aux[i++];
            }

            assert isSorted(a, lo, hi);
        }


    private static void sort(Comparable[] a, Comparable[] aux, int low, int hi)
    {
        if (hi <= lo) return;
        int mid = lo + (hi - lo) / 2;
        sort(a, aux, lo, mid);
        sort(a, aux, mid + 1, hi);
        merge(a, aux, lo, mid, hi);
    }

    public static void sort(Comparable[] a) //final
    {
        aux = new Comparable[a.length];
        sort(a, aux, 0, a.length - 1);
    }
}

注:Assert(断言)功能:检查表达式内的值,若为true,则程序正常运行,若为false,则抛出异常,终止运行。

算法复杂度为N*log(N)

优化

问题:归并排序需要根据数组大小N开辟额外的内存

原地算法(in-place Algorithm):占用额外空间小于等于c log(N)的排序算法。

插入排序、选择排序、希尔排序都属于原地算法。归并排序不属于原地算法。

改善1:对小数组使用插入排序

归并排序要为小的子数组的开辟投入很多开销(开辟数组除了元素占用内存,数组本身还有固定的开销)
当子数组大小超过7,停止(Cutoff)使用插入排序。

private static void sort(Comparable[] a, Comparable[] aux, int low, int hi)
{
    if (hi <= lo + CUTOFF - 1)
    {
        Insertion.sort(a, lo, hi);
        return;
        }
    int mid = lo + (hi - lo) / 2;
    sort(a, aux, lo, mid);
    sort(a, aux, mid + 1, hi);
    merge(a, aux, lo, mid, hi);
}

改善2:当数组排序好时,停止计算

两部分都已经排序完毕后,若前半部分的最后一个元素大于后半部分的第一个元素,则证明整个序列都是有序的。

private static void sort(Comparable[] a, Comparable[] aux, int low, int hi)
{
    if (hi <= lo) return;
    int mid = lo + (hi - lo) / 2;
    sort(a, aux, lo, mid);
    sort(a, aux, mid + 1, hi);
    if (!less(a[mid + 1], a[mid])) return;//a[mid]<a[mid+1]终止
    merge(a, aux, lo, mid, hi);
}

2.Sorting complexity

任何基于比较的排序算法在最坏情况下至少要lg(N!) ~ N lgN 次比较。

计算模型:决策树(decision tree)

成本模型:比较(compares)

上界:使用归并排序可以达到NlgN复杂度

下界:NlgN

最优算法:归并排序

归并算法的时间上界达到排序算法的时间下界;但耗费过多内存空间,从内存占用方面来讲并不是最优的。

3.comparators

比较器接口(Comparator interface):用可选顺序(alternate order)进行排序。

public interface Comparator<key>
  int compare(Key v, Key w)          //比较元素v和w

使用Comparator接口进行选择排序(Insertion sort)

public static void sort(Object[] a, Comparator comparator)
{
    int N = a.length;
    for (int i = 0; i < N;i++)
        for(int j =  i; j > 0 && less(comparator, a[j], a[j-1]); j--)
            exch(a, j, j-1);        
}

private static boolean less(Comparator c, Object v, Object w)
{    return c.compare(v, w) < 0;    }

private static void exch(Object[] a, int i, int j)
{    Object swap = a[i]; a[i] = a[j]; a[j] = swap;    }

实现Comparator接口

定义实现 Comparator 接口的类
实现 compare() 方法

public class Student 
{  
    public static final Comparator<Student> BY_NAME = new ByName();  
    public static final Comparator<Student> BY_SECTION = new BySection();  
    private final String name;  
    private final int section;  
    ...
    private static class ByName implements Comparator<Student>                        //ByName类实现了Comparator接口
    {
        public int compare(Student v, Student w)  
        {  return v.name.compareTo(w.name);  }  
    }  
   
    private static class BySection implements Comparator<Student>                     //BySection实现了Comparator接口
    {  
        public int compare(Student v, Student w)
        {  return v.section - w.section;  }  
    }  
}

4.stability

稳定性(Stability):先按性质A排序,再按性质B排序,性质B相同的那些项是否仍然是按性质A排序的?

一个稳定的排序,相同值的元素应仍保持相对顺序(relative order)

稳定的算法:插入排序、Mergesort等

不稳定的算法:选择排序:Shellsort等

Interview Questions: Mergesort

1.如何只用一半长度的辅助数组来实现归并排序?

将前半数组复制到辅助数组中,将新数组元素直接保存在原数组中。

public class SmallerAux {
    public static void merge(Comparable[] a, int n) {
        // 特殊情况直接返回
        if (a[n-1] <= a[n]) {
            return;
        }
        
        Comparable[] aux = new Comparable[n];// aux -- n; a -- 2n
        for (int i = 0; i < n; i++) {
            aux[i] = a[i];
        }
        int i = 0;
        int j = n;
        for (int k = 0; k < 2 * n; k++) {
            if (i >= n) {
                break;		// 左半边数组元素用完后,无需移动右半边数组剩余元素a[k] = aux[j++];
            } else if (j >= 2 * n) {
                a[k] = aux[i++];
            } else if (less(aux[i], a[j])) {
                a[k] = aux[i++];
            } else {
                a[k] = a[j++];
            }
        }
    }

    private static boolean less(Comparable a, Comparable b) {
        return a.compareTo(b) < 0;
    }
}

2.统计给定数组中的逆序数对数。

在归并排序的过程中处理:对于两个子数列a[left] - a[mid]和a[mid+1] - a[right],如果排序过程中a[j]<a[i],说明从a[i]到a[mid]都可以与a[j]构成逆序数对,即逆序数对增加mid-i+1个。

public class Inversions {
    private int count;
    private int[] copy;		// 防止修改原数组

    public Inversions(int[] a) {
        copy = new int[a.length];
        for (int i = 0; i < a.length; i++) {
            copy[i] = a[i];
        }
    }

    public int count() {
        sort(copy, 0, copy.length - 1);
        return count;
    }

    private void sort(int[] a, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = (left + right) / 2;
        sort(a, left, mid);
        sort(a, mid + 1, right);
        merge(a, left, mid, right);
    }

    private void merge(int[] a, int left, int mid, int right) {
        int[] aux = new int[a.length];
        for (int i = left; i <= right; i++) {
            aux[i] = a[i];
        }
        int i = left;
        int j = mid + 1;
        for (int k = left; k <= right; k++) {
            if (i > mid) {
                a[k] = aux[j++];
            } else if (j > right) {
                a[k] = aux[i++];
            } else if (aux[j] < aux[i]) {
                a[k] = aux[j++];
                count += mid - i + 1;	// 逆序数对增加,在原式子中只加了这一句
            } else {
                a[k] = aux[i++];
            }
        }
    }
}

3.将给定的链表顺序随机打乱。(链表)

只要在归并排序时从左右两数组中随机选择一个元素放入辅助数组中即可。

import java.util.Random;//结构仿照原式子

public class ShuffleList {
    public Node randomSort(Node first) {
        if (first == null || first.next == null) {
            return first;
        }
        Node mid = findMid(first);
        Node left = first;  // first-----mid   right-------
        Node right = mid.next;
        mid.next = null;		// 断开左右链表
        left = randomSort(left);
        right = randomSort(right);
        return randomMerge(left, right);

    }

    private Node randomMerge(Node left, Node right) {
        Random r = new Random();
        
        Node head = new Node();
        Node current = head;
        
        while (left != null || right != null) {
            if (left == null) {
                current.next = right;//半边连接完了,直接连接另外半边
                break;
            } else if (right == null) {
                current.next = left;
                break;
            } else {
                // 随机归并左右其中一个元素
                //随机选取左右,即无序,随机打乱链表输出
                int x = r.nextInt(2);
                if (x == 0) {
                    current.next = left;
                    current = current.next;
                    left = left.next;
                } else {
                    current.next = right;
                    current = current.next;
                    right = right.next;
                }
            }
        }
        return head.next;//head为null
    }

    private Node findMid(Node first) {
        Node mid = first;
        Node last = first;
        // 使用不同的递进速率来定位中间元素
        while (mid.next != null && last.next != null && last.next.next != null) {
            mid = mid.next;
            last = last.next.next;
        }
        return mid;
    }
}

class Node {
    int item;
    Node next;//链表
}

Assignment:CollinearPoints共线模式识别

import java.util.Comparator;
import edu.princeton.cs.algs4.StdDraw;
public class Point implements Comparable<Point> {
    // x-coordinate of this point
    private final int x;
    // y-coordinate of this point
    private final int y;
    // constructs the point (x, y)
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    } 
    // draws this point
    public   void draw() {
        StdDraw.point(x,y);
    }
    // draws the line segment from this point to that point
    public   void drawTo(Point that) {
        StdDraw.line(this.x, this.y, that.x, that.y);
    }
    // string representation
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
    // compare two points by y-coordinates, breaking ties by x-coordinates  
    public  int compareTo(Point that) {
        if(y<that.y || (y==that.y && x<that.x)) return -1;
        else if(y==that.y && x==that.x) return 0;
        else return +1;
    }   
    // the slope between this point and that point
    public  double slopeTo(Point that) {//求斜率
        if(x==that.x && y==that.y) return Double.NEGATIVE_INFINITY;
        if(x==that.x && y!=that.y) return Double.POSITIVE_INFINITY;     
        if(y==that.y) return +0.0;
        return (double)(y-that.y)/(x-that.x);
    }
    // compare two points by slopes they make with this point
    public Comparator<Point> slopeOrder(){
        return new SlopeOrder();
    }
    //nested class
    //sort rule
    private class SlopeOrder implements Comparator<Point>{
        public int compare(Point p,Point q) {//比较斜率大小
            //p点斜率大
            if(slopeTo(p)<slopeTo(q)) return -1;
            //p点斜率小
            else if(slopeTo(p)>slopeTo(q)) return 1;
            //p,q斜率相等
            else return 0;
        }
    }

    public static void main(String[] args) {
        Point p1 = new Point(0,0);
        Point p2 = new Point(1,1);
        Point p3 = new Point(2,2);
        Point p4 = new Point(2,1);
        Point p5 = new Point(4,1);
        System.out.println("p1.compareTo(p1) is "+p1.compareTo(p2));
        System.out.println("p2.compareTo(p1) is "+p2.compareTo(p1));
        System.out.println("p1.compareTo(p1) is "+p1.compareTo(p1)+"\n");

        System.out.println("p1.slopeTo(p2) is " +p1.slopeTo(p2));
        System.out.println("p1.slopeTo(p4) is "+p1.slopeTo(p4));
        System.out.println("p1.slopeTo(p1) is "+p1.slopeTo(p1));
        System.out.println("p3.slopeTo(p4) is "+p3.slopeTo(p4));
        System.out.println("p2.slopeTo(p5) is "+p2.slopeTo(p5));
    }
}

BruteCollinearPoints.java

import java.util.ArrayList;
import java.util.Arrays;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Insertion;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdDraw;

public class BruteCollinearPoints {
     //to store line segments
     private ArrayList<LineSegment> LineSegmentList;
     //to store the given points
     private Point[] points;

     //在构造函数中找出由4点构成的线段(作业说了:没有5点及更多点共线的情况)
     // finds all line segments containing 4 point
     public BruteCollinearPoints(Point[] pointsIn) { 
         //三种异常
         //一:if the argument to the constructor is null
         System.out.println(" a ");
         if(pointsIn == null) throw new IllegalArgumentException("there is no point");
         //二:if any point in the array is null
         int N = pointsIn.length;
         for(int i=0;i<N;i++) if(pointsIn[i]==null) throw new IllegalArgumentException("exist null point");
         //三:if the argument to the constructor contains a repeated point
         //检查是否有重复的点,先排序,再查重会方便,作业允许这样: For example, you may use Arrays.sort()
         points = new Point[N];
         for(int i=0;i<N;i++) points[i] = pointsIn[i];
         Arrays.sort(points);
         for(int i=1;i<N;i++) if(points[i-1].compareTo(points[i])==0) throw new IllegalArgumentException("exist repeated point"); 


         //to save every required line segment
         LineSegmentList = new ArrayList<LineSegment>();

         //find line segment找到四个连线
         for(int dot1=0;dot1<=N-4;dot1++) {
             for(int dot2=dot1+1;dot2<=N-3;dot2++) {
                 //k12:the slope between point[dot2] and point[dot1]
                 double k12 = points[dot2].slopeTo(points[dot1]);//点2与点1的斜率
                 for(int dot3=dot2+1;dot3<=N-2;dot3++) {
                     //k13:the slope between point[dot3] and point[dot1]
                     double k13 = points[dot3].slopeTo(points[dot1]);
                     if(k13 != k12) continue;//点1与点3的斜率
                     for(int dot4=dot3+1;dot4<=N-1;dot4++) {
                        //k14:the slope between point[dot4] and point[dot1]
                         double k14 = points[dot4].slopeTo(points[dot1]);
                         if(k14 != k12) continue;
                         //find a line segment 表示1,2,3,4四点在同一条直线上。点1为start,点4为end
                         LineSegment linesegment = new LineSegment(points[dot1],points[dot4]);
                         LineSegmentList.add(linesegment);
                     }
                 }
             }
         }
     }
     // the number of line segments
     public int numberOfSegments() {
         return LineSegmentList.size();//统计一共有多少条线
     }
     // the line segments
     public LineSegment[] segments() {
         LineSegment[] segments = new LineSegment[LineSegmentList.size()];
         int index=0;
         for(LineSegment Line : LineSegmentList) {
             segments[index++] = Line;
         }
         return segments;
     }    
     //main
     public static void main(String[] args) {
            In in = new In("src/week3/input8.txt"); 
            int n = in.readInt();
            StdOut.println("total "+n+" points");
            Point[] points = new Point[n];
            for (int i = 0; i < n; i++) {
                int x = in.readInt();
                int y = in.readInt();
                StdOut.println("("+x+","+y+")"); 
                points[i] = new Point(x,y);
            }       
            //draw the points
            StdDraw.enableDoubleBuffering();
            StdDraw.setXscale(0, 32768);
            StdDraw.setYscale(0, 32768);
            StdDraw.setPenColor(StdDraw.RED);
            StdDraw.setPenRadius(0.01);
            for (Point p : points) {
                p.draw();
            }
            StdDraw.show();              
            // print and draw the line segments
            BruteCollinearPoints collinear = new BruteCollinearPoints(points); //core
            StdOut.println(collinear.numberOfSegments());//统计有多少条线段
            for (LineSegment segment : collinear.segments()) {
                StdOut.println(segment); //将线段打印出来
                segment.draw();
            }
            StdDraw.show();          
     }
}

FastCollinearPoints.java与BruteCollinearPoints.java作用相同但是算法不同

import java.util.ArrayList;
import java.util.Arrays;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdOut;

//much faster than the brute-force solution
public class FastCollinearPoints {
    //to store line segments
    private ArrayList<LineSegment> LineSegmentList;
    //to store the given points
    private Point[] points;

    // finds all line segments containing 4 or more points
    public FastCollinearPoints(Point[] pointsIn) {
         //三种异常
         //一:if the argument to the constructor is null
         if(pointsIn == null) throw new IllegalArgumentException("there is no point");
        //number of points
         int N=pointsIn.length;
         //二:if any point in the array is null
         for(int i=0;i<N;i++) if(pointsIn[i]==null) throw new IllegalArgumentException("exist null point");
         //三:if the argument to the constructor contains a repeated point
         //检查是否有重复的点,先排序,再查重会方便,作业允许这样: For example, you may use        Arrays.sort()
         //同时有利于后面排除重复线段
         points = new Point[N];
         for(int i=0;i<N;i++) points[i] = pointsIn[i];
         //用的是结合了归并和插入的tim排序,稳定排序sort
         Arrays.sort(points);
         for(int i=1;i<N;i++) if(points[i-1].compareTo(points[i])==0) throw new IllegalArgumentException("exist repeated point");

         //to save every required line segment
         LineSegmentList = new ArrayList<LineSegment>();


         //当前的参考点
         Point currentCheck;
         //对照点重新存储,不包括参考点,共N-1个
         Point[] otherPoints = new Point[N-1];
         //开始比较斜率,一个一个来
         for (int i=0;i<N;i++) {
             currentCheck = points[i];
             // copy points without Point currentCheck to otherPoints
             for(int j=0;j<N;j++) {
                 if(j<i) otherPoints[j] = points[j];
                 if(j>i) otherPoints[j-1] = points[j];
             }
/
             //根据斜率对点排序
             //用的是结合了归并和插入的tim排序,稳定排序
             Arrays.sort(otherPoints,currentCheck.slopeOrder());
             //遍历已经排序的otherPoints找线段
             //注意,归并和插入排序都是稳定的,所以tim排序是稳定的,这非常重要
             //配合Point的compareTo方法,可以直接过滤掉重复线段
             //一开始没太注意compareTo方法,后来发现这个方法能固定住点之间的相对位置,所以可以过滤重复线段
             //两点共线
             int count=2;
             for(int k=1;k<N-1;k++) {
                 double k1 = otherPoints[k-1].slopeTo(currentCheck);
                 double k2 = otherPoints[k].slopeTo(currentCheck);
                 if(k1==k2) {
                     count++;
                     //当循环到最后一个点,同时这个点和前面的点共线
                     if(k==N-2) {
                         //如果4点及以上共线,并且otherPoints中与参考点共线且排在最左边的点比参考点大的话,注意此处是遍历到头,所以索引是k-count+2
                         if(count>=4 && currentCheck.compareTo(otherPoints[k-count+2])==-1) { 
                             //线段起点
                             Point start = currentCheck;
                             //线段终点
                             Point end = otherPoints[k];
                             LineSegment linesegment = new LineSegment(start,end);
                             LineSegmentList.add(linesegment);
                         }
                     }
                 }
                 else{
                    //如果4点及以上共线,并且otherPoints中与参考点共线且排在最左边的点比参考点大的话,索引是k-count+1
                     if(count>=4 && currentCheck.compareTo(otherPoints[k-count+1])==-1) {
                             Point start = currentCheck;
                             Point end = otherPoints[k-1];
                             LineSegment linesegment = new LineSegment(start,end);
                             LineSegmentList.add(linesegment);
                     }
                     count=2;
                 }
             }
         }
    }





    // the number of line segments
    public  int numberOfSegments() {
        return LineSegmentList.size();
    }
    // the line segments
    public LineSegment[] segments() {
        LineSegment[] segments = new LineSegment[LineSegmentList.size()];
         int index=0;
         for(LineSegment Line : LineSegmentList) {
             segments[index++] = Line;
         }
         return segments;
    }

    //main
         public static void main(String[] args) {
                In in = new In("src/week3/input9.txt"); 
                int n = in.readInt();
                StdOut.println("total "+n+" points");
                Point[] points = new Point[n];
                for (int i = 0; i < n; i++) {
                    int x = in.readInt();
                    int y = in.readInt();
                    StdOut.println("("+x+","+y+")"); 
                    points[i] = new Point(x,y);
                }

                //draw the points
                StdDraw.enableDoubleBuffering();
                StdDraw.setXscale(0, 32768);
                StdDraw.setYscale(0, 32768);
                StdDraw.setPenColor(StdDraw.RED);
                StdDraw.setPenRadius(0.01);
                for (Point p : points) {
                    p.draw();
                }
                StdDraw.show();

                //print and draw the line segments
                FastCollinearPoints collinear = new FastCollinearPoints(points);//core
                StdOut.println(collinear.numberOfSegments());
                for (LineSegment segment : collinear.segments()) {
                    StdOut.println(segment);
                    segment.draw();
                }
                StdDraw.show();          
         }
}

reference:

https://blog.csdn.net/littlehaes/article/details/79246041?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://www.cnblogs.com/Jimtastic/p/4003877.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值