這次整理的爲三個原理上相近的排序算法,便於比較對比分析,通過他們之間的不同理解三個算法的特點,從而能夠設計出符合三者的算法。首先呢,這三個算法都不是基於比較的算法,這和前面的那些算法不同,其核心內容不再通過比較然後交換兩者的位置了,而這次要說的是在排序的時候將其順序和“桶”的順序相關聯起來,通過不同的排序規則,進行排序,然後按照順序進行輸出,這些就是此三類算法的大致流程。
計數排序
解釋這一通是爲了有個大體印象,不至於在閱讀代碼的時候產生渾然不知。首先我們看一個較爲簡單的,叫做計數排序,這個排序可以很好的應用到整數排序,其主要的核心思想爲尋找出數組中最大和最小元素,然後開闢max-min大小的數組,然後對原數組進行遍歷,原數組某位置的元素值爲多少,則在新開闢的輔助數組中的對應位置(輔助數組下標爲原數組元素值)加1(可能有重複的數字),然後將其按照某一順序重新寫回原來的數組。來看下代碼是怎麼完成這個過程的:
public class CountSort {
public static void countSort(int[] array, int size){
int max_vaue = array[0];//序列中的最大值
int min_value = array[0];//序列中的最小值
//找到序列中的最大值和最小值
for(int i = 0; i < size; i++){
if(array[i] >= max_vaue) {
max_vaue = array[i];
}
if(array[i] <= min_value){
min_value = array[i];
}
}
//計算輔助數組的長度並開闢相應的空間
int range = max_vaue - min_value + 1;
int[] helpCount = new int [range];
//helpCount[array[i]-min_value]++; 將對應的數字放置
//即求得array[i]相對於min_value值的距離是多少
//到對應的位置
for(int i = 0; i < size; i++){
helpCount[array[i] - min_value] ++;
}
int index = 0;
//遍歷輔助空間
for(int i = 0; i < range; i++){
while (helpCount[i] -- > 0){//下標是幾則說明出現了幾次
//將下標處的數字拉回原來數組
array[index++] = i + min_value;
}
}
}
}
其實一點本質的東西就是將原數組的元素的值和help數組的下標值聯繫起來,因爲我們在進行比較排序的時候比較的也是元素的值,也即說明順序包含在原數組的值當中,所以我們改變一下策略,使得原數組元素值對因爲新數組的下標索引值。
好了,如果理解好了上面這個計數排序算法之後,再來一個桶排序吧,所謂的循序漸進嘛:
桶排序
說道桶排序,其核心的思想有點和散列表有些向,有原來的一排桶中現在變爲了多排桶,桶的第一排來接受經過計算後被分配到這一列桶,後續插入的元素則在這一咧往下排列,這麼說抽象哈,可以參考下面的圖和博客。
https://www.cnblogs.com/ECJTUACM-873284962/p/6935506.html
是不是很像定址法散列表的樣子?那該怎麼設計呢,參考下面的代碼:
public class BucketSort {
public void bucketSort(int[] array, int bucketSize){
//用於存放排序後的結果
int[] result = new int[array.length];
//開闢bucket的大小爲bucketSize
Node[] bucket = new Node[bucketSize];
//初始化每個節點
for(int i = 0 ; i < bucket.length; i++){
bucket[i] = new Node();
}
//核心部分,將各個元素按照剛纔所示的圖的形式進行分組放入
//桶中
for(int i = 0; i < array.length; i++){
int bucketIndex = hash(array[i]);
Node node = new Node(array[i]);
Node p = bucket[bucketIndex];
if(p.next == null){//無節點的情況
p.next = node;
}else{
//插入到連表中的情況,鏈表的插入操作還是比較快速的
while (p.next != null && p.next.val <= node.val){
p = p.next;
}
//更新鏈表節點
node.next = p.next;
p.next = node;
}
}
int j = 0;
//將桶中的結果放入到結果數組中
for(int i = 0; i < bucket.length; i++){
for (Node p = bucket[i].next; p != null; p = p.next){
result[j++] = p.val;
}
}
//將結果拷貝回原數組
for(int i = 0; i < array.length; i++){
array[i] = result[i];
}
}
//此哈希非彼哈希,這個主要是爲了能夠將元素“散列”到各個桶之中
public int hash(int val){
return val / 10;
}
//定義節點Node類型
class Node{
public Node(){
this(0);
}
public Node(int val){
this.val = val;
}
private int val;
private Node next;
}
}
基數排序
基數排序是一種來自於老式的穿卡機上的算法,一個卡片有80列,每一列可以在12個位置上穿孔,排序器可以被機器機械的“程序化”,其利用這樣的方式方便了老式機器對卡片進行檢查。對於我們通用的10進制數字來說,每一列中只用到10個位置,一個d位數字佔用d個列,因爲卡片器一次只能查看一個數列。直覺上可以利用高位數字進行判別,但是其是不然,因爲如果這樣排序的話,10個位置的9個必須放置到一邊,這個過程產生了較大的空間浪費,如果以低位進行,則會有效的解決這個問題。此時的基數排序則相當於對於數列先按照個位數進行排列,然後按照十位進行,然後不斷的進行到最高位(若沒有的話,用0代替)。對於此算法來說重要的問題就是排序算法一定要穩定。說了這麼多不如一個圖,也許你看下圖就明白了:
算法核心較爲簡單,但是如何設計這個算法呢,有這麼幾個地方需要注意:首先要注意排序的順序,是由低位到高位,其次還要注意算法的另一個問題,如何保存每次排序的結果,在一個就是,如何比較長度不同的數字,其次就是如何正確的將其放入到正確的桶的位置,可以參考一下代碼:
public class RadixSort {
/**
* 獲取array數組中的最大值
* @param array 待排序數組
* @return 返回數組中的最大值
*/
private static int getMaxValue(int[] array){
int max_value;
max_value = array[0];
for(int i = 1; i < array.length; i++){
if(array[i] > max_value){
max_value = array[i];
}
}
return max_value;
}
public static void radixSortCore(int[] array, int exp){
//開闢輔助數據,用於存放每次得到的排序結果
int[] output = new int [array.length];
//bucket用於存放每一位數字對應的在0-9的位置,其值
//爲每個數字的數量
int[] bucket = new int[10];
//按照每次指定了exp(此爲對於某一位進行取餘然後尋找位置)
for(int i = 0; i < array.length; i++){
bucket[(array[i] / exp) % 10]++;
}
//此過程可以更改bucket,使得更改後的bucket數組中的值
// 爲array數組中數據,在經過排序之後在output數組中的下標
//需要注意的是,需要結合下面兩個for循環過程
for(int i = 1; i < 10; i++){
bucket[i] += bucket[i - 1];
}
//例如array = 12,34,63,79,86,45;則bucket中的索引值從0-9所對
//對應的內容爲;0,0,1,1,1,1,1,0,0,1,經過上面的for循環後
//可以得到0,0,1,2,3,4,5,5,5,6 此時,
// [bucket[(array[array[i] / exp]) % 10] - 1,可以分析爲:若按照個位計算,
//(array[i] / exp) % 10 爲bucket數組的索引,若i = 5 時,可以得到
//(array[i] / exp) % 10 爲5,則bucket [(array[i] / exp) % 10]爲4
//則output的索引值(array[i] / exp) % 10] - 1爲3,即output的第四個位置
//而排序之後的結果爲:12,63,34,45,86,79。可以發現符合結果
for (int i = array.length - 1; i >= 0; i--){
output[bucket [(array[i] / exp) % 10] - 1 ] = array[i];
//當有重複數字的時候有效,即當此位置的數字個數-1
bucket[(array[i] / exp)% 10 ] --;
}
/**
* 移動到原來的數組
*/
for (int i = 0; i < array.length; i++){
array[i] = output[i];
}
output = null;
bucket = null;
}
public static void radix_sort(int[] array){
int max = getMaxValue(array);
for(int exp = 1; max / exp > 0; exp *= 10){
radixSortCore(array,exp);
}
}
}