資料結構-排序演算法
排序的定義
排序
排序是計算機程式設計中的一種重要操作,它的功能是將一個數據元素的任意序列,重新排列成一個按關鍵字有序的序列。
排序分為內部排序和外部排序
內部排序 指的是待排序記錄存放在計算機儲存器中進行的排序過程
外部排序 指的是待排序記錄的數量很大,以致記憶體一次不能容納全部記錄,在排序過程中尚需對外存進 行訪問的排序過程。
下面只介紹內部排序:
分類
- 插入排序:直接插入排序、二分法插入排序、希爾排序。
- 選擇排序:簡單選擇排序、堆排序。
- 交換排序:氣泡排序、快速排序。
- 歸併排序
- 基數排序
對比圖:
各種排序效能
O(n^2):直接插入排序,簡單選擇排序,氣泡排序。
在資料規模較小時(9W內),直接插入排序,簡單選擇排序差不多。當資料較大時,氣泡排序演算法的時間代價最高。效能為O(n^2)的演算法基本上是相鄰元素進行比較,基本上都是穩定的。
O(nlogn):快速排序,歸併排序,希爾排序,堆排序。
排序演算法選擇
1.資料規模較小
(1)待排序列基本有序的情況下,可以選擇直接插入排序;
(2)對穩定性不作要求宜用簡單選擇排序,對穩定性有要求宜用插入或冒泡
2.資料規模不是很大
(1)完全可以用記憶體空間,序列雜亂無序,對穩定性沒有要求,快速排序,此時要付出log(N)的額外空間。
(2)序列本身可能有序,對穩定性有要求,空間允許下,宜用歸併排序
3.資料規模很大
(1)對穩定性有求,則可考慮歸併排序。
(2)對穩定性沒要求,宜用堆排序。
4.序列初始基本有序(正序),宜用直接插入,冒泡
1、插入排序:
直接插入排序(Straight Insertion Sorting)的基本思想:在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第
public class InsertSort {
public static int[] sort(int[] a) {
//從陣列第二個元素開始排序
for (int i = 1; i < a.length; i++) {
int temp = a[i];//快取待排資料
int j = i - 1; //從右向左在有序區a[0...i-1]中找a[i]的插入位置
//將大於temp的資料後移
while (j >= 0 && temp < a[j]) {
a[j + 1] = a[j--];
}
//在j+1處插入待排資料
a[j + 1] = temp;
}
return a;
}
public static void main(String[] args) {
int[] a={2,3,4,5,1,7,9,10};
InsertSort.sort(a);
for(int row:a){
System.out.println(row);
}
}
}
結果:
1
2
3
4
5
7
9
10
Process finished with exit code 0
2、希爾排序
針對直接插入排序低下效率問題,有人對次進行了改進與升級,這就是現在的希爾排序。希爾排序,也稱遞減增量排序演算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序演算法。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
- 插入排序在對幾乎已經排好序的資料操作時, 效率高, 即可以達到線性排序的效率
- 但插入排序一般來說是低效的, 因為插入排序每次只能將資料移動一位
public class SheelSort {
public static void sort(int [] a){
int len=a.length;//單獨把陣列長度拿出來,提高效率
while(len!=0){
len=len/2;
for(int i=0;i<len;i++){//分組
for(int j=i+len;j<a.length;j+=len){//元素從第二個開始
int k=j-len;//k為有序序列最後一位的位數
int temp=a[j];//要插入的元素
/*for(;k>=0&&temp<a[k];k-=len){
a[k+len]=a[k];
}*/
while(k>=0&&temp<a[k]){//從後往前遍歷
a[k+len]=a[k];
k-=len;//向後移動len位
}
a[k+len]=temp;
}
}
}
}
public static void main(String[] args) {
int[] a={2,3,4,5,1,7,9,10};
SheelSort.sort(a);
for(int row:a){
System.out.println(row);
}
}
}
結果:
1
2
3
4
5
7
9
10
Process finished with exit code 0
3、簡單選擇排序
常用於取序列中最大最小的幾個數時。
(如果每次比較都交換,那麼就是交換排序;如果每次比較完一個迴圈再交換,就是簡單選擇排序。)
遍歷整個序列,將最小的數放在最前面。
遍歷剩下的序列,將最小的數放在最前面。
重複第二步,直到只剩下一個數。
public class SelectSort {
public static void sort(int[]a){
int len=a.length;
for(int i=0;i<len;i++){//迴圈次數
int value=a[i];
int position=i;
for(int j=i+1;j<len;j++){//找到最小的值和位置
if(a[j]<value){
value=a[j];
position=j;
}
}
a[position]=a[i];//進行交換
a[i]=value;
}
}
public static void main(String[] args) {
int[] a={2,3,4,5,1,7,9,10};
SelectSort.sort(a);
for(int row:a){
System.out.println(row);
}
}
}
結果:
1
2
3
4
5
7
9
10
Process finished with exit code 0
4、堆排序
對簡單選擇排序的優化。
將序列構建成大頂堆。
將根節點與最後一個節點交換,然後斷開最後一個節點。
重複第一、二步,直到所有節點斷開。
public class HeapSort {
public static void sort(int[] a){
int len=a.length;
//迴圈建堆
for(int i=0;i<len-1;i++){
//建堆
buildMaxHeap(a,len-1-i);
//交換堆頂和最後一個元素
swap(a,0,len-1-i);
}
}
//交換方法
private static void swap(int[] data, int i, int j) {
int tmp=data[i];
data[i]=data[j];
data[j]=tmp;
}
//對data陣列從0到lastIndex建大頂堆
private static void buildMaxHeap(int[] data, int lastIndex) {
//從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;
}
}
}
}
public static void main(String[] args) {
int[] a={2,3,4,5,1,7,9,10};
HeapSort.sort(a);
for(int row:a){
System.out.println(row);
}
}
}
結果:
1
2
3
4
5
7
9
10
Process finished with exit code 0
5、氣泡排序
很簡單,用到的很少,據瞭解,面試的時候問的比較多!
將序列中所有元素兩兩比較,將最大的放在最後面。
將剩餘序列中所有元素兩兩比較,將最大的放在最後面。
重複第二步,直到只剩下一個數。
程式碼實現:
設定迴圈次數。
設定開始比較的位數,和結束的位數。
兩兩比較,將最小的放到前面去。
重複2、3步,直到迴圈次數完畢。
public class BubbleSort {
public static void sort(int []a){
int len=a.length;
for(int i=0;i<len;i++){
for(int j=0;j<len-i-1;j++){//注意第二重迴圈的條件
if(a[j]>a[j+1]){
int temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
public static void main(String[] args) {
int[] a={2,3,4,5,1,7,9,10};
BubbleSort.sort(a);
for(int row:a){
System.out.println(row);
}
}
}
結果:
1
2
3
4
5
7
9
10
Process finished with exit code 0
6、快速排序
要求時間最快時。
選擇第一個數為p,小於p的數放在左邊,大於p的數放在右邊。
遞迴的將p左邊和右邊的數都按照第一步進行,直到不能遞迴。
public class QuickSort {
public static void sort(int[]a,int start,int end){
if(start<end){
int baseNum=a[start];//選基準值
int midNum;//記錄中間值
int i=start;
int j=end;
do{
while((a[i]<baseNum)&&i<end){
i++;
}
while((a[j]>baseNum)&&j>start){
j--;
}
if(i<=j){
midNum=a[i];
a[i]=a[j];
a[j]=midNum;
i++;
j--;
}
}while(i<=j);
if(start<j){
sort(a,start,j);
}
if(end>i){
sort(a,i,end);
}
}
}
public static void main(String[] args) {
int[] a={2,3,4,5,1,7,9,10};
QuickSort.sort(a,0,a.length-1);
for(int row:a){
System.out.println(row);
}
}
}
結果:
1
2
3
4
5
7
9
10
Process finished with exit code 0
7、歸併排序
速度僅次於快速排序,記憶體少的時候使用,可以進行平行計算的時候使用。
選擇相鄰兩個陣列成一個有序序列。
選擇相鄰的兩個有序序列組成一個有序序列。
重複第二步,直到全部組成一個有序序列。
public class MergeSort {
public static int[] sort(int[] a,int low,int high){
int mid = (low+high)/2;
if(low<high){
sort(a,low,mid);
sort(a,mid+1,high);
//左右歸併
merge(a,low,mid,high);
}
return a;
}
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high-low+1];
int i= low;
int j = mid+1;
int k=0;
// 把較小的數先移到新陣列中
while(i<=mid && j<=high){
if(a[i]<a[j]){
temp[k++] = a[i++];
}else{
temp[k++] = a[j++];
}
}
// 把左邊剩餘的數移入陣列
while(i<=mid){
temp[k++] = a[i++];
}
// 把右邊邊剩餘的數移入陣列
while(j<=high){
temp[k++] = a[j++];
}
// 把新陣列中的數覆蓋nums陣列
for(int x=0;x<temp.length;x++){
a[x+low] = temp[x];
}
}
public static void main(String[] args) {
int[] a={2,3,4,5,1,7,9,10};
MergeSort.sort(a,0,a.length-1);
for(int row:a){
System.out.println(row);
}
}
}
結果:
1
2
3
4
5
7
9
10
Process finished with exit code 0