第十一章
最常用的:冒泡排序、插入排序、選擇排序、歸併排序、快速排序、計數排序、基數排序、桶排序。
1.冒泡排序
當某次冒泡操作已經沒有數據交換時,說明已經達到完全有序,不用再繼續執行後續的冒泡操作
代碼
/**
* @author: xuxu
* @date 2020/2/27 9:29
* @Description: 冒泡排序
*/
public class BubbleSort {
public static void bubbleSort(int[] arr){
//控制第幾次排序
for(int i=0;i<arr.length;i++){
//因爲冒泡排序 如果有一次沒有發生排序證明已經完成排序,設置跳出的標誌位,默認沒發送排序
boolean isSort=false;
//控制每次排序數據間的比較和換位
//最後一個位置不需要發生比較所以-1,每進行一次後最後穩定的部分新增1不需要比較所以-i
for (int j=0;j<arr.length-i-1;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
isSort=true;
}
}
if(!isSort){
System.out.print("第"+(i+1)+"次排序後數組無變化退出");
break;
}
//遍歷
System.out.print("第"+(i+1)+"次排序後數組爲:");
for (int k=0;k<arr.length;k++){
System.out.print(arr[k]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] arr = {6,3,2,1,4};
bubbleSort(arr);
}
}
2.插入排序
首先,我們將數組中的數據分爲兩個區間,已排序區間和未排序區間。初始已排序區間只有一個元素,就是數組的第一個元素。插入算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間數據一直有序。重複這個過程,直到未排序區間中元素爲空,算法結束。
插入排序也包含兩種操作,一種是元素的比較,一種是元素的移動。當我們需要將一個數據 a 插入到已排序區間時,需要拿 a 與已排序區間的元素依次比較大小,找到合適的插入位置。找到插入點之後,我們還需要將插入點之後的元素順序往後移動一位,這樣才能騰出位置給元素 a 插入。
/**
* @author: xuxu
* @date 2020/2/27 9:56
* @Description:插入排序
*/
public class InsertSort {
public static void insertSort(int[] arr){
//這裏循環的是未排序部分
for(int i=1;i<arr.length;i++){
//本次循環要比較插入的數字
int insertNum = arr[i];
//這裏是循環已排序部分,從後往前比較
int j=i-1;
for(;j>=0;j--){
//如果前一個數字大於要插入的數字則向後移動讓出位置
if(arr[j]>insertNum){
arr[j+1] = arr[j];
}else{
//一旦他找到對的位置,則不需要向前繼續找,因爲前面是排好序的
break;
}
}
//將要插入的數放入前面找到的插入的位置 這裏j+1是因爲前面j--了
arr[j+1]=insertNum;
//遍歷
System.out.print("第"+(i)+"次排序後數組爲:");
for (int k=0;k<arr.length;k++){
System.out.print(arr[k]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] arr = {6,3,2,1,4};
insertSort(arr);
}
}
3.選擇排序
選擇排序算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。
/**
* @author: xuxu
* @date 2020/2/27 11:02
* @Description: 選擇排序
*/
public class SelectSort {
public static void selectSort(int[] arr){
for (int i=0;i<arr.length;i++){
int min=i;
for(int j=i;j<arr.length-1;j++){
if(arr[min]>arr[min+1]){
//先找出未排序區最小的一個下標
min = min+1;
}
}
//將min放入已排序區的最後面,同時原位置的元素放在min處
int temp = arr[i];
arr[i]=arr[min];
arr[min] = temp;
//遍歷
System.out.print("第"+(i+1)+"次排序後數組爲:");
for (int k=0;k<arr.length;k++){
System.out.print(arr[k]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] arr = {6,3,2,1,4};
selectSort(arr);
}
}
從推薦使用的角度看 插入>冒泡>選擇
冒泡排序、插入排序、選擇排序這三種排序算法,它們的時間複雜度都是 O(n2),比較高,適合小規模數據的排序。
歸併排序和快速排序兩種時間複雜度爲 O(nlogn) 。這兩種排序算法適合大規模的數據排序
歸併排序和快速排序都用到了分治思想
4.歸併排序
分治算法一般都是用遞歸來實現的。分治是一種解決問題的處理思想,遞歸是一種編程技巧,這兩者並不衝突。
遞歸公式
遞推公式:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
終止條件:
p >= r 不用再繼續分解
merge_sort(p…r) 表示,給下標從 p 到 r 之間的數組排序。我們將這個排序問題轉化爲了兩個子問題,merge_sort(p…q) 和 merge_sort(q+1…r),其中下標 q 等於 p 和 r 的中間位置,也就是 (p+r)/2。當下標從 p 到 q 和從 q+1 到 r 這兩個子數組都排好序之後,我們再將兩個有序的子數組合並在一起,這樣下標從 p 到 r 之間的數據就也排好序了
合併過程
我們申請一個臨時數組 tmp,大小與 A[p…r] 相同。我們用兩個遊標 i 和 j,分別指向 A[p…q] 和 A[q+1…r] 的第一個元素。比較這兩個元素 A[i] 和 A[j],如果 A[i]<=A[j],我們就把 A[i] 放入到臨時數組 tmp,並且 i 後移一位,否則將 A[j] 放入到數組 tmp,j 後移一位。
繼續上述比較過程,直到其中一個子數組中的所有數據都放入臨時數組中,再把另一個數組中的數據依次加入到臨時數組的末尾,這個時候,臨時數組中存儲的就是兩個子數組合並之後的結果了。最後再把臨時數組 tmp 中的數據拷貝到原數組 A[p…r] 中。
/**
* @author: xuxu
* @date 2020/2/27 14:32
* @Description: 歸併排序
* f(a,p) =f(a,q)+f(q+1,p)
*/
public class MergeSort {
public static void mergeSort(int[] arr,int begin,int end ){
if(begin>=end){
return ;
}
//中間位置
int mid = (begin+end)/2;
//遞歸分解合併
mergeSort(arr, begin, mid);
mergeSort(arr, mid+1 ,end);
merge(arr,begin,mid,end);
}
/**
* 合併操作
*/
public static void merge(int[] arr, int begin, int mid, int end){
int[] mergeArr=new int[arr.length];
//數組1的指針
int p1 = begin;
//數組2的指針
int p2 = mid+1;
//合併的數組起始位置
int index=begin;
while(p1<=mid && p2<=end){
if(arr[p1]>=arr[p2]){
mergeArr[index++]=arr[p2++];
}else{
mergeArr[index++]=arr[p1++];
}
}
while(p1<=mid){
mergeArr[index++] = arr[p1++];
}
while(p2<=end){
mergeArr[index++] = arr[p2++];
}
for(int i=begin;i<=end;i++){
arr[i] = mergeArr[i];
}
}
public static void main(String[] args) {
int[] arr = {6,5,3,4,2,1,7};
mergeSort(arr, 0, arr.length-1);
System.out.print("排序後數組元素爲:");
for (int i=0;i<arr.length;i++){
System.out.print(arr[i]);
}
}
}
5.快速排序
基本思想:選擇一個基準元素,通常選擇第一個元素或者最後一個元素,通過一趟掃描,將待排序列分成兩部分,一部分比基準元素小,一部分大於等於基準元素,此時基準元素在其排好序後的正確位置,然後再用同樣的方法遞歸地排序劃分的兩部分
實現:
public class QuickSort {
public static int getMid(int[] nums,int low,int high){
int temp=nums[low];
while(low<high){
while(low<high && nums[high]>=temp){
high--;
}
nums[low]=nums[high];//如果右邊小於中間數 移動到左邊
while(low<high && nums[low]<=temp){
low++;
}
nums[high]=nums[low]; //如果左邊有大於中間數 的數移動到右邊
}
nums[low]=temp; //賦值中間數
return low;
}
public static void quick(int[] nums , int low,int high){
if(low<high){
int mid = getMid(nums,low,high);
//遞歸調用左右兩邊繼續排序
quick(nums,low,mid-1);
quick(nums,mid+1,high);
}
}
public static void main(String[] args) {
int[] nums = {22,11,33,66,77,55,44,99,88};
quick(nums,0,nums.length-1);
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i]+" ");
}
}
}