簡介
這是一份常用經典排序算法的Java代碼實現,可運行。
算法包括:冒泡排序、插入排序、希爾排序、快速排序、選擇排序、歸併排序、堆排序。
複雜度
算法複雜度,穩定性:
忘記說希爾排序了~
希爾排序是不穩定的。因爲,雖然一次插入排序是穩定的,但是分區後的插入可能導致不同分區之間的數相對位置發生改變,因此最終是不穩定的。
希爾排序的時間複雜度沒有明確的說法,有的說平均複雜度是O(nlogn),有的說是O(n1.3),總之就是比O(n2)小啦。
希爾排序是按照不同步長對元素進行插入排序,當剛開始元素很無序的時候,步長最大,所以插入排序的元素個數很少,速度很快;當元素基本有序了,步長很小,插入排序對於有序的序列效率很高。所以,希爾排序的時間複雜度會比o(n^2)好一些。
代碼
話不多說,上代碼:
package test1;
public class Test {
/**
* 交換數組中兩個數
* @param nums
* @param i
* @param j
*/
public static void swap(int nums[],int i,int j){
int temp;
temp = nums[i];
nums[i] = nums[j];
nums[j]=temp;
}
/**
* 冒泡排序
* @param nums
*/
public static void bubbleSort(int nums[]){
for(int i = 0;i<nums.length;i++){
for(int j = 0;j<nums.length-i-1;j++){
if(nums[j]>nums[j+1]){
swap(nums,j,j+1);
}
}
}
}
/**
* 改進的冒泡排序
* @param nums
*/
public static void improvedBubbleSort(int nums[]){
for(int i = 0;i<nums.length;i++){
boolean isChanged = false;//標記一輪中,是否發生交換
for(int j = 0;j<nums.length-i-1;j++){
if(nums[j]>nums[j+1]){
swap(nums,j,j+1);
isChanged = true;
}
}
if(!isChanged){//如果沒有發生交換,則已經有序,直接結束排序
break;
}
}
}
/**
* 插入排序
* @param nums
*/
public static void insertSort(int nums[]){
int len = nums.length;
for(int i = 1;i<len;i++){
if(nums[i]<nums[i-1]){//如果有逆序,則向前找到一個不大於該值的位置,插到它後面
int temp = nums[i];//保存當前值
int j;
//向前尋找
for(j = i-1;j>=0&&nums[j]>temp;j--){
nums[j+1] = nums[j];
}
nums[j+1] = temp;//賦值
}
}
}
/**
* 引入二分查找的插入排序
* @param nums
*/
public static void insertBinarySort(int nums[]){
int len = nums.length;
for(int i = 1;i<len;i++){
if(nums[i]<nums[i-1]){//如果有逆序,則向前找到一個不大於該值的位置,插到它後面
int temp = nums[i];//記錄當前值
int low = 0,h = i-1,mid=0;//二分查找的low,high,mid位置標記
while(low<=h){
mid = (low+h)/2;
if(temp>nums[mid]){//如果當前值大於中間位置的值,則區間改爲[mid+1,h]
low = mid+1;
}else{//如果當前值小於中間位置的值,則區間改爲[low,mid-1]
h = mid-1;
}
}
//找到位置(就是low)後,從後向前移動數組,以騰出位置
for(int j = i;j>low;j--){
nums[j] = nums[j-1];
}
nums[low] = temp;//賦值
}
}
}
/**
* 希爾排序
* @param nums
*/
public static void shellSort(int nums[]){
int len = nums.length;
for(int gap = len/2;gap>0;gap/=2){//gap每次減半
for(int i = 0;i<len;i+=gap){//比較的時候,每隔gap個數比較一次
int temp = nums[i];//記錄當前值
int j;
//如果當前值小於前面對應的gap位置的數,則把前面的數賦值到當前位置
//j-=gap,將索引又移到前面gap處,以便於交換
for(j = i;j>=gap&&temp<nums[j-gap];j-=gap){
nums[j] = nums[j-gap];
}
//再把當前值賦值到前面的位置,本質就是交換了當前值和前面對應gap位置的數
nums[j] = temp;
}
}
}
/**
* 選擇排序
* @param nums
*/
public static void selectSort(int nums[]){
int len = nums.length;
for(int i = 0;i<len-1;i++){
int min = i;//記錄最小值的位置
for(int j = i+1;j<len;j++){
if(nums[j]<nums[min]){//如果有更小的,則更新最小值的位置
min = j;
}
}
//如果最小值位置改變了(不是i了),則交換,就是將最小值從後面換到前面
if(min!=i){
swap(nums,i,min);
}
}
}
/**
* 分而治之,找到劃分基準,並交換
* @param nums
* @param left
* @param r
* @return
*/
public static int partation(int nums[],int left,int r){
int pv = nums[left];//以最左邊的數爲基準
int p = left;//記錄基準位置
//遍歷數組,如果小於基準,則與基準交換
for(int i = p+1;i<=r;i++){
if(nums[i]<pv){
p++;//基準前移
if(p!=i){//如果是基準位置的下一個位置,則不用交換
swap(nums,p,i);
}
}
}
//最後,交換 劃分位置 和 最左邊
swap(nums,p,left);
return p;//返回劃分位置
}
/**
* 快速排序
* @param nums
* @param left
* @param r
*/
public static void quickSort(int nums[],int left,int r){
if(left<r){//切記,一定要左邊小於右邊時才分部處理
int p = partation(nums,left,r);//找到一個基準位置p
quickSort(nums,0,p-1);//以p爲中心,將數組分成兩個子數組,分而治之
quickSort(nums,p+1,r);
}
}
/**
* 快速排序
* @param nums
*/
public static void quickSort(int nums[]){
int len = nums.length;
quickSort(nums,0,len-1);
}
/**
* 合併
* @param nums
* @param temp
* @param left
* @param mid
* @param r
*/
public static void merge(int nums[],int temp[],int left,int mid,int r){
//拷貝一份數組
for(int i = left;i<=r;i++){
temp[i] = nums[i];
}
//記錄左右部分的索引
int pa = left,pb = mid+1;
int index = left;//合併後數組的索引
while(pa<=mid&&pb<=r){
if(temp[pa]<=temp[pb]){//哪一部分小,就拷貝哪一部分到新數組
nums[index++] = temp[pa++];
}else{
nums[index++] = temp[pb++];
}
}
//處理沒有拷貝完的部分,直接賦值到新數組
while(pa<=mid){
nums[index++] = temp[pa++];
}
while(pb<=r){
nums[index++] = temp[pb++];
}
}
/**
* 歸併排序
* @param nums
* @param temp
* @param left
* @param r
*/
public static void mergeSort(int nums[],int temp[],int left,int r){
if(left<r){//左邊小於右邊時,分而治之
int mid = (left+r)/2;
mergeSort(nums,temp,0,mid);
mergeSort(nums,temp,mid+1,r);
//合併
merge(nums,temp,left,mid,r);
}
}
/**
* 歸併排序
* @param nums
*/
public static void mergeSort(int nums[]){
int len = nums.length;
int temp[] = new int[len];
mergeSort(nums,temp,0,len-1);
}
/**
* 調整堆
* @param nums
* @param i
* @param len
*/
public static void adjustHeap(int nums[],int i,int len){
int temp = nums[i];//記錄當前節點值(父節點)
//向下遍歷子節點
for(int k = 2*i+1;k<len;k = k*2+1){
//如果存在右子節點,且右子節點大於左子節點
if(k+1<len&&nums[k]<nums[k+1]){
k++;//指向右子節點
}
//如果子節點比當前節點(父節點)大,則將子節點值賦給父節點
if(nums[k]>temp){
nums[i] = nums[k];
i = k;//記錄父節點的位置,最終要落到的位置
}else{//如果子節點小於父節點,則調整結束
break;
}
}
//將當前父節點的值,最終落位入座
nums[i] = temp;
}
/**
* 堆排序,升序,最大堆
* 通過不斷把根節點(最大值)放到最後一個節點處,
* 實現數組從小到大排序
* @param nums
*/
public static void heapSort(int nums[]){
int len = nums.length;
// build heap
for(int i = len/2-1;i>=0;i--){
//從最後一個非葉節點開始調整,使之滿足最大堆
adjustHeap(nums,i,len);
}
// exchange and adjust
for(int j = len-1;j>=0;j--){
//交換根節點與最後一個節點,通過不斷把根節點(最大值)放到最後一個節點處,
//實現數組從小到大排序
swap(nums,0,j);
//調整剩下的節點,使它們滿足最大堆
adjustHeap(nums,0,j);//j是當做長度傳過去的,所以,去掉了最後一個節點
}
}
public static void main(String[] args) {
int nums[] = {2,4,5,7,2,3,4};
System.out.println("-----排序前-----");
for(int n:nums){
System.out.println(n);
}
// bubbleSort(nums);//冒泡排序
// improvedBubbleSort(nums);//改進的冒泡排序
// insertSort(nums);//插入排序
// insertBinarySort(nums);//二分插入排序
// shellSort(nums);//希爾排序
// selectSort(nums);//選擇排序
// quickSort(nums);//快速排序
// mergeSort(nums);//歸併排序
heapSort(nums);//堆排序
System.out.println("------排序後-----");
for(int n:nums){
System.out.println(n);
}
}
}
補充:
快速排序的思想:分治法
- 1.先從數組中取出一個數作爲基準數。
- 2.分區過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。
- 3.再對左右區間重複第二步,直到各區間只有一個數。
參考文章
堆排序講解 https://www.cnblogs.com/chengxiao/p/6129630.html
各算法的性能及複雜度講解 https://segmentfault.com/a/1190000004994003