排序算法

這是一篇關於排序算法的文章,闡述以下幾點:
1 各個排序算法的思想以及java版代碼
2 各種排序算法的效率,本人對效率分析從來不敏感,所以全是書上的觀點。

第一部分:各個排序算法的思想以及java版代碼
1、冒泡排序
    1)思想:從小到大
    一組數 23,4,5,67,5,6.
    左右比較,如果左>右,交換位置,比較的位置前移,直到遇到已經排序好的位置;=>把最大元素像冒泡一樣移動到最後一位
    依次,進行
    2)代碼
    數據存放在a數組中;
    public void bubbleSort(){
        boolean flag=false;
        for(int i=0;i<index-1;i++){
            flag=false;
            for(int j=0;j<index-i-1;j++){
                if(a[j]>a[j+1]){
                    flag=true;
                    int tmp=a[j];
                    a[j]=a[j+1];
                    a[j+1]=tmp;

                }
            }
            if(flag==false)    break;
        }
    }
2、選擇排序
    1)思想:少了搬移操作的冒泡排序
    第一次比較能找到最大的元素,把它放在最後一個位置;
    然後找第二大元素,把它放在倒數第二個位置;
    。。。
    這樣繼續下去,右邊有序的長度越來越長,左邊無序的長度越來越小,當無序的長度爲0的時候就排序好了。    
    2)代碼
        數據存放在a數組中;
        public void selectionSort(){
            for(int i=0;i<index-1;i++){
                int indexm;
                for(int j=i+1;j<index;j++){
                    if(a[indexm]>a[j]){
                        indexm=j;
                    }
                }
                if(indexm!=i){
                    int temp=a[i];
                    a[i]=a[indexm];
                    a[indexm]=temp;
                }
            }

        }
3、插入排序
    1)思想:
        如果把一個數插入一個有序的數組中是一個比較容易的操作。好,把數組的第一個元素看作是一個有序的數組,把第二個元素插入進來,這樣就獲得了一個長度爲2的有序的數組了。接着把前2個元素看作一個有序的數組,把第三個元素插入進來,數組的前2+1元素是有序的了。依次......
    這樣就獲得了一個有序的數組了。
    2)代碼
        public void insertionSort(){
            int i,j;
            for(i=1;i<index;i++){
                int x=a[i];
                for(j=0;j<=i;j++){
                    if(a[j]>a[i])
                        break;
                }
                if(j<i){
                    for(int k=i;k>j;k--)
                        a[k]=a[k-1];
                    a[j]=x;
                }
            }
        }
        明白,寫的這個代碼不是最簡潔的。
4、合併排序
    1)思想
    相信大家這樣的操作對大家來說不難吧:把2個有序的數組合併成一個有序的數組。這樣就可以寫出一個函數來完成此功能。姑且成爲merge函數吧。
    好,把一個無序的數組一分爲二,如果左、右兩邊的數組都是是有序的,那樣就可以通過merge得到有序的數組了。怎樣能得到呢?把左右的小數組看成是數組,在一份爲二排序,組合。哈哈,用到遞歸了。
    可以這樣看,每一個小的步驟是:
        (1)把一個數組一分爲二;
        (2)排序左邊的數組;
        (3)排序右邊的數組;
        (4)merge 兩邊的數組
        什麼時候退出遞歸呢?肯定是要排序的數組長度爲1,因爲長度爲1的數組就是一個有序的數組。
    2)代碼
    貼出一個完整的java代碼。代碼來源是一本數據結構的課本。這之中透露的封裝意味,細細體味吧。代碼中利用一次開闢中介數組,合併排序排的是一個數組中的不同部分,這是值得學習的
class DArray {
    private int[] array;

    private int index;

    public DArray(int size) {
        array = new int[size];
    }

    public DArray() {
        this(20);
    }

    public void insert(int v) {
        array[index++] = v;
    }

    public void mergeSort() {
        int[] space = new int[array.length];
        reMergeSort(space, 0, array.length - 1);
    }

    private void reMergeSort(int[] space, int low, int high) {
        if (low == high)
            return;
        else {
            int mid = (low + high) / 2;
            reMergeSort(space, low, mid);
            reMergeSort(space, mid + 1, high);
            merge(space, low, mid + 1, high);
        }
    }
    //把2個有序的數組合併成一個有序的數組
    private void merge(int[] space,int lowPtr,int highPtr,int upperBound){
        int j=0;
        int lowBound=lowPtr;
        int n=upperBound-lowPtr+1;
        int mid=highPtr-1;
        while((lowPtr <= mid) && (highPtr <= upperBound)){
            if(array[lowPtr]>array[highPtr]){
                space[j]=array[lowPtr];
                lowPtr++;
            }else{
                space[j]=array[highPtr];
                highPtr++;
            }
            j++;
        }
        while(lowPtr<=mid){
            space[j++]=array[lowPtr++];
            
        }
        while(highPtr<=upperBound){
            space[j++]=array[highPtr++];
        }
       
        for(int i=0;i<n;i++){
            array[lowBound+i]=space[i];
        }
    }

    public void display() {
        for (int i : array)
            System.out.print(i + "\t");
        System.out.println();
    }

}
一個更好理解的代碼:
    /**
     * 將2個有序的數組合併爲一個有序的數組
     */
    public static int[] merge(int[] a, int b[]) {
        int sizeA = a.length;
        int sizeB = b.length;
        int[] c = new int[sizeA + sizeB];
        int indexA = 0, indexB = 0, indexC = 0;
        while (indexA < sizeA && indexB < sizeB) {
            int tmp;
            if (a[indexA] > b[indexB]) {
                tmp = a[indexA];
                indexA++;
            } else {
                tmp = b[indexB];
                indexB++;
            }
            c[indexC++] = tmp;

        }
        while (indexA < sizeA) {
            c[indexC++] = a[indexA++];
        }
        while (indexB < sizeB) {
            c[indexC++] = b[indexB++];
        }
        return c;
    }

    /**
     * 合併排序
     */
    public static int[] mergeSort(int[] num) {
        if (num.length == 1) {
            return num;
        }
        int half = num.length / 2;
        int[] a = new int[half];
        int[] b = new int[num.length - half];
        int[] c = merge(mergeSort(a), mergeSort(b));
        return c;
    }


5、shell排序
    1)思想
    shell排序是插入排序的升級版。在插入排序中,排序間隔是1,這樣在從小到大的排序中,如果第一個元素是最大元素,那麼就需要移動n次。試想如果排序的間隔不是1。是2的話,只需要n/2次的搬移。插入排序在一個差不多有序的數組中效率還是很高的,因爲插入排序耗時主要在搬移上。
    這樣看一個數組:
            0     1    2    3    4    5    6    7    8    9
            23     45     7     8     9     14     56     8     55     6
    間隔爲4        23                9                55
                45                14                9
                    7                6
                        8                8
        當間隔爲4的時候去排序(0,4,8),(1,5,9),(2,6)(3,7)這四個數組
        然後再設間隔爲2,再設間隔爲1,此時也就是普通的插入排序了
        這種排序的效率很大程度上取決於間隔序列的產生。在這裏用的方法是:設x=1;x=3*x+1;讓x取小於n的最大數。           

    2)代碼
class ShellSort {

    private static int maxGap;// 最大的間距

    public static void shellSort(int[] array) {
        getGapSequence(array);

        int gap = maxGap;
        while (gap >= 1) {
            for (int index = 0; index < array.length; index++) {
                for (int i = index + gap; i < array.length; i += gap) {
                    int j;
                    for (j = index; j < i; j += gap) {
                        if (array[j] > array[i])
                            break;
                    }
                    if (j < i) {
                        int tmp = array[i];
                        for (int k = i; k > j; k -= gap)
                            array[k] = array[k - gap];
                        array[j] = tmp;
                    }
                }
            }

            gap = (gap - 1) / 3;
        }

    }

    /**
     * shell排序的基礎:插入排序
     */
    public static void insertSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            int j;
            for (j = 0; j < i; j++) {
                if (array[j] > array[i])
                    break;
            }
            if (j < i) {
                int tmp = array[i];
                for (int k = i; k > j; k--)
                    array[k] = array[k - 1];
                array[j] = tmp;
            }
        }
    }

    /**獲得最大間距,計算方式:h=1;h=3*h+1;
     * */
    private static void getGapSequence(int[] array) {
        int n = 1;
        while (n < array.length) {
            maxGap = n;
            n = n * 3 + 1;
        }
    }
}
6、快速排序
    1)思想
        理解快速排序之前先理解分割。
        假如一個數組a的元素是:45,5,6,98,37,12,4,56;然後把這個數組分爲2部分,左邊是小於等於50的,右邊是大於50的,這樣就把數組變爲 45,5,6,4,37,12,98,56;以12爲分界線。具體變換過程是:
        n=8
        0   1  2  3   4   5    6  7
        45,5,6,98,37,12,4,56
    leftPtr=0                        rightPtr=n-1
        關鍵字pivot=50;
        然後leftPtr++,直到a[leftPtr]>50,也就是遇到一個大於50的數,這個數應該放在右邊,右邊的什麼位置呢?rightPtr--,直到a[rightPtr]<50 ,在右邊找到一個小於50的數的位置,然後交換。繼續查找。如果leftPtr>=rigthPtr就完成了交換。
    leftPtr=3    rightPtr=6    交換得到
        45,5,6,4,37,12,98,56
    leftPtr=6退出,分割成功。
    分割的思想就是:把一個數組的元素按某個關鍵字分開,左邊是小於等於關鍵字,右邊是大於關鍵字的部分。注意一點的是左右兩邊並不一定是有序的,或者說是根本就無序,因爲我們沒有排序啊。
    接下來具體看二分排序的思想。
        分割一個數組成左(小於關鍵字的部分)右(大於關鍵字的部分)兩個子數組;
        調用自身排序左子數組;
        調用自身排序右子數組;
        這樣數組就有序了。
    爲什麼呢?先看看關鍵值的問題。怎麼找到這個關鍵值呢?這個關鍵值成爲pivot。爲簡單起見,我們把數組(子數組)最右端的元素作爲pivot;如果分割之後,pivot的值被插在了左右子數組之間的邊界位置,那麼這就是它在數組中的排序後的位置;這樣不斷分割,排序左右子數組就得到了有序的數組。
    2)代碼
    class QuickSort {
    private int size = 0;

    private int[] array;

    public QuickSort(int size) {
        array = new int[size];
    }

    public void show() {

        for (int i = 0; i < array.length; i++)
            System.out.print(array[i] + "\t");
        System.out.println();
    }

    public void insert(int v) {
        array[size++] = v;
    }
    /*
     * 將left,right之間的數以pivot分割,pivot=array[right],並且將pivot插入排序位置
     * 返回pivot所在位置
     * */

    public int partition(int left, int right, int pivot) {
        int leftPtr = left;
        int rightPtr = right;
        System.out.println("left="+left+"\tright="+right);
        //如果此處沒有等於號的限制,將可能在下面的賦值出現異常IndexOutOfBoundException
        while (leftPtr <= rightPtr) {
            while (leftPtr < rightPtr && array[leftPtr] < pivot)
                leftPtr++;
            while (leftPtr <rightPtr && array[rightPtr] >= pivot)
                rightPtr--;
            if (leftPtr >= rightPtr)
                break;
       
            else {
                int tmp = array[leftPtr];
                array[leftPtr] = array[rightPtr];
                array[rightPtr] = tmp;
            }
           
        }
        array[right]=array[rightPtr];
        array[rightPtr]=pivot;
        System.out.println("pivot="+pivot+"\t part="+rightPtr);
        show();
        return rightPtr;

    }
    //供外部調用
    public void quickSort() {
        sort(0, array.length - 1);
    }

    private void sort(int left, int right) {
        //當只有一個元素的時候就是有序的
        if (right - left <= 0) {
            return;
        }

        int partition = partition(left, right, array[right]);
        sort(left, partition - 1);
        sort(partition + 1, right);
    }
}

public class QuickSortApp {

    public static void main(String[] args) {
        QuickSort p = new QuickSort(10);
        for (int i = 0; i < 10; i++)
            p.insert((int) (Math.random() * 100));
        /*p.insert(7);
        p.insert(6);
        p.insert(5);
        p.insert(4);
        p.insert(2);
        p.insert(8);*/
        p.show();
       
        //p.sortStack();
        //p.show();
         p.quickSort();
         p.show();

    }

}

    還有關於快速排序的進一步的算法,其改進之處在於pivot的選擇。在一個無序的數組中,以最左端或者最右端的元素爲關鍵字,分割,情況還好,但在一個有序的數組中,甚至排序順序和想要的結果是相反順序時,就很糟糕了。如果能找到一箇中間值作關鍵值效果就更好了。但要找整個數組的中間值,好像又要費好多時間。而要找到一個數組中左、中、右三個值的中間值是更容易的。
    這樣獲得一種想法:
     獲得三個數的中間值作 pivot,並且使left,right有序
      分割返回p(privot在分割後數組中的位置)
      分割left,p-1
      分割p+1,right
      基本情況(退出遞歸的條件):當長度(right-left+1)<=3,人工排序
class QuickSort2 {
    private int size = 0;

    private int[] array;

    public QuickSort2(int size) {
        array = new int[size];
    }

    public void show() {

        for (int i = 0; i < array.length; i++)
            System.out.print(array[i] + "\t");
        System.out.println();
    }

    public void insert(int v) {
        array[size++] = v;
    }

    public void quickSort() {
        sort(0, size - 1);
    }

    /*對於長度小的子數組:可以手工排序,也可以使用插入排序
     * */
    private void sort(int left, int right) {
       
        int size = right - left + 1;
        /*if (size <= 3) {
            manualSort(left, right);

        } */
        if(size<10){
            insertionSort(left,right);
        }
        else {
            // 左邊, 右邊是已經分割好的,是相對於 pivot
            int mediant = medianof3(left, right);
            int partition = partition(left, right, mediant);
            sort(left, partition - 1);
            sort(partition + 1, right);

        }
    }

    private int medianof3(int left, int right) {
        int mid = (left + right) / 2;
        if (array[left] > array[mid])
            swap(left, mid);
        if (array[left] > array[right])
            swap(left, right);
        if (array[mid] > array[right])
            swap(mid, right);
        swap(right - 1, mid);
        return array[right - 1];
    }

    /* 基本情況,長度爲1 2 3 ??? */
    private void manualSort(int left, int right) {
        int size = right - left + 1;
        if (size <= 1)
            return;
        if (size == 2) {
            if (array[left] > array[right])
                swap(left, right);
            return;
        }
        if (size == 3) {
            int mid = (left + right) / 2;
            if (array[left] > array[mid])
                swap(left, mid);
            if (array[left] > array[right])
                swap(left, right);
            if (array[mid] > array[right])
                swap(mid, right);

        }

    }

    private void swap(int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

   
    /*左邊, 右邊是已經分割好的,是相對於 pivot
     * 將left,right之間的數以pivot分割,pivot=array[right-1],並且將pivot插入排序位置
     * 返回pivot所在位置
     * */
    public int partition(int left, int right, int pivot) {
        int leftPtr = left + 1;
        int rightPtr = right - 2;
        while (leftPtr < rightPtr) {
            while (leftPtr < rightPtr && array[leftPtr] <= pivot)
                leftPtr++;
            while (rightPtr > leftPtr && array[rightPtr] >= pivot)
                rightPtr--;
            if (leftPtr >= rightPtr)
                break;
            else {
                swap(leftPtr, rightPtr);
            }
        }
        swap(leftPtr, right - 1);
        System.out.println("pivto=" + pivot);
        System.out.println("leftPtr="+leftPtr+"\trightPtr="+rightPtr);
        return leftPtr;
    }
    /*當長度《10的時候使用插入排序*/
    private void insertionSort(int left,int right){
        for(int i=1;i<=right;i++){
            int tmp=array[i];
            int j=i-1;
            while(j>=0 && array[j]>tmp){
                array[j+1]=array[j];
                j--;
            }
            array[j+1]=tmp;
        }
    }
}

public class QuickSortApp2 {

    public static void main(String[] args) {
        QuickSort2 p = new QuickSort2(10);
        for (int i = 0; i < 10; i++) {
            System.out.print(i + "\t");
            p.insert((int) (Math.random() * 100));
        }
        System.out.println();
        p.show();
        p.quickSort();
        p.show();
    }
}
   
   
7、堆排序
    1)思想
            5
        78         89
    123    98    99        100
     什麼是堆?堆是一種樹。在最小值需求中堆頂的元素是整個堆中的最小值,最大值在葉子節點上。增刪改查之後還符合這樣的要求。
    第一步:將一個無序的數組中的元素挨個插入堆中,然後從堆中remove得到的數再挨個放入數組中,這個數組就是
          有序的了;
        細看動作:數組a 5,78,89,123,98,99,100。在堆中最頂端是5,reomve()方法刪除並獲得堆頂元素5,放入數組中b;繼續remove,存放,b數組中5,78;n次操作之後b數組中的元素就是: 5,78,89,98,99,100,123。

     進一步簡化:
      第二步:節省時間。2個正確的子堆trickDown()之後成爲一個合格的大堆;
              每個堆中最下面一排的節點都是正確的,因爲只有一個節點,所以只要從size/2開始作trickDown操作
              就能使堆正確
      第三步:節省空間:堆的每一個remove操作之後,最後一個空間就是閒置的了。如果把堆頂的元素依次插入
              堆尾,這樣有序的空間越來越大,無序的空間越來越小,直至排序完成,這樣就省下了一半的空間。
    2)代碼
 class HeapSort {
    private int[] array;//store numbers
    private int index=0;//index
    private int maxSize=0;
    public HeapSort(int size){
        maxSize=size;
        array=new int[size];
    }
    public HeapSort(){
        maxSize=20;
        array=new int[20];
    }
    /*assume after index is right,and post array[index] to approprite position
     * */
    public void trickDown(int i){
        int top=array[i];
        int son=2*i+1;
        while(i<index/2 ){
            if(2*i+2<index){
                if(array[2*i+1]>array[2*i+2])
                    son=2*i+1;
                else
                    son=2*i+2;
            }else{
                son=2*i+1;
            }
            if(array[son]<=top)
                break;
            array[i]=array[son];
            i=son;
        }
        array[i]=top;
    }
    /*remove the largest number and return it
     * */
    public int remove(){
        if(index==0)
            return -9;
        int r=array[0];
        array[0]=array[--index];
        trickDown(0);
        return r;
    }
    public String toString(){
        String s="";
        for(int i=0;i<maxSize;i++){
            s+=array[i]+"\t";
        }
        return s;
    }
}
   
8、利用java.util包裏的Arrays排序
    1)思想
        我實在是懶,不想寫代碼,最省事的方法,使用java.util包裏的Arrays排序。
    2)代碼
package sort;

import java.util.StringTokenizer;
import java.util.Arrays;
public class ArraysSort {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String s1 = "2,3,45,4,56,67,5,5";
        StringTokenizer s2 = new StringTokenizer(s1, ",");
        int count=s2.countTokens();
        int num[]=new int[count];
        for(int i=0;i<num.length;i++){
            String str=s2.nextToken();
            num[i]=Integer.parseInt(str);
        }
        Arrays.sort(num);
        System.out.println(Arrays.toString(num));
    }
}
第二部分     各種排序算法的效率
冒泡:O(n*n)  穩定    一般不用,除非想不起其他方法
選擇 O(n*n)  穩定    當數組小的時候可能會用
插入O(n*n)   穩定    如果你只能想起前三種排序方法,那最好還是用這種吧。
合併 O(n*logn)浪費空間
shell排序O(N*(logN)2)
快速排序  O(n* logN) 有時候可以達到 o(n*n)
堆排序 O(N*logN)
利用java.util包裏的Arrays排序    效率不知道,想知道看源代碼,肯定使用的是以上一種。
    書名:Data Structures & Algorithms in Java 作者 Robert Lafore。
    這本書中作者講述問題的思路特別清晰,步步深入;書中的代碼風格也值得我們學習,看書中代碼過程中我一直在想着封裝,隔離、透明幾個詞,是本好書。

                                                
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章