JAVA 七大排序算法的算法思想、實現以及速度對比


  七大排序經典的排序算法:冒泡排序、快速排序、直接選擇排序、堆排序、直接插入排序、希爾排序、歸並排序。

  這七大排序算法也許在排序的數據量比較小的時候差別並不是很大,但是當數據量很大的時候相差可以達幾十倍,幾千倍甚至更高,試想在一個大型程序中也許一個性能比較強的算法需要執行一個小時,而一個性能弱的算法可能需要幾十個幾百個甚至幾千個小時。這是多么恐怖的差距,所以算法在程序設計當中是十分重要的一點。

  這里為什么會有這么大的差距主要是涉及到他們比較的次數的問題以及交換數據的問題,交換數據是主要因素,因為涉及到內存操作十分耗時。所以以盡量少的交換數據次數實現排序,就可以得到性能優越的排序算法。

  文章借鑒:http://www.cnblogs.com/nnngu/p/8283977.html ,這位大哥寫得挺全的。

  冒泡排序:

  冒泡排序主要通過相鄰元素的大小比較,把大的或則小的往前或則往后放,這個根據個人而定,但是算法思想是這樣,這個算法的實現很簡單主要涉及到值得比較以及元素交換。下面請看編碼實現:

package com.test;

public class maopao_sort {

    public static void maopaosort(int[] list) {
        int pair;//臨時變量
        //一次選出一個所以需要length-1次選擇
        for(int i=0;i<list.length-1;i++) {
            //選出一個過后放在數組末尾,之后不用再比較選出的所以是length-1-i,這里其實也可以改成放在前面,至於怎么改很簡單了。
            for(int k=0;k<list.length-1-i;k++) {
                if(list[k]>list[k+1]) {//大小比較決定排序順序
                    pair=list[k];
                    list[k]=list[k+1];
                    list[k+1]=pair;
                }
            }
        }
    }
    
    public static void main(String[] args) {
        int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1};
        maopaosort(b);
        for(int m:b) {
            System.out.print(m+"  ");
        }
        System.out.println("\n");
        
        int[] a=new int[30000];
        for(int i=0;i<a.length;i++) {
            a[i]=(int) (Math.random()*300000);
        }
        double start=System.currentTimeMillis();
        maopaosort(a);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
    }

}

  輸出結果:

  處理30000條數據需要1438ms,這個算法實現簡答但是速度是相當慢的。

  快速排序: 

  快速排序(Quick Sort) 是對冒泡排序的一種改進方法,在冒泡排序中,進行元素的比較和交換是在相鄰元素之間進行的,元素每次交換只能移動一個位置,所以比較次數和移動次數較多,效率相對較低。而在快速排序中,元素的比較和交換是從兩端向中間進行的,較大的元素一輪就能夠交換到后面的位置,而較小的元素一輪就能交換到前面的位置,元素每次移動的距離較遠,所以比較次數和移動次數較少,速度較快,故稱為“快速排序”。

  快速排序的基本思想是:通過一輪排序將待排序元素分割成獨立的兩部分, 其中一部分的所有元素均比另一部分的所有元素小,然后分別對這兩部分的元素繼續進行快速排序,以此達到整個序列變成有序序列。快速排序的最壞時間復雜度為O(n2),平均時間復雜度為O(n*log2n)。

  代碼實現:

package com.test;

public class kuaisu_sort {

    public static void quiksort(int[] list,int left,int right) {
        //left<right是用來判斷兩邊的索引是否還在走,如果相等了說明不能繼續排序就結束了。同時也是左右位置的一個標記。
        if(left<right) {
            //獲取索引位置
            int point=divider(list, left, right);
            //遞歸排序左右索引兩邊的數組
            quiksort(list, left, point-1);
            quiksort(list, point+1, right);
        }
    }
    //排序並返回本輪的索引位置
    public static int divider(int[] list,int left,int right) {
        int pair=list[left];
        //持續循環知道兩邊的索引相遇,也就是查完了數組的所有元素,就結束,返回當前的索引位置。
        while(left<right) {
            //先從右邊開始查找,找到比基准元素pair小的就停止,當然這里從左還是從右開始都是可行的。
            while(left<right&&list[right]>=pair) {
                right--;
            }
            swap(list, left, right);
            //查找左邊,找到比基准元素大的就停止,然后交換數據。
            while(left<right&&list[left]<=pair) {
                left++;
            }
            swap(list, left, right);
        }
        return left;
    }
    
    public static void swap(int[] list,int a,int b) {
        if(list!=null&&list.length>0) {
            int mm=list[a];
            list[a]=list[b];
            list[b]=mm;
        }
    }
    
    public static void main(String[] args) {
        int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1};
        quiksort(b, 0, b.length-1);
        for(int m:b) {
            System.out.print(m+"  ");
        }
        System.out.println("\n");
        
        int[] a=new int[30000];
        for(int i=0;i<a.length;i++) {
            a[i]=(int) (Math.random()*300000);
        }
        double start=System.currentTimeMillis();
        quiksort(a, 0, a.length-1);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
    }

}

  運行結果:

  這里處理30000條數據只用了6ms,比冒泡排序快了差不多240倍,要是數據量更大這差距可想而知了。下面我們繼續看其他排序。

  直接選擇排序:

  直接選擇排序(Straight Select Sort) 是一種簡單的排序方法,它的基本思想是:通過length-1 趟元素之間的比較,從length-i+1個元素中選出最小的元素,並和第i個元素交換位置。直接選擇排序的最壞時間復雜度為O(n2),平均時間復雜度為O(n2)。

  這里的i從0開始也就是說第一輪找到一個最小元素放在第一個位置,然后第二輪從剩下的元素中繼續找最小的元素放在第二個位置。然后到length-1輪就放在length-1的位置,然后整個排序結束。

  代碼實現:

package com.test;

public class zhijiexuanzepaixu {

    public static void sort(int[] list) {
        int min;//最小元素下標
        int mm;//臨時變量
        //從0到length-1輪排序,
        for(int i=0;i<list.length-1;i++) {
            min=i;
            //遍歷剩下的所有元素找到最小的或則最大的元素。
            for(int k=i;k<list.length;k++){
                //這里是找小的元素,如果當前元素比之前找到的最小元素還小就進行交換
                if(list[k]<list[min]) {
                    min=k;
                }
            }
            //這里如果最小元素已經發生了改變則進行元素交換,如果沒改變就交換的話會出問題。所以這里是很關鍵的。
            if(min!=i) {
                mm=list[i];
                list[i]=list[min];
                list[min]=mm;
            }
        }
    }
    
    public static void main(String[] args) {
        int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1};
        sort(b);
        for(int m:b) {
            System.out.print(m+"  ");
        }
        System.out.println("\n");
        
        int[] a=new int[30000];
        for(int i=0;i<a.length;i++) {
            a[i]=(int) (Math.random()*300000);
        }
        double start=System.currentTimeMillis();
        sort(a);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
    }

}

  運行結果:

  這里處理30000條數據用時250ms。相對來說還是算比較好的算法了。接下來介紹一種性能十分優秀的算法,但是實現起來相對更加復雜,而且也不是那么通俗易懂。

  堆排序: 

  堆排序(Heap Sort) 利用堆(一般為大根堆)進行排序的方法。它的基本思想是:將待排序的元素構造成一個大根堆。此時,整個序列的最大值就是堆頂的根節點。將它移走(其實就是將它與數組的末尾元素進行交換,此時末尾元素就是最大值),然后將剩余的length-1 個元素重新構造成一個大根堆,這樣就會得到length個元素中的次大值。如此反復執行,便能得到一個有序的序列。

  堆是具有下列性質的完全二叉樹:每個節點的值都大於或等於其左右孩子節點的值,稱為大根堆;每個節點的值都小於或等於其左右孩子節點的值,稱為小根堆。堆排序的最壞時間復雜度為O(n*log2n),平均時間復雜度為O(n*log2n)。

  這個算法的主要問題就是堆的構建,設計到一些位置的處理,這些問題請看具體的代碼中的注釋。下面是堆的整個調整過程。

圖一:

圖二:

圖三:

圖四:

圖五:

 

圖六:

   代碼實現:

 

package com.test;

public class heap_Sort {

    public static void heapsort(int[] list) {
        //這里第一個父節點位置length/2-1,然后前面的所有節點都是父節點。這樣順序進行堆處理,生成最大或則最小堆
        for(int i=list.length/2-1;i>=0;i--) {
            heapAjust(list,i,list.length);
        }
        //交換末尾元素與第一個元素,交換過后對第一個元素處理一次,這樣又變成了一個堆,然后繼續循環知道結束
        for(int i=list.length-1;i>0;i--) {
            swap(list, i, 0);
            //這里的長度為i是因為已經排序好的元素不在處理它,如果處理了排序就會亂掉
            heapAjust(list, 0, i);
        }
    }
    /**
     * 
     * @param parent 父元素位置
     * @param length 數組長度
     */
    private static void heapAjust(int[] list, int parent, int length) {
        //獲取當前父元素的值
        int temp=list[parent];
        //對於完全二叉樹父元素的左孩子位置是當前位置*2+1
        int leftchild=parent*2+1;
        //這里leftchild<length的作用是判斷當前的父元素的左孩子是否存在,超出了length也就是不存在了循環終止
        while(leftchild<length) {
            //判斷有無右孩子,如果有並且左孩子的值小與右孩子就讓左孩子索引指向右孩子。這里的左孩子索引其實就是當前值較大值得一個索引
            //也就是一個標記,不必在意其名稱。理解了這一點就問題就簡單了。這里的大小比較用於排序順序處理。
            if((leftchild+1)<length&&list[leftchild]<list[leftchild+1]) {
                leftchild++;
            }
            //在子節點中找到了更大的值,下面當然就是和父元素做比較了,如果父元素大直接跳出循環因為不需要進行數據交換
            if(temp>=list[leftchild]) {
                break;
            }
            //否則進行數據交換,並繼續往子樹判斷是否滿足堆的條件。
            list[parent]=list[leftchild];
            parent=leftchild;
            leftchild=parent*2+1;
        }
        //這里的parent實際上已經變成了子節點。所以需要將原父節點的值賦值給它。
        list[parent]=temp;
    }
    
    public static void swap(int[] list,int a,int b) {
        int temp=list[a];
        list[a]=list[b];
        list[b]=temp;
    }

    public static void main(String[] args) {
        int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1};
        heapsort(b);
        for(int m:b) {
            System.out.print(m+"  ");
        }
        System.out.println("\n");
        
        int[] a=new int[30000];
        for(int i=0;i<a.length;i++) {
            a[i]=(int) (Math.random()*300000);
        }
        double start=System.currentTimeMillis();
        heapsort(a);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
    }

}

 

  這里其實還有另一種實現方式,就是在比較出父元素和子元素大小的時候如果滿足條件就調用交換函數,但是這樣反倒會多執行交換,得不償失。這里只是提供想法,但是效果並不可觀。

 

  這里堆排序用時6ms,和快速排序差不多,都是速度比較快的排序方式。

  直接插入排序:

  直接插入排序的算法思想是從數組當中按順序取出元素然后插入到已經排好序的序列當中。也就說假如有10個元素,第一個元素先不動,直接從第二個元素開始,然后判斷第二個元素和之前元素的大小關系,插入對應的位置,然后執行后一個元素,后一個元素又繼續判斷他在前面已經排序好的序列中應該在哪個位置,然后插入。這樣length-1次循環就可以完成整個排序。

  代碼實現:

package com.test;

public class zhijiecharupaixu {

    public static void sort(int[] list) {
        //這里從第一個元素開始
        for(int i=1;i<list.length;i++) {
            //保存當前位置的元素
            int temp=list[i];
            int j;
            //從當前位置往前查找,找到對應的位置插入
            for(j=i-1;j>=0&&list[j]>temp;j--) {
                list[j+1]=list[j];
            }
            list[j+1]=temp;
        }
    }
    
    public static void main(String[] args) {
        int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1};
        sort(b);
        for(int m:b) {
            System.out.print(m+"  ");
        }
        System.out.println("\n");
        
        int[] a=new int[30000];
        for(int i=0;i<a.length;i++) {
            a[i]=(int) (Math.random()*300000);
        }
        double start=System.currentTimeMillis();
        sort(a);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
    }
}

  運行結果:

  直接插入排序的處理30000條數據用時297ms算是比較好的排序算法了。

  希爾排序:

  希爾排序的算法思想先把排序序列分成length/2組,對每一組進行排序,然后把真個續聯繼續分為length/2/2組依次往后知道成為一個組。每分一次就對內部的所有序列進行排序。對內部進行排序可以采用直接插入排序。相當於在直接插入排序的基礎上進行了一定的優化。

  代碼如下:

package com.test;

public class xier_sort {
    public static void shellSort(int[] list) {
        //gap用於保存當前的分組數量。
        int gap = list.length / 2;
        //gap>=1也就是說不能繼續分了就停止
        while (gap >= 1) {
            //這里就相當於一個直接插入排序。只是排序的間隙變成了gap所以減少了比較次數。
            for (int i = gap; i < list.length; i++) {
                int temp = list[i];
                int j;
                for (j = i - gap; j >= 0 && list[j] > temp; j = j - gap) {
                    list[j + gap] = list[j];
                }
                list[j + gap] = temp;
            }
            gap = gap / 2;
        }
    }
    
    public static void main(String[] args) {
        int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1};
        shellSort(b);
        for(int m:b) {
            System.out.print(m+"  ");
        }
        System.out.println("\n");
        
        int[] a=new int[30000];
        for(int i=0;i<a.length;i++) {
            a[i]=(int) (Math.random()*300000);
        }
        double start=System.currentTimeMillis();
        shellSort(a);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
    }
    
}

  運行結果:

  處理30000條數據用時7ms所以希爾排序也是一個比較優秀的排序方法。

  歸並排序:

  歸並排序的原理是假設初始序列有n個元素,則可以看成是n個有序的子序列,每個子序列的長度為1,然后兩兩歸並,得到n/2個長度為2或1的有序子序列;再兩兩歸並,…… ,如此重復,直至得到一個長度為n的有序序列為止,這種排序方法就稱為歸並排序。

  實際的代碼運行是將整個數組序列不斷的分,分到最后只有兩個的時候開始排序,然后網上,繼續排序。歸並排序采用的是遞歸排序,也就是或如果有如果有10個元素,先分成兩組,左邊一組再遞歸調用自身,右邊同樣如此,這樣一直遞歸下去就分成了兩兩一組,然后底層結束了可能就是四個一組,再對這四個進行排序,就這樣繼續往上最后排序完成。分組方式如下:

  

  代碼實現:

package com.test;

public class mergeSort {

    public static void mergeSort(int[] list, int[] tempList, int head, int rear) {
        if (head < rear) {
            // 取分割位置
            int middle = (head + rear) / 2;
            // 遞歸划分列表的左序列
            mergeSort(list, tempList, head, middle);
            // 遞歸划分列表的右序列
            mergeSort(list, tempList, middle + 1, rear);
            // 列表的合並操作
            merge(list, tempList, head, middle + 1, rear);
        }
    }

    public static void merge(int[] list, int[] tempList, int head, int middle, int rear) {
        // 左指針尾
        int headEnd = middle - 1;
        // 右指針頭
        int rearStart = middle;
        // 臨時列表的下標
        int tempIndex = head;
        // 列表合並后的長度
        int tempLength = rear - head + 1;

        // 先循環兩個區間段都沒有結束的情況
        while ((headEnd >= head) && (rearStart <= rear)) {
            // 如果發現右序列大,則將此數放入臨時列表
            if (list[head] < list[rearStart]) {
                tempList[tempIndex++] = list[head++];
            } else {
                tempList[tempIndex++] = list[rearStart++];
            }
        }
        // 判斷左序列是否結束
        while (head <= headEnd) {
            tempList[tempIndex++] = list[head++];
        }
        // 判斷右序列是否結束
        while (rearStart <= rear) {
            tempList[tempIndex++] = list[rearStart++];
        }
        // 交換數據
        for (int i = 0; i < tempLength; i++) {
            list[rear] = tempList[rear];
            rear--;
        }
    }
    
    public static void main(String[] args) {
        int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1};
        mergeSort(b, new int[b.length], 0, b.length-1);
        for(int m:b) {
            System.out.print(m+"  ");
        }
        System.out.println("\n");
        
        int[] a=new int[30000];
        for(int i=0;i<a.length;i++) {
            a[i]=(int) (Math.random()*300000);
        }
        double start=System.currentTimeMillis();
        mergeSort(a, new int[a.length], 0, a.length-1);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
    }

}

  運行結果:

  歸並排序處理30000條數據用時7ms。

  現在寫一個統一的比較函數,雖然不可能保證完全的沒有影響因素,但把其他的影響因素降到了最低。函數如下:

package com.test;

public class compair {

    public static void main(String[] args) {
        maopao_sort maopao=new maopao_sort();
        xier_sort xier=new xier_sort();
        mergeSort merge=new mergeSort();
        kuaisu_sort kuaisu=new kuaisu_sort();
        heap_Sort heap=new heap_Sort();
        zhijiecharupaixu charu=new zhijiecharupaixu();
        zhijiexuanzepaixu xuanze=new zhijiexuanzepaixu();
        
        int[] a=new int[300000];
        int[] b=new int[300000];
        int[] c=new int[300000];
        int[] d=new int[300000];
        int[] e=new int[300000];
        int[] f=new int[300000];
        int[] g=new int[300000];
        for(int i=0;i<a.length;i++) {
            a[i]=b[i]=c[i]=d[i]=e[i]=f[i]=g[i]=(int) (Math.random()*30000000);
        }
        
        double start=System.currentTimeMillis();
        kuaisu.quiksort(a, 0, a.length-1);
        double end=System.currentTimeMillis();
        System.out.println(end-start);
        
        start=System.currentTimeMillis();
        maopao.maopaosort(b);
        end=System.currentTimeMillis();
        System.out.println(end-start);
        
        start=System.currentTimeMillis();
        merge.mergeSort(c, new int[c.length], 0, c.length-1);
        end=System.currentTimeMillis();
        System.out.println(end-start);
        
        start=System.currentTimeMillis();
        xier.shellSort(d);
        end=System.currentTimeMillis();
        System.out.println(end-start);
        
        start=System.currentTimeMillis();
        xuanze.sort(e);
        end=System.currentTimeMillis();
        System.out.println(end-start);
        
        start=System.currentTimeMillis();
        heap.heapsort(f);
        end=System.currentTimeMillis();
        System.out.println(end-start);
        
        start=System.currentTimeMillis();
        charu.sort(g);
        end=System.currentTimeMillis();
        System.out.println(end-start);
        
    }

}

  運行結果:

  從這一點可以看出如果數據量很大冒泡排序是不能采用的。其他的排序方法可以視情況而定。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM