本文對當前主流排序算法原理和具體java實現,主要包括冒泡排序,插入排序,選擇排序,希爾排序,堆排序,快速排序,歸併排序進行講解。算法功能基本測試通過,讀者若有更好的算法實現請留評論裏面,感激不盡。
一、冒泡排序(BubbleSortDemo),冒泡排序基本原理:打個簡單的比方就如同我們燒開水時的氣泡,大的氣泡一個一個的往上冒,即冒泡排序的每一輪排序都選出最大的數拍在最後或者最前。冒泡排序的時間複雜度爲O(n2)。
//parm score爲輸入排序數組,return排序後數組
public static int[] BubbleSortDemo(int [] score) {
for (int i = 0; i < score.length -1; i++){for(int j = 0 ;j < score.length - i - 1; j++){
if(score[j] >score[j + 1]){
int temp = score[j];
score[j] = score[j + 1];
score[j + 1] = temp;
}
}
}
return score;
}
二、選擇排序(selectionSort),選擇排序的基本原理:用白話講就是首先從一組數中選出一個最小的數或者最大的數,然後將這個最小的數或者最大的數與此輪排序第一個數進行交換。選擇排序的時間複雜度爲O(n*n)。
public static int[] selectionSort(int[] score){
for(int i = 0; i < score.length-1; ++i){
int k = i;
for(int j = i; j < score.length; ++j){
if(score[k] > score[j]){
k = j;
}
}
if(k != i){
int temp = score[i];
score[i] = score[k];
score[k] = temp;
}
}
return score;
}
三、插入排序 (insertSort),插入排序的基本原理:用白話講就是像我們打撲克牌,把從1到K的牌排好序,我們一般採用的方法時取出倆張牌排好大小,然後取第三張牌,插入這倆種中合適的位置,然後在取第四張牌插入,前三張已經排好序的位置,直到所有的牌都排好爲止。用比較正式的話來講就是把n個待排序的元素看成一個有序表和一個無序表,開始有序表只包含一個元素,無序表中包含n-1個元素,排序過程中每次從無序表中取出第一個元素,把它的排序碼依次與有序表元素的排序碼進行比較,將它插入到有序表中的適當位置,使之成爲新的有序表。
public static int[] insertSort(int score[])
{
for(int i=1;i<score.length;i++)
{
int insertVal = score[i];
// insertValue準備和前一個數比較
int index=i-1;
while(index>=0&&insertVal<score[index])
{
// 將把arr[index]向後移動
score[index+1]=score[index];
// 讓index向前移動一位
index--;
}
// 將insertValue插入到適當位置
score[index+1]=insertVal;
}
return score;
}
四、希爾排序(shellSort),在研究希爾排序之前一定要好好熟悉插入排序,有助於理解。希爾排序的基本原理:用白話來說,希爾排序實際上就是插入排序的一種改進算法,首先對數據進行分組,然後對分組的數據插入排序,最後對所有數據插入排序。本例中分割間隔長度爲 DataLength = DataLength / 2; 對半分。最好的時間複雜度爲與分割增量有很大的關係。
public static int [] ShellSort(int[] score) {
int i, j, k; // 循環計數變量
int Temp; // 暫存變量
boolean Change; // 數據是否改變
int DataLength; // 分割集合的間隔長度
int Pointer; // 進行處理的位置
int Index=score.length;
DataLength = (int) Index / 2; // 初始集合間隔長度
while (DataLength != 0) // 數列仍可進行分割
{
// 對各個集合進行處理
for (j = DataLength; j < Index; j++) {
Change = false;
Temp = score[j]; // 暫存Data[j]的值,待交換值時用
Pointer = j - DataLength; // 計算進行處理的位置
// 進行集合內數值的比較與交換值
while (Temp < score[Pointer] && Pointer >= 0 && Pointer <= Index) {
score[Pointer + DataLength] = score[Pointer];
// 計算下一個欲進行處理的位置
Pointer = Pointer - DataLength;
Change = true;
if (Pointer < 0 || Pointer > Index)
break;
}
// 與最後的數值交換
score[Pointer + DataLength] = Temp;
}
DataLength = DataLength / 2; // 計算下次分割的間隔長度
}
return score;
}
五、快速排序(Quik Sort),快速排序基本原理:快速排序實際上就是採用遞歸分組方法,選出一個排序數中的數作爲參考節點,小於參考節點的數放在左邊,大於節點的數放在右邊。採用遞歸一直到整個左右排序都執行完。
public
static void swap1(int a[], int i, int j) { // 通過臨時變量,交換數據
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
} // 第一次交換分析
public static void quicksort(int a[], int low, int high) { // 假設傳入low=0; high=a.length-1;
if (low < high) { // 條件判斷
int pivot, p_pos, i; // 聲明變量
p_pos = low; // p_pos指向low,即位索引爲0位置 ;
pivot = a[p_pos]; // 將0位置上的數值賦給pivot;
for (i = low + 1; i <= high; i++) { // 循環次數, i=1;
if (a[i]<pivot) { // 1位置的數與0位置數作比較: a[1]>a[0]
p_pos++; // 2位與1位比較,3位與2位比較......
swap(a, p_pos, i); // 傳參並調用swap
}
}
swap1(a, low, p_pos); // 將p_pos設爲high再次調用swap
quicksort(a, low, p_pos - 1); // 遞歸調用,排序左半區
quicksort(a, p_pos + 1, high); // 遞歸調用,排序右半區
}
}
六、堆排序(Heap Sort),堆排序基本原理:堆排序採用二叉樹的基本原理進行排序,java中可用數組表示二叉樹。堆排序過程主要有倆部,第一部就是構建堆;第二部就是講堆根節點與尾節點交換。
public
static int[] heapSort(int[] data){
int arrayLength=data.length;
//循環建堆
for(int i=0;i<arrayLength-1;i++){
//建堆
buildMaxHeap(data,arrayLength-1-i);
//交換堆頂和最後一個元素
swap(data,0,arrayLength-1-i);
}
return data;
}
private static void swap(int[] data, int i, int j) {
// TODO Auto-generated method stub
int tmp=data[i];
data[i]=data[j];
data[j]=tmp;
}
//對data數組從0到lastIndex建大頂堆
private static void buildMaxHeap(int[] data, int lastIndex) {
// TODO Auto-generated method stub
//從lastIndex處節點(最後一個節點)的父節點開始
for(int i=(lastIndex-1)/2;i>=0;i--){
//k保存正在判斷的節點
int k=i;
//如果當前k節點的子節點存在
while(k*2+1<=lastIndex){
//k節點的左子節點的索引
int biggerIndex=2*k+1;
//如果biggerIndex小於lastIndex,即biggerIndex+1代表的k節點的右子節點存在
if(biggerIndex<lastIndex){
//若果右子節點的值較大
if(data[biggerIndex]<data[biggerIndex+1]){
//biggerIndex總是記錄較大子節點的索引
biggerIndex++;
}
}
//如果k節點的值小於其較大的子節點的值
if(data[k]<data[biggerIndex]){
//交換他們
swap(data,k,biggerIndex);
//將biggerIndex賦予k,開始while循環的下一次循環,重新保證k節點的值大於其左右子節點的值
k=biggerIndex;
}else{
break;
}
}
}
}
七、歸併排序(Merge Sort),歸併排序基本原來:歸併排序採用遞歸算法,分治策略等方法排序。基本流程爲分割,在子分割,子合併,大合併。整個流程就是分割和合並,採用遞歸算法實現。
public static void mergeSort(int[] data) {
sort(data, 0, data.length - 1);
}
public static void sort(int[] data, int left, int right) {
if (left >= right)
return;
// 找出中間索引
int center = (left + right) / 2;
// 對左邊數組進行遞歸
sort(data, left, center);
// 對右邊數組進行遞歸
sort(data, center + 1, right);
// 合併
merge(data, left, center, right);
}
public static void merge(int[] data, int left, int center, int right) {
// 臨時數組
int[] tmpArr = new int[data.length];
// 右數組第一個元素索引
int mid = center + 1;
// third 記錄臨時數組的索引
int third = left;
// 緩存左數組第一個元素的索引
int tmp = left;
while (left <= center && mid <= right) {
// 從兩個數組中取出最小的放入臨時數組
if (data[left] <= data[mid]) {
tmpArr[third++] = data[left++];
} else {
tmpArr[third++] = data[mid++];
}
}
// 剩餘部分依次放入臨時數組(實際上兩個while只會執行其中一個)
while (mid <= right) {
tmpArr[third++] = data[mid++];
}
while (left <= center) {
tmpArr[third++] = data[left++];
}
// 將臨時數組中的內容拷貝回原數組中
// (原left-right範圍的內容被複制回原數組)
while (tmp <= right) {
data[tmp] = tmpArr[tmp++];
}
}
八、基數排序,基數排序的基本原理:基數排序就是從對數字的個位,十位,百位等待,進行裝桶。具體過程先將個位裝桶,然後組合,在講十位裝桶,然後組合,以此類推,知道組合完了,排序也就完了。
//基於計數排序的基數排序算法
private static void radixSort(int[] array,int radix, int distance) {
//array爲待排序數組
//radix,代表基數
//代表排序元素的位數
int length = array.length;
int[] temp = new int[length];//用於暫存元素
int[] count = new int[radix];//用於計數排序
int divide = 1;
for (int i = 0; i < distance; i++) {
System.arraycopy(array, 0,temp, 0, length);
Arrays.fill(count, 0);
for (int j = 0; j < length; j++) {
int tempKey = (temp[j]/divide)%radix;
count[tempKey]++;
}
for (int j = 1; j < radix; j++) {
count [j] = count[j] + count[j-1];
}
//個人覺的運用計數排序實現計數排序的重點在下面這個方法
for (int j = length - 1; j >= 0; j--) {
int tempKey = (temp[j]/divide)%radix;
count[tempKey]--;
array[count[tempKey]] = temp[j];
}
divide = divide * radix;
}
}