常用排序算法總結

選擇排序
  • 基本思想:遍歷數組,找到最小的元素和第一個元素(nums[0])交換。次小的和第二個元素交換,也就是每次找到未排序部分中的最小值放在適當的位置。
  • 複雜度分析:長度爲N的數組,比較次數:N^2/2  交換次數:N  時間複雜度:N^2
  • 特點:運行時間和輸入無關,每次都需遍歷未排序部分找到最小值;數據的移動交換次數是最少的;
  • 代碼:
    public void select_sort(int[] nums){
    	int len = nums.length;
    	int k;
    	for(int i=0;i<len-1;i++){
    		k = i;//標記最小值的下標
    		for(int j=i+1;j<len;j++){
    			if(nums[j] < nums[k])
    				k = j;
    		}
    		if(k != i){
    			int tmp = nums[i];
    			nums[i] = nums[k];
    			nums[k] = tmp;
    		}
    	}
    }
  • 改進:簡單選擇排序,每趟循環只能確定一個元素排序後的定位。我們可以考慮改進爲每趟循環確定兩個元素(當前趟最大和最小記錄)的位置,從而減少排序所需的循環次數。改進後對n個數據進行排序,最多隻需進行[n/2]趟循環即可。
  • public void select_sort_next(int[] nums){
    	int len = nums.length;
    	int min;int max;
    	for(int i=0,j=len-1;i<j;i++,j--){
    		min = i;//標記最小值的下標
    		max = j;
    		for(int k=i;k<=j;k++){
    			if(nums[k] < nums[min])
    				min = k;
    			if(nums[k] > nums[max])
    				max = k;
    		}
    		if(min != i){
    			int tmp = nums[i];
    			nums[i] = nums[min];
    			nums[min] = tmp;
    		}
    		if(max != j){
    			int tmp = nums[j];
    			nums[j] = nums[max];
    			nums[max] = tmp;
    		}
    	}
    }
  1. 插入排序
  • 基本思想:撲克牌,拿起一張牌插入左手中已排序的部分,可以從右向左和手中的牌依次比較,直到找到一個小於他的(升序排列)。
  • 複雜度分析:長度爲N的數組,最壞情況比較次數:N^2/2  交換次數:N^2/2  時間複雜度:介於N和N^2之間
  • 特點:運行時間和輸入有關,若輸入是一個已排序的數組,能很快發現,運行時間線性,對於所有元素都相等的數組,時間也是線性的;特別適合部分有序數組的排序以及小規模數組,所以在歸併和快速排序中,數組變小後可以使用插入排序提高效率;是穩定的;
  • 代碼:
    public void Insertion_sort(int[] nums){
    	int len = nums.length;
    	for(int i=1;i<len;i++){//i剛拿起的未排序的撲克牌
    		int tmp = nums[i];
    		int j = i-1;
    		while(j >= 0 && tmp < nums[j]){//j從右向左將手中的牌依次和i比較
    			nums[j+1] = nums[j];
    			j--;
    		}
    		nums[j+1] = tmp;
    	}
    }
  1. 冒泡排序
  • 基本思想:(升序排列)最大值向下沉,相鄰的元素依次比較。
  • 複雜度分析:長度爲N的數組,比較次數:N^2/2  交換次數:N^2/2  時間複雜度:N^2
  • 特點:運行時間和輸入無關;
  • 代碼:
  • public void Bubble_sort(int[] nums){
    	int len = nums.length;
    	for(int i=len-1;i>0;i--){//i最大值存放的位置
    		for(int j=0;j<i;j++){
    			if(nums[j] >= nums[j+1]){
    				int tmp = nums[j];
    				nums[j] = nums[j+1];
    				nums[j+1] = tmp;
    			}	
    		}
    	}
    }
  • 改進:對冒泡排序常見的改進方法是加入一標誌性變量exchange,用於標誌某一趟排序過程中是否有數據交換,如果進行某一趟排序時並沒有進行數據交換,則說明數據已經按要求排列好,可立即結束排序,避免不必要的比較過程。所以可以:設置一標誌性變量pos,用於記錄每趟排序中最後一次進行交換的位置。由於pos位置之後的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可。
  • public void Bubble_sort_next(int[] nums){
    	int len = nums.length;
    	for(int i=len-1;i>0;){
    		int tmp_pos = 0;
    		for(int j=0;j<i;j++){
    			if(nums[j] >= nums[j+1]){
    				tmp_pos = j;
    				int tmp = nums[j];
    				nums[j] = nums[j+1];
    				nums[j+1] = tmp;
    			}//pos之後都是排好序的了
    		}
    		i = tmp_pos;
    	}
    }
  1. 歸併排序
  • 基本思想:將兩個已排序的數組歸併爲一個有序數組。
  • 複雜度分析:長度爲N的數組,時間複雜度:NlogN
  • 特點:穩定排序,需要額外的空間,空間複雜度O(N)
  • 代碼:
  • public void merge(int[] nums,int start,int end,int mid){
    	//額外的空
    	int[] arr = new int[end-start+1];
    	int i = start;
    	int j = mid+1;
    	int k = 0;
    	while(i<=mid && j<=end){
    		if(nums[i] < nums[j])
    			arr[k++] = nums[i++];
    		else
    			arr[k++] = nums[j++];
    	}
    	while(i<=mid)
    		arr[k++] = nums[i++];
    	while(j<=end)
    		arr[k++] = nums[j++];
    	for(i=start;i<=end;i++)
    		nums[i] = arr[i-start];
    }
    //遞歸版本
    public void Merge_sort(int[] nums,int start,int end){
    	if(start >= end)
    		return;
    	int mid = start + (end-start)/2;
    	Merge_sort(nums,start,mid);
    	Merge_sort(nums,mid+1,end);
    	merge(nums,start,end,mid);
    }

  1. 快速排序
  • 基本思想:將數組切分爲兩部分,兩部分整體有序,內部無序,之後遞歸切分即可。
  • 複雜度分析:長度爲N的數組,時間複雜度:NlogN
  • 特點:簡單、高效、適用於各種不同的輸入數據
  • 代碼:
  • public int partition(int[] nums,int start,int end){
    	int x = nums[start];//哨兵節點
    	int i = start+1;
    	int j = end;
    	while(i <= j){
    		while(i <= j && nums[i] < x)
    			i++;
    		while(i <= j && nums[j] >= x)
    			j--;
    		if(i > j)
    			break;
    		int tmp = nums[i];
    		nums[i] = nums[j];
    		nums[j] = tmp;
    	}
    	nums[start] = nums[j];
    	nums[j] = x;
    	return j;
    }
    //遞歸版本
    public void Quick_sort(int[] nums,int start,int end){
    	if(start >= end)
    		return;
    	int index = partition(nums,start,end);
    	Quick_sort(nums,start,index-1);
    	Quick_sort(nums,index+1,end);
    }
  • 改進:當數組比較小時,就可以使用插入排序:代碼如下
  • public void Quick_sort_next(int[] nums,int start,int end,int k){
    	if(end <= start+k){
    		Insertion(nums,start,end);
    		return;
    	}
    	int index = partition(nums,start,end);
    	Quick_sort(nums,start,index-1);
    	Quick_sort(nums,index+1,end);
    }
  • 改進:當數組中包含大量重複元素時,可以將數組切分爲三部分:小於、等於和大於x,從而避免對元素全部重複的子數組的遞歸排序;
  • public void Quick_sort_improve(int[] nums,int start,int end){
    	//遞歸結束的條件
    	if(start >= end)
    		return;
    	int x = nums[start];
    	int i = start +1;
    	int lt = start;//存放下一個小於x的值,目前指向等於x的值,lt與i之間的元素等於x
    	int gt = end;//存放下一個大於x的值,i與gt之間的是未排序的
    	while(i <= gt){
    		if(nums[i] < x)
    			exchange(nums,i++,lt++);//lt原來指向等於x的值,i小於x的值,交換後均向後移動
    		else if(nums[i] > x)
    			exchange(nums,i,gt--);//gt原來指向的值大小不明確,所以交換後i不變
    		else
    			i++;//等於x,i++即可
    	}
    	//此時,start--lt-1,小於x,lt--gt等於x,gt+1--end大於x,遞歸對兩部分排序即可
    	Quick_sort_improve(nums,start,lt-1);
    	Quick_sort_improve(nums,gt+1,end);
    }
    	
    	
    public static void main(String[] args){
    	int[] nums = {1,9,11,-5,-2,0,-5,7,8,1,20,11,-11,32,-9,56,1,1,1,1,1,-11};
    	//int[] nums = {1,9,11,14,20};
    	new Test().Quick_sort_improve(nums,0,nums.length-1);
    	for(int i=0;i<nums.length;i++)
    		System.out.print(nums[i]+" ");
    }
  • /*基本思想:
     *當數組中包含大量重複元素時,利用此改進的快速排序,可達到線性的時間複雜度
     *數組分爲三部分,小於x,等於x,大於x*/
    public void exchange(int[] nums,int i,int j){
    	int tmp = nums[i];
    	nums[i] = nums[j];
    	nums[j] = tmp;
    }
  • 還有一種改進就是:使用子數組的一小部分元素的中位數來切分數組,也就是選擇更好的x,使得切分後左右兩部分儘可能均勻,代價是需要計算中位數;
  • 參考:《算法》 八大排序算法








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章