- 選擇排序
- 冒泡排序
- 插入排序
- 希爾排序
- 堆排序
- 歸併排序
- 快速排序
- 基數排序
- 計數排序
- 桶排序
1. 選擇排序
這個排序方法最簡單,廢話不多說,直接上代碼:
public class SelectSort {
/**
* 選擇排序
* 思路:每次循環得到最小值的下標,然後交換數據。
* 如果交換的位置不等於原來的位置,則不交換。
*/
public static void main(String[] args) {
selectSort(Datas.data);
Datas.prints("選擇排序");
}
public static void selectSort(int[] data){
int index=0;
for (int i = 0; i < data.length; i++) {
index = i;
for (int j = i; j < data.length; j++) {
if (data[index]>data[j]) {
index = j;
}
}
if (index != i) {
swap(data,index,i);
}
}
}
public static void swap(int[] data,int i,int j){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
選擇排序兩層循環,第一個層循環遍歷數組,第二層循環找到剩餘元素中最小值的索引,內層循環結束,交換數據。內層循環每結束一次,排好一位數據。兩層循環結束,數據排好有序。
2 冒泡排序
冒泡排序也簡單,上代碼先:
public class BubbleSort {
/**
* 冒泡排序
* 思路:內部循環每走一趟排好一位,依次向後排序
*/
public static void main(String[] args) {
bubbleSort(new int[]{9, 4, 2, 6, 8, 2, 0});
}
private static void bubbleSort(int[] data) {
int temp;
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data.length - i - 1; j++) {
if (data[j] > data[j+1]) {
temp =data[j];
data[j]=data[j+1];
data[j+1] = temp;
}
}
}
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ", ");
}
}
}
冒泡排序和選擇排序有點像,兩層循環,內層循環每結束一次,排好一位數據。不同的是,數據像冒泡一樣,不斷的移動位置,內層循環結束,剛好移動到排序的位置。
該圖對應上面的代碼進行的說明,沒有用專門的畫圖工具,使用的是window的maspint,大家湊合着看哈_明白意思就成!
3 插入排序
插入排序也是簡單的排序方法,代碼量不多,先看代碼:
public class InsertSort {
/**
* 插入排序
* 思路:將數據插入到已排序的數組中。
*/
public static void main(String[] args) {
int[] data = Datas.data;
int temp;
for (int i = 1; i < data.length; i++) {
temp = data[i];//保存待插入的數值
int j = i;
for (; j>0 && temp<data[j-1]; j--) {
data[j] = data[j-1];
//如果帶插入的數值前面的元素比該值大,就向後移動一位
}
//內部循環結束,找到插入的位置賦值即可。
data[j]=temp;
}
Datas.prints("插入排序");
}
}
該圖是上面插入排序的說明圖,插入排序,其過程就是其名字說明的一樣,將待排序的數據插入到已排序的數據當中。兩層循環,內層循環結束一次,插入排序排好一位數據。
4 希爾排序
希爾排序,也叫縮減增量排序,其中增量的設置影響着程序的性能。最好的增量的設置爲1,3,5,7,11,。。。這樣一組素數,並且各個元素之間沒有公因子。這樣的一組增量 叫做Hibbard增量。使用這種增量的希爾排序的最壞清醒運行時間爲θ()
當不使用這種增量時,希爾排序的最壞情形運行時間爲θ()
這裏電腦打印這些太麻煩,乾脆手寫拍照啦哈哈哈哈。。。。。
好了,廢話不多說,上代碼;
public class ShellSort {
/**
* 希爾排序(縮減增量排序)
* 想想也不難。
* 思路:三層循環
* 第一層循環:控制增量-增量隨着程序的進行依次遞減一半
* 第二層循環:遍歷數組
* 第三層循環:比較元素,交換元素。
* 這裏需要注意的是:比較的兩個元素和交換的兩個元素是不同的。
*/
public static void main(String[] args) {
int[] data = Datas.data;
int k;
for (int div = data.length/2; div>0; div/=2) {
for (int j = div; j < data.length; j++) {
int temp = data[j];
for (k=j; k>=div && temp<data[k-div] ; k-=div) {
data[k] = data[k-div];
}
data[k] = temp;
}
}
Datas.prints("希爾排序");
}
}
程序中,需要注意的是第三層循環,第三層循環的代碼中,if語句的比較和內部的交換是分別不同的兩個數據。原因是:把大的數據後移,小的數據前移,形成這樣一種趨勢,才能實現排序。
當然可以試試,if語句比較的兩個數據和內部移動的數據一致的話,會出現什麼問題?出現的問題就是移動的數據打破了之前形成的大的數據在後,小的數據在前的趨勢。無法排序。
5 堆排序
堆排序,要知道什麼是堆?說白了,堆就是完全二叉樹,堆是優先隊列。要求父元素比兩個子元素要大。這就好辦了。數組元素構建堆,根節點最大,刪除根節點得到最大值,剩下的元素再次構建堆,接着再刪除根節點,得到第二大元素,剩下的元素再次構建堆,依次類推,得到一組排好序的數據。爲了更好地利用空間,我們把刪除的元素不使用新的空間,而是使用堆的最後一位保存刪除的數據。
代碼上來:
public class HeapSort {
/**
* 堆排序(就是優先隊列)
* 也就是完全二叉樹
* 第一步:建堆.其實就是講數組中的元素進行下慮操作,
* 使得數組中的元素滿足堆的特性。
* 第二步:通過將最大的元素轉移至堆的末尾,
* 然後將剩下的元素在構建堆。
* 完成排序。
* 最重要的過程就是構建堆的過程。
* 裏面的比較思路和希爾排序中的比較思路一致。
* 將大的元素上浮,小的元素下浮。始終和temp比較。
* temp除了第一次比較可能改變外,其他次數的比較不改變該值。
* 這樣的處理就是讓較大的元素趨於上浮,較小的元素下浮。
*/
public static void main(String[] args) {
int[] data = Datas.data;
for (int i = data.length/2; i >=0; i--) {
buildHeap(data,i,data.length);
}
Datas.prints("堆排序-構建樹");
System.out.println("============================");
for (int i = data.length-1; i>0; i--) {
swap(data, 0, i);
buildHeap(data, 0, i);
}
Datas.prints("堆排序-排序後");
}
static void swap(int[] data,int i,int j){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
static void buildHeap(int[] data,int i,int len){
int leftChild = leftChild(i);
int temp = data[i];
for (; leftChild<len;) {
if (leftChild != len-1 && data[leftChild]<data[leftChild+1]) {
leftChild++;
}
if (temp<data[leftChild]) {
data[i] = data[leftChild];
}else {
/**braek說明兩個兒子都比父節點小,
* 父節點大於兩個兒子
* 所以直接停止比較,減小比較的次數。
*/
break;
}
i = leftChild;
leftChild = leftChild(i);
}
data[i] = temp;
}
//返回節點i的左兒子的index
static int leftChild(int i){
return 2*i+1;
}
}
堆排序,最重要的就是構建堆,構建堆是核心!我們代碼中使用的是數組形式的二叉樹,也就是優先隊列。要真正看懂這部分的代碼,需要知道優先隊列部分的知識,不難,看看就懂啦。說白了就是二叉樹。
6 歸併排序
歸併排序思路就是將兩個已經排好序的數組插入到第三個數組當中。核心就是將原有數組分割兩部分,排好序,插入到第三個與原有數組大小一致的數組中。代碼上來:
public class MergeSort {
/**
* 歸併排序
* 思路:如果是兩個已排序的數組,進行合併非常簡單。
* 所以就對原有數組進行分割,分割成各個排序的數組,
* 然後遞歸合併。
*/
public static void main(String[] args) {
int[] data = Datas.data;
merge(data);
Datas.prints("歸併排序");
}
public static void merge(int[] data){
int[] temp = new int[data.length];
merge0(data, temp, 0, data.length-1);
}
public static void merge0(int[] data,int[] temp,int left,int rigth){
if (left<rigth) {
int center = (left+rigth)/2;
merge0(data, temp, left, center);
merge0(data, temp, center+1, rigth);
mergeSort(data,temp,left,center,rigth);
}
}
public static void mergeSort(int[] data,int[] temp,int left,int center,int right){
int leftEnd = center;
int rightStar = center+1;
int len = right-left+1;
int tempPos = left;
/**
* 這裏的三個循環很容易理解。
* 其實現實兩個已經排序的數組進行比較,
* 將元素添加到temp數組中保存。
*/
while (left<=leftEnd&&rightStar<=right) {
if (data[left]<=data[rightStar]) {
temp[tempPos++] = data[left++];
}else {
temp[tempPos++] = data[rightStar++];
}
}
while (left<=leftEnd) {
temp[tempPos++]=data[left++];
}
while (rightStar<=right) {
temp[tempPos++]=data[rightStar++];
}
/**
* 關鍵的一步是下面的拷貝工作。
* 爲什麼數組中的拷貝是從right--開始???
* 原因是:通過說明圖中,我們知道,元素比較之後,
* 會將元素賦值給temp數組相對應的位置上,並不會影響其他位置的數據。
* 並且下面的循環中也沒有使用其他位置上面的數據,僅僅拷貝
* 本次已經排序的元素。
* 下面的拷貝是從right開始,right位置是本次排序最右邊的元素
* 其實也可以從left開始,只不過left在上面的排序中值已經改變,
* 可以定義一個int leftFlag = left;保存初始最左邊的位置,
* 此時下面的循環可以改爲:
* for (int i = 0; i < len; i++,leftFlag++) {
* data[leftFlag]=temp[leftFlag];
* }
* 運行程序,你會發現,正確輸出結果。
*/
for (int i = 0; i < len; i++,right--) {
data[right]=temp[right];
}
}
}
說明圖中已經說明了關於數組分割,排序、歸併的步驟。歸併排序其實就是分割,排序,歸併,最後得到排序的結果。
排序要等到分割完成之後進行,歸併要等盜排序之後進行。
分割通過遞歸調用進行,排序通過程序中的三個while循環進行,即完成了歸併。最後將數據拷貝要原來的數組中去。
7 快速排序
快速排序有點類似於歸併排序,其實也是分割,不同的是,快速排序的分割是按照中值進行分割的,所以中值的好壞影響着程序的性能。最常見的情形是三數中值分割法!!
該方法的思路是選取左,右、中三個數進行交換,把三個數中最小值放在左邊,中間值放在中間,最大值放在右邊,這樣以中值爲界形成了兩部分,要注意這時候還沒有排序,只是進行了樞紐元的選取。中間值就是樞紐元!
然後以樞紐元爲中心,分別交換左右兩側的數據,把大的數據放在樞紐元的右側,把小的數據放在樞紐元的左側,最終形成大致排序的兩組數據,樞紐元排好序,然後遞歸調用快速排序。
同時,爲了移動數據方便,我們把樞紐元的位置放在right-1的位置。
大家可能要問了:爲什麼把樞紐元放在right-1的位置呢?
原因是:right的位置放置的是比樞紐元大的數,在選取樞紐元的時候,我們把大的數放在right的位置,最小的數放在left的位置,把樞紐元放在right-1的位置,這樣當完成數據交換之後樞紐元只需一次交換。如果把樞紐元放在中間的位置,要知道的是,中間位置並不一定就是樞紐元要排序的位置。這個地方要搞清楚,還得看代碼:
public class QuickSort {
/**
* 快速排序
* 首先找到三數中值,然後分別移動左右兩邊的數據,
* 以中值數分割成兩組,一組比中值數大,一組比中值數小。
* 然後遞歸快排兩組數組。
* 當待排序的數組小於CUTOFF時,使用插入排序。
*/
private static int[] datas = {4, 9, 0, 1, 4, 5, -14, -15, 90, 7, 99};
public static void main(String[] args) {
quickSort(datas, 0, datas.length-1);
for (int i = 0; i < datas.length; i++) {
System.out.print(datas[i] + ", ");
}
}
public static void quickSort(int[] data,int left,int right) {
int CUTOFF = 1;
if (left+CUTOFF<right) {
//找到中值數
int i = partition(data, left, right);
//遞歸排序中值數兩邊的數據
quickSort(data, left, i-1);
quickSort(data, i+1, right);
}else {
/**
* 插入排序
* 當待排序的元素少於20個時候,
* 快速排序性能不如直接插入排序好。
* 所以else語句裏面,在待排序基本有序的情況下
* 可以使用直接插入排序更好。
*/
InsertSort();
}
}
private static int partition(int[] data, int left, int right) {
int media = media3(data, left, right);
//保存左右界,left,right值不變
int i =left;
int j = right-1;
//循環移動左右兩邊的元素
while (true) {
while(data[++i]<media);
while (data[--j]>media);
if (i>j) {
break;
}
swap(data, i, j);
}
//將中值數移動到i處。中值數即排在i處。
swap(data, i, right-1);
return i;
}
//找到中值數
public static int media3(int[] data,int left,int right){
int center = (left+right)/2;
/**
* 前兩個if語句的比較,
* 使得最小值放在最左邊。
*/
if (data[center]<data[left]) {
swap(data, center, left);
}
if (data[right]<data[left]) {
swap(data, right, left);
}
/**
* 第三個if語句使得最大值放在最右邊。
* 中間值,放在中間位置。
*/
if (data[right]<data[center]) {
swap(data, right, center);
}
//把中間的位置放在right-1的位置。
swap(data, center, right-1);
return data[right-1];
}
//交換數據
public static void swap(int[] data,int i,int j) {
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
private static void InsertSort() {
for (int i = 1; i < datas.length; i++) {
for (int j = i; j > 0; j--) {
if (datas[j] < datas[j-1]) {
swap(datas, j, j-1);
}
}
}
}
}
快速排序說明圖,示意了i、j遊標的移動位置。結合程序應該能看懂。
不過值得大家注意的是,程序中不僅僅使用快速排序的思路,而在最後,當left與right的差值在CUTOFF的時候,直接使用直接插入排序,不再使用快速排序。原因我在註釋中已經給出。
如果不使用CUTOFF時候的插入排序,最終的結果並不是我們想要的。如果僅僅使用快速排序得到最終結果,則代碼是不正確的。
上面的代碼必須在最後使用一次插入排序才能得到最終的結果。
8 基數排序
桶排序之前不瞭解,我看的《數據結構與算法分析》一書中並沒有給出大量的講解,反而代碼是通過例題的形式給出的。桶排序其實就是形成大的容器,通過比較數據各個位上的數進行排序。
別的不多說了,直接上代碼:
public class RadixSort {
/**
* 基數排序
* 二維數組構成桶
* 一維數組記錄每個位存放的個數。
* 每次構建桶完成,拷貝數據到原來的數組中去。
* 繼續下一輪桶的構建。
* 分別個位,十位,百位。。。
* 程序必須知道最大值的位數。
*/
public static void main(String[] args) {
radixSort(Datas.data,3);
Datas.prints("基數排序");
}
public static void radixSort(int[] data,int maxLen){
//maxLen表示最大值的長度
//LSD最低位優先排序 MSD最高位優先排序 l從0開始 循環三次
int k = 0;
int n = 1;
int[][] bucket = new int[10][data.length];//桶
/**
* 表示桶的每一行也就是每一位存放的個數
*/
int[] orders = new int[10];
int temp = 0;
for (int l = 0; l < maxLen; l++) {
for (int i = 0; i < data.length; i++) {
temp = (data[i]/n)%10;
bucket[temp][orders[temp]] = data[i];
orders[temp]++;
}
//將桶中的數值保存會原來的數組中
for (int i = 0; i < 10; i++) {
for (int j = 0; j < orders[i]; j++) {
if (orders[i]>0) {
data[k]=bucket[i][j];
k++;
}
}
//拷貝完成清除記錄的個數,設爲0
orders[i]=0;
}
//n乘以10 取十位 百位的數值
n*=10;
k=0;
//k值記錄拷貝數據到原有數組中的位置,拷貝完成恢復0
}
}
}
這個是實例,程序打印結果的話,不好看,只好手寫大家看效果。
bucket二維數組存放原始數據。orders數組存放每一位數存放的原始數據的個數。外層循環每執行一次,就把數據拷貝給原來的數組。然後進行下一輪循環。分別進行個位、十位、百位、、、、的循環。這是從最低位開始排序。也有最高位開始進行的排序。
9 計數排序
這個直接上代碼:
public class CuntingSort {
/**
* 計數排序
* 思路:構建一個與待排序中最大值相同大小的數組,
* 該數組存放待排序數組中每個數字出現的個數。
*/
public static void main(String[] args) {
cunting(Datas.data, 333);
Datas.prints("計數排序");
}
public static void cunting(int[] data,int max){
int[] temp = new int[max+1];
int[] result = new int[data.length];
/**
* 該循環設置初始值爲0
*/
for (int i = 0; i < temp.length; i++) {
temp[i]=0;
}
/**
* 該for語句循環遍歷原數組,將數組中元素出現的個數存放在
* temp數組中相對應的位置上。
* temp數組長度與最大值的長度一致。保證每個元素都有一個對應的位置。
*/
for (int i = 0; i < data.length; i++) {
temp[data[i]]+=1;
}
/**
* 累計每個元素出現的個數。
* 通過該循環,temp中存放原數組中數據小於等於它的個數。
* 也就是說此時temp中存放的就是對應的元素排序後,在數組中存放的位置+1。
*/
for (int i = 1; i < temp.length; i++) {
temp[i]=temp[i]+temp[i-1];
}
/**
* 這裏從小到大遍歷也可以輸出正確的結果,但是不是穩定的。
* 只有從大到小輸出,結果纔是穩定的。
* result中存放排序會的結果。
*/
for (int i = data.length-1; i>=0; i--) {
int index = temp[data[i]];
result[index-1]= data[i];
temp[data[i]]--;
}
Datas.data = result;
}
}
這個排序還真沒法畫圖,其實這個排序相當容易理解。找出待排序數組中最大的元素,構建一個與最大元素數+1長度的數組temp,這樣保證待排序數組中的每一個元素都能在temp數組中找到自己的位置,但是temp不是用來存放元素的,而是存放每個元素在待排序數組中出現的個數。這一步通過第二個for循環得到。
接着第三個for循環,循環遍歷temp,目的就是得到每個元素排序後存放在原有數組中的位置。大家想一想,每個位置存放自己出現的個數,那麼小於自己的元素出現的個數加到一起,即可得到自己排序後數組中的存放位置。是不是真的很巧妙!!!!!
到此,基本就是該排序算法的核心啦!!!
10 桶排序
桶排序是另外一種以O(n)或者接近O(n)的複雜度排序的算法. 它假設輸入的待排序元素是等可能的落在等間隔的值區間內.一個長度爲N的數組使用桶排序, 需要長度爲N的輔助數組. 等間隔的區間稱爲桶, 每個桶內落在該區間的元素. 桶排序是基數排序的一種歸納結果。
算法的主要思想: 待排序數組A[1…n]內的元素是隨機分佈在[0,1)區間內的的浮點數.輔助排序數組B[0…n-1]的每一個元素都連接一個鏈表. 將A內每個元素乘以N(數組規模)取底,並以此爲索引插入(插入排序)數組B的對應位置的連表中. 最後將所有的鏈表依次連接起來就是排序結果.
這個過程可以簡單的分步如下:
1、設置一個定量的數組當作空桶子。
2、尋訪序列,並且把項目一個一個放到對應的桶子去。
3、對每個不是空的桶子進行排序。
4、從不是空的桶子裏把項目再放回原來的序列中。
例如要對大小爲[1…1000]範圍內的n個整數A[1…n]排序,可以把桶設爲大小爲10的範圍,具體而言,設集合B[1]存儲[1…10]的整數,集合B[2]存儲(10…20]的整數,……集合B[i]存儲((i-1)10, i10]的整數,i = 1,2,…100。總共有100個桶。然後對A[1…n]從頭到尾掃描一遍,把每個A[i]放入對應的桶B[j]中。 然後再對這100個桶中每個桶裏的數字排序,這時可用冒泡,選擇,乃至快排,一般來說任何排序法都可以。最後依次輸出每個桶裏面的數字,且每個桶中的數字從小到大輸出,這樣就得到所有數字排好序的一個序列了。
/**
* 桶排序算法,對arr進行桶排序,排序結果仍放在arr中
* @param arr
*/
public static void main(String[] args){
bucketSort(Datas.datad);
for (int i = 0; i < Datas.datad.length; i++) {
System.out.println(Datas.datad[i]+",");
}
}
public static void bucketSort(double arr[]){
int n = arr.length;
ArrayList<Double> arrList[] = new ArrayList[n];
//把arr中的數均勻的的分佈到[0,1)上,每個桶是一個list,存放落在此桶上的元素
for(int i =0;i<n;i++){
int temp = (int) Math.floor(n*arr[i]);
if(null==arrList[temp])
arrList[temp] = new ArrayList<>();
arrList[temp].add(arr[i]);
}
//對每個桶中的數進行插入排序
for(int i = 0;i<n;i++){
if(null!=arrList[i])
insert(arrList[i]);
}
//把各個桶的排序結果合併
int count = 0;
for(int i = 0;i<n;i++){
if(null!=arrList[i]){
Iterator<Double> iter = arrList[i].iterator();
while(iter.hasNext()){
Double d = (Double)iter.next();
arr[count] = d;
count++;
}
}
}
}
/**
* 用插入排序對每個桶進行排序
* @param list
*/
public static void insert(ArrayList<Double> list){
if(list.size()>1){
for(int i =1;i<list.size();i++){
if((Double)list.get(i)<(Double)list.get(i-1)){
double temp = (Double) list.get(i);
int j = i-1;
for(;j>=0&&((Double)list.get(j)>(Double)list.get(j+1));j--)
list.set(j+1, list.get(j));
list.set(j+1, temp);
}
}
}
}
}
原文地址:http://www.tuicool.com/articles/3emMVz
這個是我看到的關於桶排序說的最好的一篇文章。
舉例說明:
假如待排序列K= { 49、 38 、 35、 97 、 76、 73 、 27、 49 }。這些數據全部在1—100之間。因此我們定製10個桶,然後確定映射函數f(k)=k/10。則第一個關鍵字49將定位到第4個桶中(49/10=4)。依次將所有關鍵字全部堆入桶中,並在每個非空的桶中進行快速排序後得到如下圖所示:
對上圖只要順序輸出每個B[i]中的數據就可以得到有序序列了。
這個例子有點類似於基數排序。因此,可以說,桶排序是基數排序的一種。
網上很多講述基數排序,計數排序,桶排序的文章,但是很多都搞混了。如果大家希望看到最權威的講述就看《算法導論》這本書。這本書專門一章講述基數排序,計數排序和桶排序。不過,算法導論有點難呦~~
本文中全部代碼下載,[請猛戳這裏!!]