常用算法(Java表述)


 冒泡排序(Bubble Sort)

冒泡排序:一種交換排序,它的基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止。穩定排序算法

時間復雜度 O(n2),里層循環每趟比較第 j 項和第 j+1項,如果前項大於后項,則發生交換。缺點是每次比較后都可能發生交換,交換次數太多了,值從小到大。

 

冒泡排序

通俗概述:依次比較相鄰兩關鍵字,如果反序則立即發生交換,如果正序則繼續比較下一個相鄰項,雙重嵌套循環實現

參考代碼:

package edu.tyut;

import java.util.Arrays;

/**
 * 冒泡排序:利用雙重循環,如果前一個數比后一個數大,則發生交換,每次比較都發生交換
   優化版:添加flag標記,循環外定義flag標記為true,進入循環后flag設為false,
  若一次循環后發生了交換則將flag設為ture,進入下次循環;若整次循環只發生比較未發生交換,則說明數組已經有序,后續循環比較則不必要了,減少比較次數,提升性能
*/ public class BubbleSort { public static void bubbleSort(int[] arrs) { int temp = 0;      boolean flag=true;//用flag作標記,當沒有發生交換的時候,說明數組已經是有序的了。 for (int i = 0; i < arrs.length - 1&&flag; i++) {      flag=false;//flag標記初始化為false; for (int j = 0; j < arrs.length - i - 1; j++) { //判斷當前項是否大於后一項 if (arrs[j] > arrs[j + 1]) {//是,則發生交換 temp = arrs[j]; arrs[j] = arrs[j + 1]; arrs[j + 1] = temp; flag=true;//發生交換flag標記設為true } } } } public static void main(String[] args) { int[] arr=new int[]{2,6,5,4,2,4,6,9,7}; System.out.println(Arrays.toString(arr)); bubbleSort(arr); System.out.println(Arrays.toString(arr)); } }

 

簡單選擇排序(Simple Selection Sort)

 選擇排序有:簡單選擇排序、樹形選擇排序、堆排序

簡單選擇排序:就是通過n-1次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和i(1<=i<=n)個記錄交換值

時間復雜度 O(n2),在冒泡排序的基礎上,只不過每次比較后,將保存最小值的索引指向小值,在一次循環結束時,將最小值與循環首項發生交換。整體分為已排序和未排序兩部分,值從小到大。

 簡單選擇排序

注意區別:冒泡排序跟直接選擇排序都是依次比較相鄰的記錄,但是冒泡排序是一有反序立即交換,而直接插入排序則是出現反序將最小值記錄下來,最后再發生交換。

參考代碼:

package edu.tyut;

import java.util.Arrays;

/**
 * 簡單選擇排序:在待排序中找出最小值,然后放到已排序的末尾
 *          利用雙重for循環,找出最小值,再發生一次交換
 */
public class SelectionSort {
    //需要遍歷獲得最小值的次數
    //要注意一點,當要排序N個數,已經經過N-1次遍歷后,已經是有序數列
    public static void selectSort(int[] arrs) {
        for (int i = 0; i < arrs.length - 1; i++) {
            int temp=0;
            int index=i;//用來保存最小的索引,初始指向當前第i項
            //從未排序數中找出最小值,這里最大索引應該是數組最后一位即length-1
            for (int j = i+1; j <arrs.length; j++) {
                //如果后面的值比索引值還小,則將索引指向最小值
                if (arrs[j]<arrs[index]){
                    index=j;
                }
            }
            //已經找到最小值,然后將最小值放到已排序的末尾
            temp=arrs[i];
            arrs[i]=arrs[index];
            arrs[index]=temp;
        }
    }

    public static void main(String[] args) {
        int[] arrs=new int[]{2,6,8,99,4,46,4,5};
        System.out.println(Arrays.toString(arrs));
        selectSort(arrs);
        System.out.println(Arrays.toString(arrs));
    }
}

  

直接插入排序( Straight Insertion Sort)

直接插入排序:是將一個記錄插入到已經排好序的有序表中,從而得到一個新的、記錄增1的有序表。是一種最簡單的插入排序,穩定算法

時間復雜度O(n2),利用雙重for循環,從第二個數開始遍歷,然后保存待插入的數,這樣有序部分則可以向后覆蓋一個位置,然后里層循環 j >= 0 && temp < arrs[j] 找到合適的位置后,只需要將待插入的數插入即可。

 直接插入排序

參考代碼:

package edu.tyut;

import java.util.Arrays;

/**
 * 插入排序:
 */
public class InsertionSort {
    public static void insertSort(int[] arrs) {
        //第一個肯定是有序的,所以從第二個數開始遍歷
        for (int i = 1 ;i < arrs.length; i++) {
            int j=0;
            int temp = arrs[i];//取出第i給數,和前i-1個數比較,插入合適位置。
            //因為前i-1個數都是從小到大的有序序列,所以只要當前比較的數arr[j]比temp大,就把這個數后移一位
            for (j = i - 1; j >= 0 && temp < arrs[j]; j--) {
                arrs[j+1]=arrs[j];//因為第i個數已經保存為temp,所以可以向后覆蓋
            }
            //將第i個值插入到合適位置,因為交換結束后再自減運算,所以這里需要+1
            arrs[j+1]=temp;
        }
    }
    public static void main(String[] args) {
        int[] arrs=new int[]{2,6,5,2,3,4,3,0,6};
        System.out.println(Arrays.toString(arrs));
        insertSort(arrs);
        System.out.println(Arrays.toString(arrs));
    }
}


 

 希爾排序(Shell Sort)

希爾排序:又稱為增量排序,它是一種插入排序,它是直接插入排序算法的一種威力加強版。它的基本思想是:把記錄按步長 gap(差距,間隙) 分組,對每組記錄采用直接插入排序方法進行排序。 隨着步長逐漸減小,所分成的組包含的記錄越來越多,當步長的值減小到 1 時,整個數據合成為一組,構成一組有序記錄,則完成排序。

時間復雜度為O(nlogn),該方法因DL.Shell於1959年提出而得名。超越了O(n2)的算法的歷史。他是在直接插入排序的增強,將記錄按步長gap分組,然后在分組內進行直接插入排序。不穩定算法。

希爾排序

參考代碼:

package edu.tyut;

import java.util.Arrays;

/**
 * 希爾排序:在直接插入排序的基礎上,將記錄按步長進行分組
 *          所以可知,直接插入排序相當與步長為1的希爾排序
 */
public class ShellSort {
    public static void shellSort(int[] arrs) {
        //初始化步長為數組長度的一半
        int gap = arrs.length / 2;
        //循環結束條件,當gap<1是,循環結束,即排序結束
        while (gap >= 1) {
            //把距離為gap的元素編為一個組,掃描所有組
            for (int i = gap; i < arrs.length; i++) {
                int j = 0;
                int temp = arrs[i];//保存待插入元素,以便組內元素向后移動覆蓋
                //對距離為gap的組內元素進行直接選擇排序
                for (j = i - gap; j >= 0 && temp < arrs[j]; j = j - gap) {
                    arrs[j+gap]=arrs[j];
                }
                //將待插入元素插入合適位置
                arrs[j+gap]=temp;
            }
            //一次循環結束,所有分組均已完成直接選擇排序,則可將將距離縮小為原本的一半
            gap=gap/2;
        }
    }

    public static void main(String[] args) {
        int[] arrs=new int[]{3,6,8,4,6,7,9,8,7,5};
        System.out.println(Arrays.toString(arrs));
        shellSort(arrs);
        System.out.println(Arrays.toString(arrs));
    }
}

 

堆排序(Heap Sort)

堆排序:就是利用堆進行排序的方法。它的基本思想是,將待排序的序列構造成一個大頂堆。此時,整個序列的最大值就是堆頂的根結點,將它移走(其實就是將其與堆數組的末尾元素交換,此時末尾元素就是最大值),然后將剩余的n-1給序列重新構造成一個堆,這樣就會得到n個元素中的次小值。如此反復執行,便能得到一個有序序列。--------用直接插入排序,將數組調整為堆結構,然后再簡單選擇排序,選擇最值交換,再調整堆結構。

 堆排序-1

堆排序-2

參考代碼:

package edu.tyut;

import java.util.Arrays;

/**
 * 堆排序:利用堆(順序存儲的完全二叉樹)進行排序,每次取出堆頂最值后,對堆進行調整,使堆頂元素(根結點)為最值
 * 先用簡單選擇排序篩選出子節點中的最小值,然后通過直接插入排序,將父結點(待插入的值)插入到合適的位置。
 */
public class HeapSort {
    /**
     * 將數組調整為符合堆規律的結構
     * @param arr   傳入需要調整的數組
     * @param parent    父結點
     * @param length    需要調整的數組長度
     */
    public void heapAdjust(int[] arr, int parent, int length) {
        int temp = arr[parent];//先保存父結點的值,以便后續移動交換
        int child = parent * 2 + 1;//先獲取到該父結點的左子結點
        while (child < length) {
            //如果存在右子結點,且右子結點大於左子結點,則選取右子結點
            if (child + 1 < length && arr[child] < arr[child + 1]) {
                child++;
            }
            //判斷父結點(待插入的值)是否比子節點大
            if(temp>arr[child]){
                break;//父結點大,結束當前循環
            }else {
                /*此處類似與直接插入排序的思想*/
                arr[parent]=arr[child];//將子結點的值覆蓋父節點的值
                parent=child;
                child=child *2+1;
            }
        }
        //此時已經找到合適的位置,將待插入的值插入合適的位置
        arr[parent]=temp;
    }

    /**
     *堆排序
     * @param list
     */
    public void  shellSort(int[] list){
        /*循環建立初始堆,初始父結點為數組的一半,即完全二叉樹的最右下的父結點,
        然后遞減,依次向上調整,這樣任意指定父結點調整時,下面的子節點已經是符合堆規律的*/
        //循環建立初始化堆
        for (int i = list.length/2; i >=0 ; i--) {
            heapAdjust(list,i,list.length);
        }
        //進行n-1次循環,完成排序,這里類似於選擇排序的思想
        for (int i = list.length-1; i >0; i--) {
            //將最大值list[0]與最后一個元素交換
            int temp=list[i];
            list[i]=list[0];
            list[0]=temp;
            //交換完之后,最大值已經在底層數組的末尾,然后將交換后的堆進行調整
            heapAdjust(list,0,i);//注意這里的長度已經-1了,所以堆調整不包含最后一個元素
        }
    }

    public static void main(String[] args) {
        int[] arr=new int[]{2,6,4,9,2,3,54,1,6,166,52,6,656,54,451,6,56};
        System.out.println(Arrays.toString(arr));
        HeapSort heapSort=new HeapSort();
        heapSort.shellSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

 

 

歸並排序(Merge Sort)

歸並排序:就是利用歸並的思想實現的排序的方法。它的原理是假設初始序列含有n個記錄,則可以看成是n個有序的子序列,每個子序列的長度為1,然后兩兩歸並,得到[n/2] ([x]表示不小於x的整數)個長度為2或1的有序子序列,再兩兩歸並,。。。。如此重復,知道得到一個長度為n的有序序列為止,這種排序方法成為2路歸並排序.

注意仔細觀察它的形狀,你會發現,它很像一個導致的完全二叉樹,通常涉及到完全二叉樹結構的排序算法,效率一般都不低-----這就是歸並排序。

 

歸並排序

通俗概述:將無序數組進行兩兩分段,並將相鄰兩段進行比較排序,最后擴大步長歸並結果。 

參考代碼:

package edu.tyut;

import java.util.Arrays;

/**
 * 歸並排序
 */
public class MergeSort {
    //在各段內進行排序
    private void mergeSort(int[] arr, int low, int mid, int high) {
        int i = low; // i是第一段序列的起始索引
        int j = mid + 1; // j是第二段序列的起始索引
        int k = 0; // k是臨時存放合並序列的下標
        int[] arr2 = new int[high - low + 1]; // arr2是臨時合並序列
        // 掃描第一段和第二段序列,直到有一個掃描結束
        while (i <= mid && j <= high) {
            // 判斷第一段和第二段取出的數哪個更小,將其存入合並序列,並繼續向下掃描
            if (arr[i] <= arr[j]) {
                arr2[k] = arr[i];
                i++;
                k++;
            } else {
                arr2[k] = arr[j];
                j++;
                k++;
            }
        }
        //最完美的情況就是左右兩邊都同時掃描完添加進序列,但情況總是一邊先結束掃描
        // 若第一段序列還沒掃描完,將其全部復制到合並序列
        while (i <= mid) {
            arr2[k] = arr[i];
            i++;
            k++;
        }
        // 若第二段序列還沒掃描完,將其全部復制到合並序列
        while (j <= high) {
            arr2[k] = arr[j];
            j++;
            k++;
        }
        // 將合並序列復制到原始序列中
        for (k = 0, i = low; i <= high; i++, k++) {
            arr[i] = arr2[k];
        }
    }

    //對數組進行分段
    private void mergeDiv(int[] arr, int gap, int length) {
        int i;
        // 歸並gap長度的兩個相鄰子表
        //因為數組索引是從0開始,所以起始索引為0,步長范圍內最大索引為i+2*gap-1,中索引為i+gap-1
        for (i = 0; i + 2 * gap - 1 < length; i +=(2 * gap) ){
            mergeSort(arr, i, i + gap - 1, i + 2 * gap - 1);
        }
        // 特殊情況處理,若為奇數表,余下兩個子表,后者長度小於gap
        if (i + gap - 1 < length) {
            mergeSort(arr, i, i + gap - 1, length - 1);
        }
    }

    public void sort(int[] arr) {
        if(arr==null){
            return;
        }
        //首先初始化步長為1,然后2倍增長,與快速排序相反
        for (int gap = 1; gap < arr.length; gap = 2 * gap) {
            mergeDiv(arr, gap, arr.length);
        }
    }

    public static void main(String[] args) {
        int[] arr = {2,2,5,5,46,8,9,32,5,6,24,6};
        MergeSort mergeSort = new MergeSort();
        System.out.println("排序前:"+Arrays.toString(arr));
        mergeSort.sort(arr);
        System.out.println("排序后:"+Arrays.toString(arr));
    }
}

 

 

快速排序(Quick Sort)

快速排序的基本思想是:通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別堆這兩部分記錄繼續進行排序,以達到整個序列有序的目的。

快速排序是一種交換排序。快速排序由C. A. R. Hoare在1962年提出。

它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分:分割點左邊都是比它小的數,右邊都是比它大的數。然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸 進行,以此達到整個數據變成有序序列。

 快速排序

參考代碼:

package edu.tyut;

import java.util.Arrays;

/**
 * 快速排序
 */
public class QuickSort {
    public void quickSort(int[] arr) {
        if (arr.length > 0) {
            quickSort(arr, 0, arr.length-1);
        }
    }

    private void quickSort(int[] arr, int left, int right) {
        //遞歸條件,左小標小於右下標
        if (left < right) {
            int base = division(arr, left, right);
            //對基准數左側的一組數值進行遞歸切割,以致於將這些數值完整的排序
            quickSort(arr,left,base-1);
            //對基准數右側的一組數值進行遞歸切割,以致於將這些數值完整的排序
            quickSort(arr,base+1,right);
        }
    }

    /**
     * 用基准數切割數組
     * @param arr
     * @param left
     * @param right
     * @return
     */
    private int division(int[] arr, int left, int right) {
        //以最左邊作為基准數,保存最左邊的值,以便后續覆蓋
        int base = arr[left];
        while (left < right) {
            //從序列右端開始,向左遍歷,直到找到小於base的數
            while (left < right && arr[right] >= base) {
                right--;
            }
            arr[left] = arr[right];//將小於基准數的右值覆蓋最左邊的位置
            //從序列左端開始,向右遍歷,知道找到大於base的數
            while (left < right && arr[left] <= base) {
                left++;
            }
            arr[right]=arr[left];
        }
        //最后將base放到left的位置,此時,left位置的左側數值應該都比letf小,反之亦然
        arr[left]=base;
        return left;
    }

    public static void main(String[] args) {
        int[] arr=new int[]{2,5,5,4,6,44,9,65,62,6,9,56,56,2,62,65,95,5,23,9,562,6,95,6,59,465,65,95,9};
        System.out.println(Arrays.toString(arr));
        QuickSort quickSort=new QuickSort();
        quickSort.quickSort(arr);
        System.out.println(Arrays.toString(arr));
    }

}

 

 

*Java系統提供的Arrays.sort函數。對於基礎類型,底層使用快速排序。對於非基礎類型,底層使用歸並排序。請問是為什么?

答:這是考慮到排序算法的穩定性。對於基礎類型,相同值是無差別的,排序前后相同值的相對位置並不重要,所以選擇更為高效的快速排序,盡管它是不穩定的排序算法;而對於非基礎類型,排序前后相等實例的相對位置不宜改變,所以選擇穩定的歸並排序。


免責聲明!

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



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