归并排序改良 java_Java 八种排序算法总结
image
前言
好久沒復習基礎了,寫個冒泡排序都要想一會。感覺自己好像老了好多,今天手癢總結一下排序算法。目前網上博客普遍都有詳細介紹,寫的很清楚。說實話我是沒必要再寫一遍的,感覺就是在啰嗦、還是重復性的,但是如果只是單純看的話,不到3分鐘我就忘記了(可能是健忘癥晚期)。所以還是自己親手“教訓”一下印象比較深刻。
一、簡介
排序(Sorting) 是計算機程序設計中的一種重要操作,它的功能是將一個數據元素(或記錄)的任意序列,重新排列成一個關鍵字有序的序列。
排序算法在很多領域得到相當地重視,尤其是在大量數據的處理方面。一個優秀的算法可以節省大量的資源。在各個領域中考慮到數據的各種限制和規范,要得到一個符合實際的優秀算法,得經過大量的推理和分析。
那么怎么對比哪個排序算法更好呢? 這時候就是要看誰運行的比較快。 哈哈 真是一句廢話,誰不知道呢
timg (1).gif
所以我們需要了解一下
二、算法復雜度
時間復雜度是指執行算法所需要的計算工作量;而空間復雜度是指執行這個算法所需要的內存空間
(1)時間復雜度
時間復雜度可以認為是對排序數據的總的操作次數。反映當n變化時,操作次數呈現什么規律。
常見的時間復雜度有:常數階O(1),對數階O(log2n),線性階O(n), 線性對數階O(nlog2n),平方階O(n2)。
時間復雜度O(1):算法中語句執行次數為一個常數,則時間復雜度為O(1)。
(2)空間復雜度
空間復雜度是指算法在計算機內執行時所需存儲空間的度量,它也是問題規模n的函數。
空間復雜度O(1):當一個算法的空間復雜度為一個常量,即不隨被處理數據量n的大小而改變時,可表示為O(1)。
空間復雜度O(log2N):當一個算法的空間復雜度與以2為底的n的對數成正比時,可表示為O(log2n) , ax=N,則x=logaN。
空間復雜度O(n):當一個算法的空間復雜度與n成線性比例關系時,可表示為0(n)。
(3)排序算法穩定性
假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱為不穩定的。
穩定性的意義
1、如果只是簡單的進行數字的排序,那么穩定性將毫無意義。
2、如果排序的內容僅僅是一個復雜對象的某一個數字屬性,那么穩定性依舊將毫無意義
3、如果要排序的內容是一個復雜對象的多個數字屬性,但是其原本的初始順序毫無意義,那么穩定性依舊將毫無意義。
4、除非要排序的內容是一個復雜對象的多個數字屬性,且其原本的初始順序存在意義,那么我們需要在二次排序的基礎上保持原有排序的意義,才需要使用到穩定性的算法,例如要排序的內容是一組原本按照價格高低排序的對象,如今需要按照銷量高低排序,使用穩定性算法,可以使得想同銷量的對象依舊保持著價格高低的排序展現,只有銷量不同的才會重新排序。
image
太復雜了,我也只了解了點皮毛。就簡單介紹這些,要詳細了解的同學們還是自己看書吧!!!
download.jpg
三、常見算法
(1)冒泡排序
1、簡介:
重復地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果他們的順序(如從大到小、首字母從A到Z)錯誤就把他們交換過來
2、步驟:
比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最后一對。在這一點,最后的元素應該會是最大的數。
針對所有的元素重復以上的步驟,除了最后一個。
持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較
3、動圖:
image
4、代碼:
public static void order1(int[] a) {
for(int x=0;x
for (int y = 0; y < a.length-1-x; y++) {
if(a[y]>a[y+1]) {
int t = a[y];
a[y] = a[y+1];
a[y+1] = t;
}
}
}
}
image.gif
(2)選擇排序
1、簡介:
每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然后,再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。以此類推,直到全部待排序的數據元素排完。 選擇排序是不穩定的排序方法
2、步驟:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。
重復第二步,直到所有元素均排序完畢。
3、動圖:
image
4、代碼:
public static void order2(int[] a) {
for (int i = 0; i < a.length-1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[i] > a[j]) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
}
image.gif
(3)插入排序
1、簡介:
插入算法把要排序的數組分成兩部分:第一部分包含了這個數組的所有元素,但將最后一個元素除外(讓數組多一個空間才有插入的位置),而第二部分就只包含這一個元素(即待插入元素)。在第一部分排序完成后,再將這個最后元素插入到已排好序的第一部分中。
2、步驟:
從第一個元素開始,該元素可以認為已經被排序
取出下一個元素,在已經排序的元素序列中從后向前掃描
如果該元素(已排序)大于新元素,將該元素移到下一位置
重復步驟3,直到找到已排序的元素小于或者等于新元素的位置
將新元素插入到下一位置中
重復步驟2~5
3、動圖:
image
4、代碼:
public static void order3(int[] a) {
for (int i = 1; i < a.length; i++) {
int get = a[i];
int j = i-1;
while (j >= 0 && a[j] > get) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = get;
}
}
(4)歸并排序
1、簡介:
將已有序的子序列合并,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合并成一個有序表,稱為二路歸并。
2、步驟:
把長度為n的輸入序列分成兩個長度為n/2的子序列。
對這兩個子序列分別采用歸并排序。
將兩個排序好的子序列合并成一個最終的排序序列。
3、動圖:
4、代碼:
/**
* 歸并排序
*/
public static void order4(int[] a) {
sort(a,0,a.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++];
}
//將臨時數組中的內容拷貝回原數組中
while (tmp <= right) {
data[tmp] = tmpArr[tmp++];
}
}
(5)快速排序
1、簡介:
在數組中隨機選一個數(默認數組首個元素),數組中小于等于此數的放在左邊,大于此數的放在右邊,再對數組兩邊遞歸調用快速排序,重復這個過程。
2、步驟:
先從數列中取出一個數作為key值;
將比這個數小的數全部放在它的左邊,大于或等于它的數全部放在它的右邊;
對左右兩個小數列重復第二步,直至各區間只有1個數。
3、動圖:
image
4、代碼:
/**
* 快速排序
*/
public static void order5(int[] a) {
quickSort(a,0,a.length-1);
}
private static void quickSort(int[] a, int left, int right) {
if(left < right) {
int i = getMiddle(a,left,right);
quickSort(a, left, i - 1);
quickSort(a, i + 1,right);
}
}
private static int getMiddle(int[] a, int low, int high) {
int pivot = a[low];
int i = low;
int j = high;
while(i < j) {
while(pivot <= a[j] && i < j) j--;
while(pivot >= a[i] && i < j) i++;
if(i < j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
a[low] = a[i];
a[i] = pivot;
return i;
}
(6)希爾排序
1、簡介:
希爾排序是插入排序改良的算法,希爾排序步長從大到小調整,第一次循環后面元素逐個和前面元素按間隔步長進行比較并交換,直至步長為1,步長選擇是關鍵。
2、步驟:
先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,具體算法描述:
選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列個數k,對序列進行k 趟排序;
每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。
3、動圖:
image
4、代碼:
/**
* 希爾排序
*/
public static void order6(int[] a) {
int len = a.length;//單獨把數組長度拿出來,提高效率。
while(len != 0) {
len = len/2;
for (int i = 0; i < len; i++) {//分組
for (int j = i + 1; j < a.length; j+=len) {//元素從第二個開始
int k = j - len;//k為有序序列最后一位的位數
int temp = a[j];//要插入的元素
while (k >= 0 && temp < a[k]) {//從后往前遍歷
a[k + len] = a[k];
k -= len;//向后移動len位
}
a[k + len] = temp;
}
}
}
}
(7)基數排序
1、簡介:
基數排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最后的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。
2、步驟:
取得數組中的最大數,并取得位數;
arr為原始數組,從最低位開始取每個位組成radix數組;
對radix進行計數排序(利用計數排序適用于小范圍數的特點);
3、動圖:
image
4、代碼:
/**
* 基數排序
*/
public static void order7(int[] a) {
int max = a[0];
for (int i = 1; i < a.length; i++) {
if (a[i] > max) {
max = a[i];
}
}
int time = 0;
while (max > 0) {
max /= 10;
time++;
}
List> queue = new ArrayList>();
for (int i = 0; i < 10; i++) {
ArrayList queue1 = new ArrayList();
queue.add(queue1);
}
for (int i = 0; i < time; i++) {
for (int j = 0; j < a.length; j++) {
int x = a[j] % (int) Math.pow(10, i+1)/(int)Math.pow(10, i);
ArrayList queue2 = queue.get(x);
queue2.add(a[j]);
queue.set(x, queue2);
}
int count = 0;
for (int k = 0; k < 10; k++) {
while (queue.get(k).size()>0) {
ArrayList queue3 = queue.get(k);
a[count] = queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
(8)堆排序
1、簡介:
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,并同時滿足堆積的性質:即子結點的鍵值或索引總是小于(或者大于)它的父節點。
2、步驟:
將初始待排序關鍵字序列(R1,R2….Rn)構建成大頂堆,此堆為初始的無序區;
將堆頂元素R[1]與最后一個元素R[n]交換,此時得到新的無序區(R1,R2,……Rn-1)和新的有序區(Rn),且滿足R[1,2…n-1]<=R[n];
由于交換后新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,……Rn-1)調整為新堆,然后再次將R[1]與無序區最后一個元素交換,得到新的無序區(R1,R2….Rn-2)和新的有序區(Rn-1,Rn)。不斷重復此過程直到有序區的元素個數為n-1,則整個排序過程完成。
3、動圖:
image
4、代碼:
/**
* 堆排序
*/
public static void order8(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 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++;
}
}
//如果K節點的值小于其較大的子節點的值
if(data[k] < data[biggerIndex]) {
//交換他們
swap(data, k, biggerIndex);
k = biggerIndex;
} else {
break;
}
}
}
}
private static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
四、總結
場景應用:
(1)若n較小(如n≤50),可采用直接插入或直接選擇排序。
當記錄規模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數少于直接插人,應選直接選擇排序為宜。
(2)若文件初始狀態基本有序(指正序),則應選用直接插人、冒泡或隨機的快速排序為宜;
(3)若n較大,則應采用時間復雜度為O(nlgn)的排序方法:快速排序、堆排序或歸并排序。
快速排序是目前基于比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短;
堆排序所需的輔助空間少于快速排序,并且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。
若要求排序穩定,則可選用歸并排序。但本章介紹的從單個記錄起進行兩兩歸并的 排序算法并不值得提倡,通常可以將它和直接插入排序結合在一起使用。先利用直接插入排序求得較長的有序子文件,然后再兩兩歸并之。因為直接插入排序是穩定 的,所以改進后的歸并排序仍是穩定的。
果然算法很復雜,以我智商都有點不夠用。分析的我腦殼有點疼,實在扯不下去。就寫這么多了。見諒。。。
download.gif
五、Demo地址
六、參考文檔
七、內容推薦
上一篇:
如果你覺得我寫的不錯或者對您有所幫助的話
不妨頂一個【微笑】,別忘了點贊、收藏、加關注哈
您的每個舉動都是對我莫大的支持
image
總結
以上是生活随笔為你收集整理的归并排序改良 java_Java 八种排序算法总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java oracle查询语句无法赋值给
- 下一篇: java复杂性_如何衡量C或Java文件