這是一篇關於排序算法的文章,闡述以下幾點:
1 各個排序算法的思想以及java版代碼
2 各種排序算法的效率,本人對效率分析從來不敏感,所以全是書上的觀點。
第一部分:各個排序算法的思想以及java版代碼
1、冒泡排序
1)思想:從小到大
一組數 23,4,5,67,5,6.
左右比較,如果左>右,交換位置,比較的位置前移,直到遇到已經排序好的位置;=>把最大元素像冒泡一樣移動到最後一位
依次,進行
2)代碼
數據存放在a數組中;
public void bubbleSort(){
boolean flag=false;
for(int i=0;i<index-1;i++){
flag=false;
for(int j=0;j<index-i-1;j++){
if(a[j]>a[j+1]){
flag=true;
int tmp=a[j];
a[j]=a[j+1];
a[j+1]=tmp;
}
}
if(flag==false) break;
}
}
2、選擇排序
1)思想:少了搬移操作的冒泡排序
第一次比較能找到最大的元素,把它放在最後一個位置;
然後找第二大元素,把它放在倒數第二個位置;
。。。
這樣繼續下去,右邊有序的長度越來越長,左邊無序的長度越來越小,當無序的長度爲0的時候就排序好了。
2)代碼
數據存放在a數組中;
public void selectionSort(){
for(int i=0;i<index-1;i++){
int indexm;
for(int j=i+1;j<index;j++){
if(a[indexm]>a[j]){
indexm=j;
}
}
if(indexm!=i){
int temp=a[i];
a[i]=a[indexm];
a[indexm]=temp;
}
}
}
3、插入排序
1)思想:
如果把一個數插入一個有序的數組中是一個比較容易的操作。好,把數組的第一個元素看作是一個有序的數組,把第二個元素插入進來,這樣就獲得了一個長度爲2的有序的數組了。接着把前2個元素看作一個有序的數組,把第三個元素插入進來,數組的前2+1元素是有序的了。依次......
這樣就獲得了一個有序的數組了。
2)代碼
public void insertionSort(){
int i,j;
for(i=1;i<index;i++){
int x=a[i];
for(j=0;j<=i;j++){
if(a[j]>a[i])
break;
}
if(j<i){
for(int k=i;k>j;k--)
a[k]=a[k-1];
a[j]=x;
}
}
}
明白,寫的這個代碼不是最簡潔的。
4、合併排序
1)思想
相信大家這樣的操作對大家來說不難吧:把2個有序的數組合併成一個有序的數組。這樣就可以寫出一個函數來完成此功能。姑且成爲merge函數吧。
好,把一個無序的數組一分爲二,如果左、右兩邊的數組都是是有序的,那樣就可以通過merge得到有序的數組了。怎樣能得到呢?把左右的小數組看成是數組,在一份爲二排序,組合。哈哈,用到遞歸了。
可以這樣看,每一個小的步驟是:
(1)把一個數組一分爲二;
(2)排序左邊的數組;
(3)排序右邊的數組;
(4)merge 兩邊的數組
什麼時候退出遞歸呢?肯定是要排序的數組長度爲1,因爲長度爲1的數組就是一個有序的數組。
2)代碼
貼出一個完整的java代碼。代碼來源是一本數據結構的課本。這之中透露的封裝意味,細細體味吧。代碼中利用一次開闢中介數組,合併排序排的是一個數組中的不同部分,這是值得學習的
class DArray {
private int[] array;
private int index;
public DArray(int size) {
array = new int[size];
}
public DArray() {
this(20);
}
public void insert(int v) {
array[index++] = v;
}
public void mergeSort() {
int[] space = new int[array.length];
reMergeSort(space, 0, array.length - 1);
}
private void reMergeSort(int[] space, int low, int high) {
if (low == high)
return;
else {
int mid = (low + high) / 2;
reMergeSort(space, low, mid);
reMergeSort(space, mid + 1, high);
merge(space, low, mid + 1, high);
}
}
//把2個有序的數組合併成一個有序的數組
private void merge(int[] space,int lowPtr,int highPtr,int upperBound){
int j=0;
int lowBound=lowPtr;
int n=upperBound-lowPtr+1;
int mid=highPtr-1;
while((lowPtr <= mid) && (highPtr <= upperBound)){
if(array[lowPtr]>array[highPtr]){
space[j]=array[lowPtr];
lowPtr++;
}else{
space[j]=array[highPtr];
highPtr++;
}
j++;
}
while(lowPtr<=mid){
space[j++]=array[lowPtr++];
}
while(highPtr<=upperBound){
space[j++]=array[highPtr++];
}
for(int i=0;i<n;i++){
array[lowBound+i]=space[i];
}
}
public void display() {
for (int i : array)
System.out.print(i + "\t");
System.out.println();
}
}
一個更好理解的代碼:
/**
* 將2個有序的數組合併爲一個有序的數組
*/
public static int[] merge(int[] a, int b[]) {
int sizeA = a.length;
int sizeB = b.length;
int[] c = new int[sizeA + sizeB];
int indexA = 0, indexB = 0, indexC = 0;
while (indexA < sizeA && indexB < sizeB) {
int tmp;
if (a[indexA] > b[indexB]) {
tmp = a[indexA];
indexA++;
} else {
tmp = b[indexB];
indexB++;
}
c[indexC++] = tmp;
}
while (indexA < sizeA) {
c[indexC++] = a[indexA++];
}
while (indexB < sizeB) {
c[indexC++] = b[indexB++];
}
return c;
}
/**
* 合併排序
*/
public static int[] mergeSort(int[] num) {
if (num.length == 1) {
return num;
}
int half = num.length / 2;
int[] a = new int[half];
int[] b = new int[num.length - half];
int[] c = merge(mergeSort(a), mergeSort(b));
return c;
}
5、shell排序
1)思想
shell排序是插入排序的升級版。在插入排序中,排序間隔是1,這樣在從小到大的排序中,如果第一個元素是最大元素,那麼就需要移動n次。試想如果排序的間隔不是1。是2的話,只需要n/2次的搬移。插入排序在一個差不多有序的數組中效率還是很高的,因爲插入排序耗時主要在搬移上。
這樣看一個數組:
0 1 2 3 4 5 6 7 8 9
23 45 7 8 9 14 56 8 55 6
間隔爲4 23 9 55
45 14 9
7 6
8 8
當間隔爲4的時候去排序(0,4,8),(1,5,9),(2,6)(3,7)這四個數組
然後再設間隔爲2,再設間隔爲1,此時也就是普通的插入排序了
這種排序的效率很大程度上取決於間隔序列的產生。在這裏用的方法是:設x=1;x=3*x+1;讓x取小於n的最大數。
2)代碼
class ShellSort {
private static int maxGap;// 最大的間距
public static void shellSort(int[] array) {
getGapSequence(array);
int gap = maxGap;
while (gap >= 1) {
for (int index = 0; index < array.length; index++) {
for (int i = index + gap; i < array.length; i += gap) {
int j;
for (j = index; j < i; j += gap) {
if (array[j] > array[i])
break;
}
if (j < i) {
int tmp = array[i];
for (int k = i; k > j; k -= gap)
array[k] = array[k - gap];
array[j] = tmp;
}
}
}
gap = (gap - 1) / 3;
}
}
/**
* shell排序的基礎:插入排序
*/
public static void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int j;
for (j = 0; j < i; j++) {
if (array[j] > array[i])
break;
}
if (j < i) {
int tmp = array[i];
for (int k = i; k > j; k--)
array[k] = array[k - 1];
array[j] = tmp;
}
}
}
/**獲得最大間距,計算方式:h=1;h=3*h+1;
* */
private static void getGapSequence(int[] array) {
int n = 1;
while (n < array.length) {
maxGap = n;
n = n * 3 + 1;
}
}
}
6、快速排序
1)思想
理解快速排序之前先理解分割。
假如一個數組a的元素是:45,5,6,98,37,12,4,56;然後把這個數組分爲2部分,左邊是小於等於50的,右邊是大於50的,這樣就把數組變爲 45,5,6,4,37,12,98,56;以12爲分界線。具體變換過程是:
n=8
0 1 2 3 4 5 6 7
45,5,6,98,37,12,4,56
leftPtr=0 rightPtr=n-1
關鍵字pivot=50;
然後leftPtr++,直到a[leftPtr]>50,也就是遇到一個大於50的數,這個數應該放在右邊,右邊的什麼位置呢?rightPtr--,直到a[rightPtr]<50 ,在右邊找到一個小於50的數的位置,然後交換。繼續查找。如果leftPtr>=rigthPtr就完成了交換。
leftPtr=3 rightPtr=6 交換得到
45,5,6,4,37,12,98,56
leftPtr=6退出,分割成功。
分割的思想就是:把一個數組的元素按某個關鍵字分開,左邊是小於等於關鍵字,右邊是大於關鍵字的部分。注意一點的是左右兩邊並不一定是有序的,或者說是根本就無序,因爲我們沒有排序啊。
接下來具體看二分排序的思想。
分割一個數組成左(小於關鍵字的部分)右(大於關鍵字的部分)兩個子數組;
調用自身排序左子數組;
調用自身排序右子數組;
這樣數組就有序了。
爲什麼呢?先看看關鍵值的問題。怎麼找到這個關鍵值呢?這個關鍵值成爲pivot。爲簡單起見,我們把數組(子數組)最右端的元素作爲pivot;如果分割之後,pivot的值被插在了左右子數組之間的邊界位置,那麼這就是它在數組中的排序後的位置;這樣不斷分割,排序左右子數組就得到了有序的數組。
2)代碼
class QuickSort {
private int size = 0;
private int[] array;
public QuickSort(int size) {
array = new int[size];
}
public void show() {
for (int i = 0; i < array.length; i++)
System.out.print(array[i] + "\t");
System.out.println();
}
public void insert(int v) {
array[size++] = v;
}
/*
* 將left,right之間的數以pivot分割,pivot=array[right],並且將pivot插入排序位置
* 返回pivot所在位置
* */
public int partition(int left, int right, int pivot) {
int leftPtr = left;
int rightPtr = right;
System.out.println("left="+left+"\tright="+right);
//如果此處沒有等於號的限制,將可能在下面的賦值出現異常IndexOutOfBoundException
while (leftPtr <= rightPtr) {
while (leftPtr < rightPtr && array[leftPtr] < pivot)
leftPtr++;
while (leftPtr <rightPtr && array[rightPtr] >= pivot)
rightPtr--;
if (leftPtr >= rightPtr)
break;
else {
int tmp = array[leftPtr];
array[leftPtr] = array[rightPtr];
array[rightPtr] = tmp;
}
}
array[right]=array[rightPtr];
array[rightPtr]=pivot;
System.out.println("pivot="+pivot+"\t part="+rightPtr);
show();
return rightPtr;
}
//供外部調用
public void quickSort() {
sort(0, array.length - 1);
}
private void sort(int left, int right) {
//當只有一個元素的時候就是有序的
if (right - left <= 0) {
return;
}
int partition = partition(left, right, array[right]);
sort(left, partition - 1);
sort(partition + 1, right);
}
}
public class QuickSortApp {
public static void main(String[] args) {
QuickSort p = new QuickSort(10);
for (int i = 0; i < 10; i++)
p.insert((int) (Math.random() * 100));
/*p.insert(7);
p.insert(6);
p.insert(5);
p.insert(4);
p.insert(2);
p.insert(8);*/
p.show();
//p.sortStack();
//p.show();
p.quickSort();
p.show();
}
}
還有關於快速排序的進一步的算法,其改進之處在於pivot的選擇。在一個無序的數組中,以最左端或者最右端的元素爲關鍵字,分割,情況還好,但在一個有序的數組中,甚至排序順序和想要的結果是相反順序時,就很糟糕了。如果能找到一箇中間值作關鍵值效果就更好了。但要找整個數組的中間值,好像又要費好多時間。而要找到一個數組中左、中、右三個值的中間值是更容易的。
這樣獲得一種想法:
獲得三個數的中間值作 pivot,並且使left,right有序
分割返回p(privot在分割後數組中的位置)
分割left,p-1
分割p+1,right
基本情況(退出遞歸的條件):當長度(right-left+1)<=3,人工排序
class QuickSort2 {
private int size = 0;
private int[] array;
public QuickSort2(int size) {
array = new int[size];
}
public void show() {
for (int i = 0; i < array.length; i++)
System.out.print(array[i] + "\t");
System.out.println();
}
public void insert(int v) {
array[size++] = v;
}
public void quickSort() {
sort(0, size - 1);
}
/*對於長度小的子數組:可以手工排序,也可以使用插入排序
* */
private void sort(int left, int right) {
int size = right - left + 1;
/*if (size <= 3) {
manualSort(left, right);
} */
if(size<10){
insertionSort(left,right);
}
else {
// 左邊, 右邊是已經分割好的,是相對於 pivot
int mediant = medianof3(left, right);
int partition = partition(left, right, mediant);
sort(left, partition - 1);
sort(partition + 1, right);
}
}
private int medianof3(int left, int right) {
int mid = (left + right) / 2;
if (array[left] > array[mid])
swap(left, mid);
if (array[left] > array[right])
swap(left, right);
if (array[mid] > array[right])
swap(mid, right);
swap(right - 1, mid);
return array[right - 1];
}
/* 基本情況,長度爲1 2 3 ??? */
private void manualSort(int left, int right) {
int size = right - left + 1;
if (size <= 1)
return;
if (size == 2) {
if (array[left] > array[right])
swap(left, right);
return;
}
if (size == 3) {
int mid = (left + right) / 2;
if (array[left] > array[mid])
swap(left, mid);
if (array[left] > array[right])
swap(left, right);
if (array[mid] > array[right])
swap(mid, right);
}
}
private void swap(int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
/*左邊, 右邊是已經分割好的,是相對於 pivot
* 將left,right之間的數以pivot分割,pivot=array[right-1],並且將pivot插入排序位置
* 返回pivot所在位置
* */
public int partition(int left, int right, int pivot) {
int leftPtr = left + 1;
int rightPtr = right - 2;
while (leftPtr < rightPtr) {
while (leftPtr < rightPtr && array[leftPtr] <= pivot)
leftPtr++;
while (rightPtr > leftPtr && array[rightPtr] >= pivot)
rightPtr--;
if (leftPtr >= rightPtr)
break;
else {
swap(leftPtr, rightPtr);
}
}
swap(leftPtr, right - 1);
System.out.println("pivto=" + pivot);
System.out.println("leftPtr="+leftPtr+"\trightPtr="+rightPtr);
return leftPtr;
}
/*當長度《10的時候使用插入排序*/
private void insertionSort(int left,int right){
for(int i=1;i<=right;i++){
int tmp=array[i];
int j=i-1;
while(j>=0 && array[j]>tmp){
array[j+1]=array[j];
j--;
}
array[j+1]=tmp;
}
}
}
public class QuickSortApp2 {
public static void main(String[] args) {
QuickSort2 p = new QuickSort2(10);
for (int i = 0; i < 10; i++) {
System.out.print(i + "\t");
p.insert((int) (Math.random() * 100));
}
System.out.println();
p.show();
p.quickSort();
p.show();
}
}
7、堆排序
1)思想
5
78 89
123 98 99 100
什麼是堆?堆是一種樹。在最小值需求中堆頂的元素是整個堆中的最小值,最大值在葉子節點上。增刪改查之後還符合這樣的要求。
第一步:將一個無序的數組中的元素挨個插入堆中,然後從堆中remove得到的數再挨個放入數組中,這個數組就是
有序的了;
細看動作:數組a 5,78,89,123,98,99,100。在堆中最頂端是5,reomve()方法刪除並獲得堆頂元素5,放入數組中b;繼續remove,存放,b數組中5,78;n次操作之後b數組中的元素就是: 5,78,89,98,99,100,123。
進一步簡化:
第二步:節省時間。2個正確的子堆trickDown()之後成爲一個合格的大堆;
每個堆中最下面一排的節點都是正確的,因爲只有一個節點,所以只要從size/2開始作trickDown操作
就能使堆正確
第三步:節省空間:堆的每一個remove操作之後,最後一個空間就是閒置的了。如果把堆頂的元素依次插入
堆尾,這樣有序的空間越來越大,無序的空間越來越小,直至排序完成,這樣就省下了一半的空間。
2)代碼
class HeapSort {
private int[] array;//store numbers
private int index=0;//index
private int maxSize=0;
public HeapSort(int size){
maxSize=size;
array=new int[size];
}
public HeapSort(){
maxSize=20;
array=new int[20];
}
/*assume after index is right,and post array[index] to approprite position
* */
public void trickDown(int i){
int top=array[i];
int son=2*i+1;
while(i<index/2 ){
if(2*i+2<index){
if(array[2*i+1]>array[2*i+2])
son=2*i+1;
else
son=2*i+2;
}else{
son=2*i+1;
}
if(array[son]<=top)
break;
array[i]=array[son];
i=son;
}
array[i]=top;
}
/*remove the largest number and return it
* */
public int remove(){
if(index==0)
return -9;
int r=array[0];
array[0]=array[--index];
trickDown(0);
return r;
}
public String toString(){
String s="";
for(int i=0;i<maxSize;i++){
s+=array[i]+"\t";
}
return s;
}
}
8、利用java.util包裏的Arrays排序
1)思想
我實在是懶,不想寫代碼,最省事的方法,使用java.util包裏的Arrays排序。
2)代碼
package sort;
import java.util.StringTokenizer;
import java.util.Arrays;
public class ArraysSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
String s1 = "2,3,45,4,56,67,5,5";
StringTokenizer s2 = new StringTokenizer(s1, ",");
int count=s2.countTokens();
int num[]=new int[count];
for(int i=0;i<num.length;i++){
String str=s2.nextToken();
num[i]=Integer.parseInt(str);
}
Arrays.sort(num);
System.out.println(Arrays.toString(num));
}
}
第二部分 各種排序算法的效率
冒泡:O(n*n) 穩定 一般不用,除非想不起其他方法
選擇 O(n*n) 穩定 當數組小的時候可能會用
插入O(n*n) 穩定 如果你只能想起前三種排序方法,那最好還是用這種吧。
合併 O(n*logn)浪費空間
shell排序O(N*(logN)2)
快速排序 O(n* logN) 有時候可以達到 o(n*n)
堆排序 O(N*logN)
利用java.util包裏的Arrays排序 效率不知道,想知道看源代碼,肯定使用的是以上一種。
書名:Data Structures & Algorithms in Java 作者 Robert Lafore。
這本書中作者講述問題的思路特別清晰,步步深入;書中的代碼風格也值得我們學習,看書中代碼過程中我一直在想着封裝,隔離、透明幾個詞,是本好書。
排序算法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章
Linux基本操作命令
wbzjacky
2019-02-24 13:12:38
真實的模擬***綜合實驗
wbzjacky
2019-02-24 13:12:37
三層交換機的HSRP、vlan、端口聚合
wbzjacky
2019-02-24 13:12:37
HSRP和二層交換機的端口聚合、vlan
wbzjacky
2019-02-24 13:12:37
如果同事暗中傷害你,應該怎麼辦?
這個饅頭有餡
2019-02-24 13:59:08
職場中,抱怨越多的員工,越被領導瞧不起!
這個饅頭有餡
2019-02-24 13:59:08
老程序員被裁,應屆生卻能月薪 1.3 萬?這你能忍?
前端高達
2019-02-24 13:48:04
遇到到處蹭吃卻從不請客吃飯的主怎麼辦?
樑軍年
2019-02-24 13:26:35
高標準機房綜合配線安裝
wbzjacky
2019-02-24 13:12:38
IPsec ***實驗
wbzjacky
2019-02-24 13:12:37
CISCO路由AAA的Easy ***
wbzjacky
2019-02-24 13:12:37
CISCO訪問控制列表 企業網絡管理的必殺技
wbzjacky
2019-02-24 13:12:37