1. 程式人生 > 其它 >插入、歸併、快速演算法的比較以及拓撲排序的迴圈檢測

插入、歸併、快速演算法的比較以及拓撲排序的迴圈檢測

執行完上述的三個實現,會發現插入排序用時是最多的,這是因為插入排序在最壞情況下為**N^2/2**, 一般情況下為**N^2/4**, 插入排序在部分有序,部分倒置,小陣列的情況下處理處理速度是最快的。 快速排序在此問題要比插入排序快了**20倍有餘**,快速排序的處理速度是**NlogN**,排序演算法的下限便是**NlogN**,歸併排序也達到了此點,但是快速排序好的地方便是快速排序所用的空間少,交換陣列元素的次數也比歸併排序掃了很多。 第三個演算法便是結合了快速排序優點和插入排序對小陣列處理快的優點,比原始的快速排序要優化了**20%~30%**。

問題描述

輸入整數陣列 arr ,找出其中最小的 k 個數。例如,輸入4、5、1、6、2、7、3、8這8個數字,則最小的4個數字是1、2、3、4。

示例 1:

輸入:arr = [3,2,1], k = 2
輸出:[1,2] 或者 [2,1]
示例 2:

輸入:arr = [0,1,2,1], k = 1
輸出:[0]

分別使用插入排序和快速排序以及快速排序結合插入排序解決此問題

使用插入排序

//本人對於小陣列的處理傾向於插入排序
class Solution {

    private int[] res;

    public int[] getLeastNumbers(int[] arr, int k) {
        res = new int[k];
        for(int i = 1; i < arr.length ; i++){
            for(int j = i ; j > 0 && less(arr[j],arr[j-1]);j--){
                exchange(arr,j,j-1);
            }
        }
        for(int i = 0; i < k ; i++){
            res[i] = arr[i];
        }
        return res;
    }

    private boolean less(int v,int w){
        return v < w;
    }
    private void exchange(int[] a,int v,int w){
        int t = a[v];
        a[v] = a[w];
        a[w] = t;
    }
}

使用快速排序

//本人對於小陣列的處理傾向於插入排序
//對於不重複主鍵的大陣列,本人推薦使用快速排序加插入排序實現
//對於重複主鍵陣列,本人推薦使用三切分的快速排序
class Solution {

    private int[] res;

    public int[] getLeastNumbers(int[] arr, int k) {
        quickSort(arr,0,arr.length-1);
        res = new int[k];
        for(int i = 0; i < k ; i++){
            res[i] = arr[i];
        }
        return res;
    }

    private void quickSort(int[] a,int lo,int hi){
        if(hi <= lo) return;
        int j = slice(a,lo,hi);
        quickSort(a,lo,j-1);
        quickSort(a,j+1,hi);
    }

    private int slice(int[] a,int lo,int hi){
        int i = lo,j = hi + 1;
        int v = a[lo];
        while(true){
            while(less(a[++i],v)) if(i == hi) break;
            while(less(v,a[--j])) if(j == lo) break;
            if(i >= j) break;
            exchange(a,i,j);
        }
        exchange(a,lo,j);
        return j;
    }

    private boolean less(int v,int w){
        return v < w;
    }
    private void exchange(int[] a,int v,int w){
        int t = a[v];
        a[v] = a[w];
        a[w] = t;
    }
}

快速排序結合插入排序對小陣列以及部分有序陣列處理快的優點

與快速排序不同的地方在於,多了一個insertSort方法:

    private void insertSort(int[] a,int lo,int hi){
        for(int i = lo + 1; i <= hi ; i++){
            for(int j = i ; j > lo && less(a[j],a[j-1]) ; j--){
                exchange(a,j,j-1);
            }
        }
    }

以及quickSort內部變成了:

    private void quickSort(int[] a,int lo,int hi){
        if(hi <= lo + 5){
            insertSort(a,lo,hi);
            return;
        }
        int j = slice(a,lo,hi);
        quickSort(a,lo,j-1);
        quickSort(a,j+1,hi);
    }

執行完上述的三個實現,會發現插入排序用時是最多的,這是因為插入排序在最壞情況下為N^2/2, 一般情況下為N^2/4, 插入排序在部分有序,部分倒置,小陣列的情況下處理處理速度是最快的。

快速排序在此問題要比插入排序快了20倍有餘,快速排序的處理速度是NlogN,排序演算法的下限便是NlogN,歸併排序也達到了此點,但是快速排序好的地方便是快速排序所用的空間少,交換陣列元素的次數也比歸併排序掃了很多。

第三個演算法便是結合了快速排序優點和插入排序對小陣列處理快的優點,比原始的快速排序要優化了20%~30%

問題描述

你這個學期必須選修 numCourses 門課程,記為0到numCourses - 1 。

在選修某些課程之前需要一些先修課程。 先修課程按陣列prerequisites 給出,其中prerequisites[i] = [ai, bi] ,表示如果要學習課程ai 則 必須 先學習課程 bi 。

例如,先修課程對[0, 1] 表示:想要學習課程 0 ,你需要先完成課程 1 。
請你判斷是否可能完成所有課程的學習?如果可以,返回 true ;否則,返回 false 。

示例 1:

輸入:numCourses = 2, prerequisites = [[1,0]]
輸出:true
解釋:總共有 2 門課程。學習課程 1 之前,你需要完成課程 0 。這是可能的。
示例 2:

輸入:numCourses = 2, prerequisites = [[1,0],[0,1]]
輸出:false
解釋:總共有 2 門課程。學習課程 1 之前,你需要先完成​課程 0 ;並且學習課程 0 之前,你還應先完成課程 1 。這是不可能的。

使用有向圖的拓撲排序解決此問題

import java.util.NoSuchElementException;
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        DiGraph digraph = new DiGraph(prerequisites,numCourses);
        DirectedCycle topology = new DirectedCycle(digraph);
        return !topology.hasCycle();
    }
}

/**
 * <h2>揹包</h2>
 * <p>部落格地址:https://home.cnblogs.com/u/qzlzzz/</p>
 * @since 2021/11/13
 * @author qzlzzz
 * @version 2.0
 * @param <Item> {@code type parameter}
 */
class Bag<Item> implements Iterable<Item> {

    //the first value in this bag
    private Node<Item> first;

    // all value in this bag
    private int n;

    private class Node<Item>{

        //value
        private Item item;

        //next index
        private Node<Item> next;

    }

    public Bag(){
        this.first = null;
        this.n = 0;
    }

    /**
     * <h3>判斷揹包是否為空</h3>
     * @return {@code true} else {@code false}
     */
    public boolean isEmpty(){
        return first == null;
    }

    /**
     * <h3>向背包加入元素</h3>
     * @param item {@code the value}
     */
    public void add(Item item){
        Node<Item> oldFirst = first;
        first = new Node<Item>();
        first.item = item;
        first.next = oldFirst;
    }

    /**
     * <h3>迭代器</h3>
     * @return
     */
    @Override
    public Iterator<Item> iterator() {
        return new LinkedIterator(first);
    }

    private class LinkedIterator implements Iterator<Item> {


        private Node<Item> current;

        public LinkedIterator(Node first){
             this.current = first;
        }


        @Override
        public boolean hasNext() {
            return current != null;
        }

        @Override
        public Item next() {
            if(!hasNext()) throw new NoSuchElementException();
            Item item = current.item;
            current = current.next;
            return item;
        }
    }
}

class DiGraph{

    private int V;

    private int E;

    private Bag<Integer>[] bags;

    public DiGraph(int V){
        this.V = V;
        this.E = 0;
        bags = new Bag[V];
        for(int i = 0 ; i < V ; i++){
            bags[i] = new Bag<Integer>();
        }
    }

    public DiGraph(int[][] in,int V){
        this(V);
        int E = in.length;
        for(int i = 0; i < E ; i++){
            int v = in[i][0];
            int w = in[i][1];
            addEdg(v,w);
        }
    }

    private void addEdg(int v,int w){
        bags[v].add(w);
        this.E++;
    }

    public Iterable<Integer> adj(int v){
        return bags[v];
    }

    public int V(){
        return this.V;
    }
    public int E(){
        return this.E;
    }
}

class DirectedCycle{

    private int[] edgeTo;//記錄路徑

    private boolean[] marked;//標記已訪問過的點

    private boolean[] onStack;

    private Stack<Integer> cycle;//保留迴圈

    public DirectedCycle(DiGraph digraph){
        marked = new boolean[digraph.V()];
        onStack = new boolean[digraph.V()];
        edgeTo = new int[digraph.V()];
        for(int v = 0 ; v < digraph.V(); v++){
            if(!marked[v]){
                dfs(digraph,v);
            }
        }
    }

    private void dfs(DiGraph digraph,int v){
        marked[v] = true;
        onStack[v] = true;
        for(int w : digraph.adj(v)){
            if(hasCycle()) return;
            if(!marked[w]){
                edgeTo[w] = v;
                dfs(digraph,w);
            }else if(onStack[w]){
                cycle = new Stack<Integer>();
                for(int t = v; t != w ; t = edgeTo[t]){
                    cycle.push(t);
                }
                cycle.push(w);
                cycle.push(v);
            }
        }
        onStack[v] = false;        
    }

    public boolean hasCycle(){
        return cycle != null;
    }
}

參考

https://algs4.cs.princeton.edu/home/