Java零基礎的開始:八大排序(面試答題!)
八大排序(重點!!)
排序演算法大體可分為兩種:
1、比較排序,時間複雜度O(nlogn) ~ O(n^2),主要有:氣泡排序,選擇排序,插入排序,歸併排序,堆排序,快速排序等。
2、非比較排序,時間複雜度可以達到O(n),主要有:計數排序,基數排序,桶排序等。
一、氣泡排序
演算法思路
1、比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
2、對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
3、針對所有的元素重複以上的步驟,除了最後一個;
4、重複步驟1~3,直到排序完成。 :
``java
/** * 氣泡排序的規則 * 1、從第一個數開始,將這個數 與它相鄰的數比較,如果這個數大於相鄰的數 * 則兩個數交換位置 * 2、依次從第二個數開始比較,與相鄰的數比較 * 3、以此類推 到倒數第二數截至 * 4、重複以上步驟 */ public static void main(String[] args) { int[] array = {5, 4, 3, 2, 1}; //用於臨時交換的變數 int temp = 0; for (int j = 0; j < array.length - 1; j++) { for (int i = 0; i < array.length - j - 1; i++) { //相鄰的數比較 if (array[i] > array[i + 1]) { temp = array[i]; array[i] = array[i + 1]; array[i + 1] = temp; } } System.out.println("比較一輪之後:" + Arrays.toString(array)); } }
二、選擇排序
演算法思路:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
/** * 選擇排序: 從一堆數中選擇一個最小數 放在第一個位置,再從一堆數中選擇一個 * 最小數放在第二個位置, 依次 將一堆數的最小數按順序排放。 * 步驟: 1、假設第一個數是最小數,需要定義最小數的下標minIndex=0 * 將這個數與後面的每一個數比較,找到最小數的下標即可 * 2、將第一個數與最小數的下標交換 ,得出最小數在第一位。 * 3、 依次類推, 將已比較的數 忽略,繼續從生下的元素中找足最小數,放入已比較的數的下一位 * 直到整個數列比較結束 * @param args */ public static void main(String[] args) { int [] array = {3,2,1,5,7,4}; for(int j=0;j<array.length-1;j++) { // 假設第一個數是最小數 int minIndex = j; // 為什麼i =j+1 因為初始值要略過已比較的下標 for (int i = 1+j; i < array.length; i++) { if (array[minIndex] > array[i]) { minIndex = i; } } // 將這個最小數放在 第一位 int temp = 0; temp = array[j]; array[j] = array[minIndex]; array[minIndex] = temp; System.out.println("----第一次完成後:" + Arrays.toString(array)); } System.out.println("最後的排序:"+Arrays.toString(array)); } }
三、插入排序
演算法思路:
1、從第一個元素開始,該元素可以認為已經被排序;
2、取出下一個元素,在已經排序的元素序列中從後向前掃描;
3、如果該元素(已排序)大於新元素,將該元素移到下一位置;
4、重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
5、將新元素插入到該位置後;
6、重複步驟2~5。
/** * 插入排序 * 1、從第一個元素開始,假設第一個元素是已排好序的 * 2、從下一個元素開始,依次比較它前面的所有元素(從後向前掃描) * 3、 如果這個元素 小於它前面的元素 則兩兩交換 , * 如果這個元素 大於它前面的元素,則不交換 * 4、依次重複2,3步驟 ,直到將所有數 比較完成 * 5,4,3,2,1 * * 4 5 3 2 1 i從1開始 * * 4 3 5 2 1 i從2開始 * 3 4 5 2 1 * * 3 4 2 5 1 i從3開始 * 3 2 4 5 1 * 2 3 4 5 1 * * 2 3 4 1 5 i從4開始 * 2 3 1 4 5 * 2 1 3 4 5 * 1 2 3 4 5 * @param args */ public static void main(String[] args) { int [] array = {5,4,3,2,1}; // 外層迴圈迴圈 每一個數的比較次數 for(int j=0;j<array.length-1;j++) { int temp = 0; for (int i = 1+j; i > 0; i--) { if (array[i] < array[i - 1]) { temp = array[i]; array[i] = array[i - 1]; array[i - 1] = temp; } } System.out.println("每一次完成後的結果:"+ Arrays.toString(array)); } System.out.println("最後一次完成後的結果:"+Arrays.toString(array)); }
四、歸併排序
演算法思路:
該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為2-路歸併。
1、把長度為n的輸入序列分成兩個長度為n/2的子序列;
2、對這兩個子序列分別採用歸併排序;
3、將兩個排序好的子序列合併成一個最終的排序序列。
/*
k表示最終i和j比較之後最終需要放的位置
i和j用來表示當前需要考慮的元素
left表示最左邊的元素
right表示最右邊的元素
middle表示中間位置元素,放在第一個已經排好序的陣列的最後一個位置
*/
public class Merging {
/*******************測試************************/
public static void main(String[] args) {
int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 , 9 , 19 ,12,16,14,12,22,33 };
mergeSort(nums , 0 , nums.length - 1 );
System.out.println(Arrays.toString(nums));
}
/********************演算法************************/
/*
arr:要處理的陣列
l:開始位置
r:結束位置
遞迴對arr[ l ... r ]範圍的元素進行排序
*/
private static void mergeSort(int[] arr,int left,int right){
if( right - left <= 10 ){ //當資料很少的時候使用插入排序演算法
ChaRuPaiXu.ChaRuPaiXuFa2( arr , left ,right);
return;
}
int middle = ( left + right ) / 2; //計算中點位置
mergeSort( arr , left , middle ); //不斷地對陣列的左半邊進行對邊分
mergeSort( arr , middle+1 , right ); //不斷地對陣列的右半邊進行對半分
if( arr[middle] > arr[middle+1] )//當左邊最大的元素都比右邊最小的元素還小的時候就不用歸併了
merge( arr , left , middle , right ); //最後將已經分好的陣列進行歸併
}
//將arr[ l... mid ]和arr[ mid ... r ]兩部分進行歸併
/*
|2, 7, 8, 3, 1 | 6, 9, 0, 5, 4|
*/
private static void merge(int[] arr, int left, int mid, int right) {
int arr1[] = new int[ right - left + 1 ]; //定義臨時陣列
for( int i = left ; i <= right ; i++ ) //將陣列的元素全部複製到新建的臨時陣列中
arr1[ i - left ] = arr[ i ];
int i = left;
int j = mid + 1; //定義兩個索引
for( int k = left;k <= right ; k++){
if( i > mid ) //如果左邊都比較完了
{
arr[ k ] = arr1[ j - left ]; //直接將右邊的元素都放進去
j++;
}
else if( j > right ){ //右邊都比較完了
arr[ k ] = arr1 [i - left ]; //直接將左邊的元素放進去
i++;
}
else if( arr1[ i-left ] < arr1[ j-left ] ){
arr[ k ] = arr1[ i - left];
i++;
}
else
{
arr[ k ] = arr1[ j - left];
j++;
}
}
}
}
五、快速排序
演算法思路:
通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
快速排序使用分治法來把一個串(list)分為兩個子串(sub-lists)。
1、從數列中挑出一個元素,稱為 “基準”(pivot);
2、重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分割槽退出之後,該基準就處於數列的中間位置。這個稱為分割槽(partition)操作;
3、遞迴地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
public class Quick {
public static void main(String[] args){
int array[] = {1,2,4,3,9,7,8,6};
quickSort(array,0,array.length-1);
for( int i = 0 ; i < array.length ; i++ ){
System.out.print(array[i]+" ");
}
}
private static void quickSort(int[] arr,int l,int r){
if( l >= r ) return;
int p = partition(arr,l,r); //找到中間位置
quickSort(arr,l,p-1);
quickSort(arr,p+1,r);
}
private static int partition(int[] arr,int l,int r){
int v = arr[l]; //取出第一個元素
int j = l; //j表示小於第一個元素和大於第一個元素的分界點
for( int i = l + 1;i <= r;i++ ){
//將所有小於第一個元素的值的元素全部都放到它的左邊
if( arr[i] < v ){ //如果當前元素小於v,則交換
swap(arr,i,j+1);
j++;
}
}
swap(arr,l,j); //將第一個元素和中間的元素進行交換
return j;
}
}
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基準位
temp = arr[low];
while (i<j) {
//先看右邊,依次往左遞減
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左邊,依次往右遞增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果滿足條件則交換
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最後將基準為與i和j相等位置的數字交換
arr[low] = arr[i];
arr[i] = temp;
//遞迴呼叫左半陣列
quickSort(arr, low, j-1);
//遞迴呼叫右半陣列
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
六、堆排序
演算法思路:
堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
最大堆要求節點的元素都要不小於其孩子,最小堆要求節點元素都不大於其左右孩子
那麼處於最大堆的根節點的元素一定是這個堆中的最大值.
public class Heap {
public static void main(String[] args) {
int A[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};
HeapSort(A, A.length);
System.out.println(Arrays.toString(A));
}
public static void Swap(int A[], int i, int j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
public static void Heapify(int A[], int i, int size) // 從A[i]向下進行堆調整
{
int left_child = 2 * i + 1; // 左孩子索引
int right_child = 2 * i + 2; // 右孩子索引
int max = i; // 選出當前結點與其左右孩子三者之中的最大值
if (left_child < size && A[left_child] > A[max])
max = left_child;
if (right_child < size && A[right_child] > A[max])
max = right_child;
if (max != i)
{
Swap(A, i, max); // 把當前結點和它的最大(直接)子節點進行交換
Heapify(A, max, size); // 遞迴呼叫,繼續從當前結點向下進行堆調整
}
}
public static int BuildHeap(int A[], int n) // 建堆,時間複雜度O(n)
{
int heap_size = n;
for (int i = heap_size / 2 - 1; i >= 0; i--) // 從每一個非葉結點開始向下進行堆調整
Heapify(A, i, heap_size);
return heap_size;
}
public static void HeapSort(int A[], int n)
{
int heap_size = BuildHeap(A, n); // 建立一個最大堆
while (heap_size > 1) // 堆(無序區)元素個數大於1,未完成排序
{
// 將堆頂元素與堆的最後一個元素互換,並從堆中去掉最後一個元素
// 此處交換操作很有可能把後面元素的穩定性打亂,所以堆排序是不穩定的排序演算法
Swap(A, 0, --heap_size);
Heapify(A, 0, heap_size); // 從新的堆頂元素開始向下進行堆調整,時間複雜度O(logn)
}
}
}
七、希爾排序
演算法思路:
1、選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2、按增量序列個數k,對序列進行k 趟排序;
3、每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度
常用的h序列由Knuth提出,該序列從1開始,通過如下公式產生:
h = 3 * h +1
反過來程式需要反向計算h序列,應該使用
h = ( h - 1 ) / 3
public class Shell {
public static void main(String[] args) {
int array[] = {1,2,4,3,9,7,8,6};
int h = 0;
int length = array.length;
while( h <= length ){ //計算首次步長
h = 3 * h + 1;
}
while( h >= 1 ){
for( int i = h;i < length;i++ ){
int j = i - h; //左邊的一個元素
int get = array[i]; //當前元素
while( j >= 0 && array[j] > get ){ //左邊的比當前大,則左邊的往右邊挪動
array[j+h] = array[j];
j = j - h;
}
array[j + h] = get; //挪動完了之後把當前元素放進去
}
h = ( h - 1 ) / 3;
}
for( int i = 0 ; i < array.length ; i++ ){
System.out.print(array[i]+" ");
}
}
}