一、內(nèi)部排序
1、插入排序—直接插入排序(Straight Insertion Sort)
/**
* 基本思想:
* 將一個記錄插入到已排序好的有序表中,從而得到一個新,記錄數(shù)增1的有序表。即:先將序列的第1個記錄看成是一個
* 有序的子序列,然后從第2個記錄逐個進(jìn)行插入,直至整個序列有序?yàn)橹埂? */
public class InsertSort {
public static void main(String[] args) {
int a[] = {3,1,5,7,2,4,9,6,10,8};
InsertSort obj=new InsertSort();
System.out.println("初始值:");
obj.print(a);
obj.insertSort(a);
System.out.println("\n排序后:");
obj.print(a);
}
public void print(int a[]){
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
public void insertSort(int[] a) {
for(int i=1;i<a.length;i++){//從頭部第一個當(dāng)做已經(jīng)排好序的,把后面的一個一個的插到已經(jīng)排好的列表中去。
if(a[i]<a[i-1]){
int j;
int x=a[i];//x為待插入元素
a[i]=a[i-1];
for(j=i-1; j>=0 && x<a[j];j--){//通過循環(huán),逐個后移一位找到要插入的位置。
a[j+1]=a[j];
}
a[j+1]=x;//插入
}
}
}
}
效率:時間復(fù)雜度:O(n^2).
2、插入排序—希爾排序(Shell`s Sort)
基本思想:
先將整個待排序的記錄序列分割成為若干子序列分別進(jìn)行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進(jìn)行依次直接插入排序
操作方法:
選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列個數(shù)k,對序列進(jìn)行k 趟排序;
每趟排序,根據(jù)對應(yīng)的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進(jìn)行直接插入排序。僅增量因子為
1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。
package com;
/*
* Java實(shí)現(xiàn)希爾排序(縮小增量排序)
* author:wyr
* 2016-7-14
*兩個步驟:1,建堆 2,對頂與堆的最后一個元素交換位置
*/
public class ShellSort {
public static void main(String[] args) {
int a[] = {3,1,5,7,2,4,9,6,10,8};
ShellSort obj=new ShellSort();
System.out.println("初始值:");
obj.print(a);
obj.shellSort(a);
System.out.println("\n排序后:");
obj.print(a);
}
private void shellSort(int[] a) {
int dk = a.length/2;
while( dk >= 1 ){
ShellInsertSort(a, dk);
dk = dk/2;
}
}
private void ShellInsertSort(int[] a, int dk) {//類似插入排序,只是插入排序增量是1,這里增量是dk,把1換成dk就可以了
for(int i=dk;i<a.length;i++){
if(a[i]<a[i-dk]){
int j;
int x=a[i];//x為待插入元素
a[i]=a[i-dk];
for(j=i-dk; j>=0 && x<a[j];j=j-dk){//通過循環(huán),逐個后移一位找到要插入的位置。
a[j+dk]=a[j];
}
a[j+dk]=x;//插入
}
}
}
public void print(int a[]){
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
}
希爾排序時效分析很難,關(guān)鍵碼的比較次數(shù)與記錄移動次數(shù)依賴于增量因子序列d的選取,特定情況下可以準(zhǔn)確估算出關(guān)鍵
碼的比較次數(shù)和記錄的移動次數(shù)。目前還沒有人給出選取最好的增量因子序列的方法。增量因子序列可以有各種取法,有
取奇數(shù)的,也有取質(zhì)數(shù)的,但需要注意:增量因子中除1 外沒有公因子,且最后一個增量因子必須為1。希爾排序方法是一
個不穩(wěn)定的排序方法。
3. 選擇排序—簡單選擇排序(Simple Selection Sort)
基本思想:
在要排序的一組數(shù)中,選出最小(或者最大)的一個數(shù)與第1個位置的數(shù)交換;然后在剩下的數(shù)當(dāng)中再找最小(或者最大)
的與第2個位置的數(shù)交換,依次類推,直到第n-1個元素(倒數(shù)第二個數(shù))和第n個元素(最后一個數(shù))比較為止。
操作方法:
第一趟,從n 個記錄中找出關(guān)鍵碼最小的記錄與第一個記錄交換;
第二趟,從第二個記錄開始的n-1 個記錄中再選出關(guān)鍵碼最小的記錄與第二個記錄交換;
以此類推.....
第i 趟,則從第i 個記錄開始的n-i+1 個記錄中選出關(guān)鍵碼最小的記錄與第i 個記錄交換,
直到整個序列按關(guān)鍵碼有序。
package com;
/*
* Java實(shí)現(xiàn)希爾排序(縮小增量排序)
* author:wyr
* 2016-7-14
*兩個步驟:1,建堆 2,對頂與堆的最后一個元素交換位置
*/
public class SimpleSelectSort {
public static void main(String[] args) {
int a[] = {3,1,5,7,2,4,9,6,10,8};
SimpleSelectSort obj=new SimpleSelectSort();
System.out.println("初始值:");
obj.print(a);
obj.selectSort(a);
System.out.println("\n排序后:");
obj.print(a);
}
private void selectSort(int[] a) {
for(int i=0;i<a.length;i++){
int k=i;//k存放最小值下標(biāo)。每次循環(huán)最小值下標(biāo)+1
for(int j=i+1;j<a.length;j++){//找到最小值下標(biāo)
if(a[k]>a[j])
k=j;
}
swap(a,k,i);//把最小值放到它該放的位置上
}
}
public void print(int a[]){
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
public void swap(int[] data, int i, int j) {
if (i == j) {
return;
}
data[i] = data[i] + data[j];
data[j] = data[i] - data[j];
data[i] = data[i] - data[j];
}
}
簡單選擇排序的改進(jìn)——二元選擇排序
簡單選擇排序,每趟循環(huán)只能確定一個元素排序后的定位。我們可以考慮改進(jìn)為每趟循環(huán)確定兩個元素(當(dāng)前趟最大和最小記錄)的位置,從而減少排序所需的循環(huán)次數(shù)。改進(jìn)后對n個數(shù)據(jù)進(jìn)行排序,最多只需進(jìn)行[n/2]趟循環(huán)即可。具體實(shí)現(xiàn)如下:
void SelectSort(int r[],int n) {
int i ,j , min ,max, tmp;
for (i=1 ;i <= n/2;i++) {
// 做不超過n/2趟選擇排序
min = i; max = i ; //分別記錄最大和最小關(guān)鍵字記錄位置
for (j= i+1; j<= n-i; j++) {
if (r[j] > r[max]) {
max = j ; continue ;
}
if (r[j]< r[min]) {
min = j ;
}
}
//該交換操作還可分情況討論以提高效率
tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;
tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp;
}
}
4. 選擇排序—堆排序(Heap Sort)
1、基本思想:
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進(jìn)。
堆的定義下:具有n個元素的序列 (h1,h2,...,hn),當(dāng)且僅當(dāng)滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)時稱之為堆。在這里只討論滿足前者條件的堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最大項(xiàng)(大頂堆)。完全二 叉樹可以很直觀地表示堆的結(jié)構(gòu)。堆頂為根,其它為左子樹、右子樹。
思想:初始時把要排序的數(shù)的序列看作是一棵順序存儲的二叉樹,調(diào)整它們的存儲序,使之成為一個 堆,這時堆的根節(jié)點(diǎn)的數(shù)最大。然后將根節(jié)點(diǎn)與堆的最后一個節(jié)點(diǎn)交換。然后對前面(n-1)個數(shù)重新調(diào)整使之成為堆。依此類推,直到只有兩個節(jié)點(diǎn)的堆,并對 它們作交換,最后得到有n個節(jié)點(diǎn)的有序序列。從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最后一個元素交換位置。所以堆排序有兩個函數(shù)組成。一是建堆的滲透函數(shù),二是反復(fù)調(diào)用滲透函數(shù)實(shí)現(xiàn)排序的函數(shù)。
2、實(shí)例
初始序列:46,79,56,38,40,84
建堆:
交換,從堆中踢出最大數(shù)
依次類推:最后堆中剩余的最后兩個結(jié)點(diǎn)交換,踢出一個,排序完成。
public class HeapSort {
public static void main(String[] args) {
int[] a={49,38,65,97,76,13,27,49,78,34,12,64};
int arrayLength=a.length;
//循環(huán)建堆
for(int i=0;i<arrayLength-1;i++){
//建堆
buildMaxHeap(a,arrayLength-1-i);
//交換堆頂和最后一個元素
swap(a,0,arrayLength-1-i);
System.out.println(Arrays.toString(a));
}
}
//對data數(shù)組從0到lastIndex建大頂堆
public static void buildMaxHeap(int[] data, int lastIndex){
//從lastIndex處節(jié)點(diǎn)(最后一個節(jié)點(diǎn))的父節(jié)點(diǎn)開始
for(int i=(lastIndex-1)/2;i>=0;i--){
//k保存正在判斷的節(jié)點(diǎn)
int k=i;
//如果當(dāng)前k節(jié)點(diǎn)的子節(jié)點(diǎn)存在
while(k*2+1<=lastIndex){
//k節(jié)點(diǎn)的左子節(jié)點(diǎn)的索引
int biggerIndex=2*k+1;
//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k節(jié)點(diǎn)的右子節(jié)點(diǎn)存在
if(biggerIndex<lastIndex){
//若果右子節(jié)點(diǎn)的值較大
if(data[biggerIndex]<data[biggerIndex+1]){
//biggerIndex總是記錄較大子節(jié)點(diǎn)的索引
biggerIndex++;
}
}
//如果k節(jié)點(diǎn)的值小于其較大的子節(jié)點(diǎn)的值
if(data[k]<data[biggerIndex]){
//交換他們
swap(data,k,biggerIndex);
//將biggerIndex賦予k,開始while循環(huán)的下一次循環(huán),重新保證k節(jié)點(diǎn)的值大于其左右子節(jié)點(diǎn)的值
k=biggerIndex;
}else{
break;
}
}
}
}
//交換
private static void swap(int[] data, int i, int j) {
int tmp=data[i];
data[i]=data[j];
data[j]=tmp;
}
}
5、交換排序(冒泡排序)
//冒泡排序中每一趟比較都會將最大的那個數(shù)找出來
public static void sort1(int[] arr) {
boolean sorted = true;// 假定有序
// 理論上會進(jìn)行arr.length-1趟比較
for (int i = arr.length - 1; i > 0; i--) {
//sorted = true;// 每一趟比較初始都假定數(shù)組有序
for (int j = 0; j < i; j++) {
// 每趟比較的次數(shù)為arr.length - i;
if (arr[j] > arr[j + 1]) {
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
sorted = false;
}
}
if (sorted == true) {
break;
}
}
}
說明:
1、冒泡排序就是每次都是前一個數(shù)和后一個數(shù)進(jìn)行比較,如果前面的數(shù)大,那么兩者就交換位置。
2、冒泡排序每次都會找出一個“最大數(shù)”,所以每趟比較的次數(shù)遞減,同時如果在某躺中沒有發(fā)生過交換,那么顯然數(shù)組就已
經(jīng)是有序的了,無序再進(jìn)行下一趟排序。時間復(fù)雜度為O(n^2)。平均時間復(fù)雜度為O(n^2),是一種穩(wěn)定的排序算法,空間復(fù)
雜度為O(1)。
6、交換排序(快速排序)
/**
* 快速排序<br/>
* <ul>
* <li>從數(shù)列中挑出一個元素,稱為“基準(zhǔn)”</li>
* <li>重新排序數(shù)列,所有元素比基準(zhǔn)值小的擺放在基準(zhǔn)前面,所有元素比基準(zhǔn)值大的擺在基準(zhǔn)的后面(相同的數(shù)可以到任一邊)。在這個分割之后,
* 該基準(zhǔn)是它的最后位置。這個稱為分割(partition)操作。</li>
* <li>遞歸地把小于基準(zhǔn)值元素的子數(shù)列和大于基準(zhǔn)值元素的子數(shù)列排序。</li>
* </ul>
*
* @param numbers
* @param start
* @param end
*/
public static void quickSort(int[] numbers, int start, int end) {
if (start < end) {
int base = numbers[start]; // 選定的基準(zhǔn)值(第一個數(shù)值作為基準(zhǔn)值)
int temp; // 記錄臨時中間值
int i = start, j = end;
do {
while ((numbers[i] < base) && (i < end))
i++;
while ((numbers[j] > base) && (j > start))
j--;
if (i <= j) {
temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
i++;
j--;
}
} while (i <= j);
if (start < j)
quickSort(numbers, start, j);
if (end > i)
quickSort(numbers, i, end);
}
}
7、歸并排序算法
基本思想:
歸并(Merge)排序法是將兩個(或兩個以上)有序表合并成一個新的有序表,即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合并為整體有序序列。
歸并排序示例:
合并方法:
- 設(shè)r[i…n]由兩個有序子表r[i…m]和r[m+1…n]組成,兩個子表長度分別為n-i +1、n-m。j=m+1;k=i;i=i; //置兩個子表的起始下標(biāo)及輔助數(shù)組的起始下標(biāo)
- 若i>m 或j>n,轉(zhuǎn)⑷ //其中一個子表已合并完,比較選取結(jié)束
- //選取r[i]和r[j]較小的存入輔助數(shù)組rf如果r[i]<r[j],rf[k]=r[i]; i++; k++; 轉(zhuǎn)⑵否則,rf[k]=r[j]; j++; k++; 轉(zhuǎn)⑵
- //將尚未處理完的子表中元素存入rf如果i<=m,將r[i…m]存入rf[k…n] //前一子表非空如果j<=n , 將r[j…n] 存入rf[k…n] //后一子表非空
- 合并結(jié)束。
算法實(shí)現(xiàn):
/**
* 歸并排序
* 簡介:將兩個(或兩個以上)有序表合并成一個新的有序表 即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合并為整體有序序列
* 時間復(fù)雜度為O(nlogn)
* 穩(wěn)定排序方式
* @param nums 待排序數(shù)組
* @return 輸出有序數(shù)組
*/
public static int[] sort(int[] nums, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
// 左邊
sort(nums, low, mid);
// 右邊
sort(nums, mid + 1, high);
// 左右歸并
merge(nums, low, mid, high);
}
return nums;
}
/**
* 將數(shù)組中l(wèi)ow到high位置的數(shù)進(jìn)行排序
* @param nums 待排序數(shù)組
* @param low 待排的開始位置
* @param mid 待排中間位置
* @param high 待排結(jié)束位置
*/
public static void merge(int[] nums, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;// 左指針
int j = mid + 1;// 右指針
int k = 0;
// 把較小的數(shù)先移到新數(shù)組中
while (i <= mid && j <= high) {
if (nums[i] < nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
// 把左邊剩余的數(shù)移入數(shù)組
while (i <= mid) {
temp[k++] = nums[i++];
}
// 把右邊邊剩余的數(shù)移入數(shù)組
while (j <= high) {
temp[k++] = nums[j++];
}
// 把新數(shù)組中的數(shù)覆蓋nums數(shù)組
for (int k2 = 0; k2 < temp.length; k2++) {
nums[k2 + low] = temp[k2];
}
}