十大排序算法的比较与性能分析

  本系列文章共有十一篇:
    冒泡排序及优化详解
    快速排序及优化详解
    插入排序及优化详解
    希尔排序及优化详解
    选择排序及优化详解
    归并排序及优化详解
    堆排序详解
    计数排序及优化详解
    桶排序详解
    基数排序及优化详解
    十大排序算法的比较与性能分析

一、时间复杂度与空间复杂度

  在解决某一具体问题时,常常会涉及到不同的算法。此时,就需要用一些指标来评估不同算法的好坏,常用的指标有两个:时间复杂度和空间复杂度,相比之下,时间复杂度更为常用一些。

1.1 时间复杂度

  时间复杂度主要用来衡量一个算法运行所需要的时间,理论上这个时间受限于硬件、数据输入、数据输出因素等影响,必须要实际运行才能知道这个时间。所以此时就需要用一种估算的方式,来衡量真正的算法运行所需要的时间。
  一个算法中的语句执行次数称为语句频度或时间频度,记为T(n)。n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。为了表示输入规模n和时间频度T(n)的关系,引入了时间复杂度的概念。
  若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
  时间复杂度有不同的表示方式,常用的是大O表示法,大O表示方法的计算步骤:
   1>用常数1取代运行时间中的所有加法常数。
   2>只保留最高阶项。
   3>去除最高阶的常数。
  常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3),…, k次方阶O(nk),指数阶O(2n)。 常见的算法时间复杂度由小到大依次为:O(1) < O(logn) < (n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)。
  常见的复杂度例子:
   1>常数时间O(1)

		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;

  2>线性时间O(n)

	    for (int num : array) {
	        max = Math.max(max, num);
	        min = Math.min(min, num);
	    }

   算法循环体中的代码执行了n次,因此时间复杂度为O(n)。
  3>平方阶时间O(n2)

 for(int i=0;i<n;i++){   
      for(int j=0;j<n;i++){
         //复杂度为O(1)的算法
         ... 
      }

  4>O(n3)

 for(i=0;i<n;i++){  
       for(j=0;j<i;j++)  {
          for(k=0;k<j;k++)
             x=x+2;  
       }
}

  5>对数阶O(logn)

int i = 1, n = 100;
while( i < n )
{
    i = i * 2;
}

   有x个2相乘后大于或等于n,则会退出循环,于是由2^x = n得到x = log(2)n,所以这个循环的时间复杂度为O(logn)。
  6>线性对数时间O(nlogn)
   常见的是归并排序。

1.2 空间复杂度

  算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数,也是一种“渐进表示法”,这些所需要的内存空间通常分为“固定空间内存”(包括基本程序代码、常数、变量等)和“变动空间内存”(随程序运行时而改变大小的使用空间)。

1.3 常用算法的时间复杂度和空间复杂度

    

二、排序算法的类别

  关于排序算法的分类,如下:

三、排序算法的综合比较

  关于排序算法的总体比较,见于下表:

排序算法 平均时间 稳定性 备注
冒泡排序 O(n2) 稳定 n小时较好
快速排序 O(nlogn) 不稳定 n大、元素较无序时较好
插入排序 O(n2) 稳定 n小、元素基本有序时较好
希尔排序 O(n1.3) 不稳定 n小时较好
选择排序 O(n2) 不稳定 n小时较好
归并排序 O(nlogn) 稳定 n大时较好
堆排序 O(nlogn) 不稳定 n大时较好
计数排序 O(n + k) 稳定 n是输入数组长度
k是最大的数的大小
桶排序 O(N+NlogN-NlogM) 稳定 N个数据平均的分配到M个桶中
基数排序 O(d(n+r)) 稳定 r为基数
d为位数

四、排序算法的性能比较

  可以用生成固定长度的数组,数组内为固定范围的随机数。对该数组进行排序,来对比检测排序算法的性能,测试代码如下:

public class AllSort {
	public static void main(String[] args) {
		int arrayNum = 50;
		int arrayMax = 50;
		long start = 0L;
		long end = 0L;
		
		int[ ] array = createArray(arrayNum,arrayMax);
		int[ ] array1 = array;
		int[ ] array2 = array;
		int[ ] array3 = array;
		int[ ] array4 = array;
		int[ ] array5 = array;
		int[ ] array6 = array;
		int[ ] array7 = array;
		int[ ] array8 = array;
		int[ ] array9 = array;
		int[ ] array10 = array;
		
		start = System.currentTimeMillis();
		bubbleSort(array1);
		end = System.currentTimeMillis();
		System.out.println("1.冒泡排序所花费的时间"+(end-start)+"ms,冒泡排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array1.length;i++)
			System.out.print(array1[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		quickSort(array2,0,array2.length-1);
		end = System.currentTimeMillis();
		System.out.println("2.快速排序所花费的时间"+(end-start)+"ms,快速排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array2.length;i++)
			System.out.print(array2[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		insertionSort(array3,array3.length-1);
		end = System.currentTimeMillis();
		System.out.println("3.插入排序所花费的时间"+(end-start)+"ms,插入排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array3.length;i++)
			System.out.print(array3[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		shellSort(array4,array4.length-1);
		end = System.currentTimeMillis();
		System.out.println("4.希尔排序所花费的时间"+(end-start)+"ms,希尔排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array4.length;i++)
			System.out.print(array4[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		selectSort(array5,array5.length-1);
		end = System.currentTimeMillis();
		System.out.println("5.选择排序所花费的时间"+(end-start)+"ms,选择排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array5.length;i++)
			System.out.print(array5[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		mergeSort(array6,array6.clone(),0,array6.length-1);
		end = System.currentTimeMillis();
		System.out.println("6.归并排序所花费的时间"+(end-start)+"ms,归并排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array6.length;i++)
			System.out.print(array6[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		heapSort(array7);
		end = System.currentTimeMillis();
		System.out.println("7.堆排序所花费的时间"+(end-start)+"ms,堆排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array7.length;i++)
			System.out.print(array7[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		array = countSort(array8);
		end = System.currentTimeMillis();
		System.out.println("8.计数排序所花费的时间"+(end-start)+"ms,计数排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array8.length;i++)
			System.out.print(array8[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		bucketSort(array9);
		end = System.currentTimeMillis();
		System.out.println("9.桶排序所花费的时间"+(end-start)+"ms,桶排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array9.length;i++)
			System.out.print(array9[i]+" ");
		System.out.println("\n");
		
		start = System.currentTimeMillis();
		basicSort(array10);
		end = System.currentTimeMillis();
		System.out.println("10.基数排序所花费的时间"+(end-start)+"ms,基数排序后的数组为:");
		System.out.print("  ");
		for(int i=0;i<array10.length;i++)
			System.out.print(array10[i]+" ");
		System.out.println("\n");
		
		
	}
	
	/*生成测试数组,number为数组元素数量,max为数组元素最大值*/
	static int[] createArray(int number,int max){
		int[] array = new int[number];
        Random rand = new Random();
		for(int i=0;i<array.length;i++){
			array[i] = rand.nextInt(max); 
		}
		return array;
	}
	
	/*1.冒泡排序*/
	static void bubbleSort(int[] array) {
        int arrayLength = array.length;
        
        int preIndex = 0;
        int backIndex = arrayLength - 1;
        while(preIndex < backIndex) {
            preSort(array, arrayLength, preIndex);
            preIndex++;
            
            if (preIndex >= backIndex) {
                break;
            }
            
            backSort(array, backIndex);
            backIndex--;
        }
    }
    
    // 从前向后排序
	static void preSort(int[] array, int length, int preIndex) {
        for (int i = preIndex + 1; i < length; i++) {
            if (array[preIndex] > array[i]) {
                swap(array, preIndex, i);
            }
        }
    }
    
    // 从后向前排序
	static void backSort(int[] array, int backIndex) {
        for (int i = backIndex - 1; i >= 0; i--) {
            if (array[i] > array[backIndex]) {
                swap(array, i, backIndex);
            }
        }
    }
	
	/*2.快速排序*/
	static void quickSort(int[] arr, int startIndex, int endIndex) {
		/*当startIndex大于等于endIndex时,不再递归*/
		if (startIndex >= endIndex) {
		    return;
		}
		/*得到基准元素位置*/
		int keyIndex = divide(arr, startIndex, endIndex);
		/*递归处理基准的左右两部分*/
		quickSort(arr, startIndex, keyIndex - 1);
		quickSort(arr, keyIndex + 1, endIndex);
    }
	
	static int divide(int[] arr, int startIndex, int endIndex) {
		/*取第一个位置的元素作为基准元素*/
		int key = arr[startIndex];
		int left = startIndex;
		int right = endIndex;
		/*坑的位置,初始等于key的位置*/
		int index = startIndex;
		
		/*当right大于等于left时,执行循环*/
		while (right >= left){
		   /*right指针从右向左进行比较*/
		   while (right >= left) {
		      if (arr[right] < key) {
		    	  /*最右边的元素覆盖原来坑中的值*/
		          arr[left] = arr[right];
		          /*坑的位置改变,变成最右边元素对应的索引*/
		          index = right;
		          /*left索引右移,因为原来的值已被right索引的值所覆盖*/
	              left++;
		          break;
		      }
		      /*最右边的元素参与比较,无论是否参与与坑中元素的交换,都左移,不再参与下一轮的比较*/
		      right--;
		   }
		   
		   /*left指针从左向右进行比较*/
		   while (right >= left) {
		      if (arr[left] > key) {
		         arr[right] = arr[left];
		         index = left;
		         right--;
		         break;
		      }
		      left++;
		   }
		}
		
		/*将基准元素放在index位置,也就是大小元素放在其前后的中间位置*/
		arr[index] = key;
		return index;
	}
	
	/*3.2-路插入排序*/
	static void insertionSort(int[] arr,int len) {
        int j, first, last, mid;
        /*临时数组*/
        int[ ] tempArr =new int[len];        
        tempArr[0] = arr[0];
        /*first和last分别指临时数组tempArr中排好序的元素的第一个和最后一个位置*/
        first = last = 0;       

        for(int i = 1; i<len; i++){
        	/*j 是调整系数*/
            if(first > last){
                j = len;        
            }else{
                j = 0;
            }
            /*tempArr中间元素的位置*/
            mid = ((first+last+j)/2)%len; 
            /*arr[i]应该插入在tempArr的前半部分*/
            if(arr[i] < tempArr[mid]){      
            	/*j指向tempArr数组中的第一个元素*/
                j = first;    
                /*first 前移,取余是为了实现循环数组效果*/
                first = (first-1+len)%len;  
                /*待插元素大于 j 所指元素*/
                while(arr[i] > tempArr[j]){    
                	/*j 所指元素前移,取余是为了实现循环数组效果*/
                	tempArr[(j-1+len)%len] = tempArr[j];  
                	/*j 指向下一个元素*/
                    j = j+1;               
                }
                /*移动结束,待插元素插在tempArr[j]前*/
                tempArr[(j-1+len)%len] = arr[i];    
            /*arr[i]应该插入在tempArr的后半部分*/
            }else{   
            	/*j指向tempArr数组中的最后一个元素*/
                j = last;    
                /*last后移, 指向插入后的最后一个元素*/
                last++;            
                /*待插元素小于 j 所指元素*/
                while(arr[i] < tempArr[j]){  
                	/*j 所指元素后移*/
                	tempArr[(j+1)%len] = tempArr[j]; 
                	/*j 指向上一个元素*/
                    j = (j-1+len)%len;         
                }
                /*移动结束,待插元素插在tempArr[j]后*/
                tempArr[(j+1)%len] = arr[i]; 
            }
        }
        
        /*把在tempArr中排好序的元素依次赋给arr*/
        for(int i = 0; i < len; i++){                    
        	arr[i] = tempArr[(first+i)%len];
        }
    }	
	
	/*4.希尔排序*/
	static void shellSort(int[] arr, int len) {
		   /*初始化划分增量*/
		   int increment = len;	
		   int j,temp,low,mid,high;
		   /*每次减小增量,直到increment = 1*/
		   while (increment > 1){	
			  /*增量的取法之一:除三向下取整+1*/
			  increment = increment/3 + 1;
			  /*对每个按增量划分后的逻辑分组,进行直接插入排序*/
			  for (int i = increment; i < len; ++i) {	
				 low = 0;
				 high = i-1;
				 temp = arr[i];
		         while(low <= high){
		            mid = (low+high)/2;
		            if(arr[mid]>temp){
		                high = mid-1;
		            }else{
		                low = mid+1;
		            }
		         }
		         j = i-increment;
			     /*移动元素并寻找位置*/
				 while(j >= high+1){	
					arr[j+increment] = arr[j];
					j -= increment;
				 } 
				 /*插入元素*/
				 arr[j+increment] = temp;	
			  }
		   } 
	    }
	
	/*5.选择排序*/
	static void selectSort(int[] arr, int len) {
        /*初始化左端、右端元素索引*/
	    int left = 0;
	    int right = len - 1;
	    while (left < right){
	    	/*初始化最小值、最大值元素的索引*/
	        int min = left;
	        int max = right;
	        for (int i = left; i <= right; i++){
	        	/*标记每趟比较中最大值和最小值的元素对应的索引min、max*/
	            if (arr[i] < arr[min])
	                min = i;
	            if (arr[i] > arr[max])
	                max = i;
	        }
	        /*最大值放在最右端*/
	        int temp = arr[max];
	        arr[max] = arr[right];
	        arr[right] = temp;
	        /*此处是先排最大值的位置,所以得考虑最小值(arr[min])在最大位置(right)的情况*/
	        if (min == right)
	            min = max;
	        /*最小值放在最左端*/
	        temp = arr[min];
	        arr[min] = arr[left];
	        arr[left] = temp;
	        /*每趟遍历,元素总个数减少2,左右端各减少1,left和right索引分别向内移动1*/
	        left++;
	        right--;
	    }
	}
	
	/*6.归并排序算法*/
	static void mergeSort (int a[], int temp[], int start,int end) {
		if(end > start){
	        int mid =  start+(end-start)/2;  
	        /*对左右子序列递归*/
	        mergeSort(temp, a,start,mid);  
	        mergeSort(temp, a,mid+1,end);  
	        /*合并左右子数组*/
	        merge(a, temp, start,mid,end);  
		}
	}
		 
	static void merge (int arr[],int temp[],int start,int mid,int end) {
		/*左边子序列的头元素*/
		int i = start; 
		/*右边子序列的头元素*/
	    int j = mid+1;
	    
	    for(int k = start;k <= end;k++){
	      if(i>mid){
	    	  /*左边子序列元素用尽*/
	    	  arr[k] = temp[j++]; 
	      }else if(j>end){
	    	  /*右边子序列元素用尽*/
	    	  arr[k] = temp[i++]; 
	      }else if(temp[j]<temp[i]){
	    	  /*右边子序列当前元素小于左边子序列当前元素, 取右半边元素*/
	    	  arr[k] = temp[j++]; 
	      }else {
	    	  /*右边子序列当前元素大于等于左边子序列当前元素,取左半边元素*/
	    	  arr[k] = temp[i++]; 
	      }
	    }
    }
	
	/*7.堆排序*/
	static void heapSort(int[] arr) {
		if (arr == null || arr.length == 0) {
			return;
		}
		int len = arr.length;
		
		/*构建大顶堆(假设按升序方式排列),这里其实就是把原来的数
		 * 组,变成一个按大顶堆结构排列的数组
		 */
		buildMaxHeap(arr);
		/*将堆顶元素与最后一一个元素交换,总个数-1,重置大顶堆*/
		for (int i = len - 1; i > 0; i--) {
			swap(arr, 0, i);
			len--;
			heapify(arr, 0, len);
		}
	}
	 
	static void buildMaxHeap(int[] arr) {
	    for (int i = 0; i < arr.length; i++) {
	       int currentIndex = i;
	       /*父结点索引*/
	       int fatherIndex = (currentIndex - 1) / 2;
	       /*如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点
	        *然后继续和上面的父结点值比较,直到不大于父结点,则退出循环
	        */
	       while (arr[currentIndex] > arr[fatherIndex]) {
	           /*交换当前结点与父结点的值*/
	           swap(arr, currentIndex, fatherIndex);
	           /*将当前索引指向父索引*/
	           currentIndex = fatherIndex;
	           /*重新计算当前索引的父索引*/
	           fatherIndex = (currentIndex - 1) / 2;
	        }
	     }
	}
	 
	static void heapify(int[] arr, int i, int len) {
		/*先根据堆性质,找出它左右节点的索引*/
		int left = 2*i+1;
		int right = 2*i+2;
		/*默认当前节点(父节点)是最大值*/
		int largestIndex = i;
		if (left < len && arr[left] > arr[largestIndex]) {
			/*如果左边子结点的值更大,更新最大值的索引*/
			largestIndex = left;
		}
		if (right < len && arr[right] > arr[largestIndex]) {
			/*如果右边子结点的值更大,更新最大值的索引*/
			largestIndex = right;
		}
	 	if (largestIndex != i) {
			/*如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换*/
			swap(arr, i, largestIndex);
			/*互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整*/
			heapify(arr, largestIndex, len);
		}
	}
	 
	static void swap (int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	
	/*8.计数排序*/
	static int[] countSort(int[] array) {
	    // 找出原始数组array中的最大值、最小值
	    int max = 0;
	    int min = 0;
	    for (int num : array) {
	        max = Math.max(max, num);
	        min = Math.min(min, num);
	    }
	    /*初始化计数数组count,长度为最大值减最小值加1*/
	    int[] count = new int[max-min+1];
	    /*对计数数组各元素赋值*/
	    for (int num : array) {
	        /*array中的元素要减去最小值,再作为新索引*/
	        count[num-min]++;
	    }
	    /*计数数组变形,新元素的值是前面元素累加之和的值*/
	    for (int i=1; i<count.length; i++) {
	        count[i] += count[i-1];
	    }
	    /*结果数组*/
	    int[] result = new int[array.length];
	    /*遍历array中的元素,填充到结果数组中去,从后往前遍历*/
	    for (int j=array.length-1; j>=0; j--) {
	    	/*计数数组下标*/
	    	int countIndex = array[j]-min;
	    	/*结果数组下标*/
	    	int resultIndex = count[countIndex]-1;
	        result[resultIndex] = array[j];
	        count[countIndex]--;
	    }
	    return result;
	}

	/*9.桶排序*/
	static void bucketSort(int[] arr){
	    /*计算最大值与最小值*/
	    int max = 0;
	    int min = 0;
	    for(int i = 0; i < arr.length; i++){
	        max = Math.max(max, arr[i]);
	        min = Math.min(min, arr[i]);
	    }
	    
	    /*计算桶的数量*/
	    int bucketNum = (max-min)/arr.length+1;
	    /*用ArrayList组织对应的桶*/
	    List<ArrayList<Integer>> bucketArr = new ArrayList<ArrayList<Integer>>(bucketNum);
	    for(int i = 0;i<bucketNum;i++){
	        bucketArr.add(new ArrayList<Integer>());
	    }
	    
	    /*将每个元素放入对应的桶*/
	    for(int i = 0; i<arr.length;i++){
	    	/*找元素对应的桶*/
	        int num = (arr[i]-min)/(arr.length);
	        /*在同种放入对应的元素*/
	        bucketArr.get(num).add(arr[i]);
	    }
	    
	    /*对每个桶内的元素进行排序*/
	    for(int i = 0; i<bucketArr.size();i++){
	        Collections.sort(bucketArr.get(i));
	    }
	    
	    /*将桶中的元素赋值到原始数组arr*/
		for(int i = 0,index = 0; i < bucketArr.size(); i++){
			for(int j = 0; j < bucketArr.get(i).size(); j++){
				arr[index++] = bucketArr.get(i).get(j);
			}
		}  
	}
	
	/*10.基数排序*/
	static void basicSort (int[] array){
        //获取最大值;要看排几次,主要看这个最大值有几位;
        int max = 0;
        for (int i=0;i<array.length;i++){
            if (max<array[i]){
                max = array[i];
            }
        }

        //获取最大值位数;
        int times = 0;
        while (max>0){
            max/=10;times++;//求取这个最大值的位数,依次除以10;直到为0;
        }
        List<ArrayList> queue = new ArrayList<ArrayList>();//多维数组
        for (int i=0;i<10;i++){
            ArrayList q = new ArrayList();
                queue.add(q);//由于数字的特殊性,大数组中要添加10个小数组;
        }

        //开始比较,重点
        for (int i=0;i<times;i++){
            for (int j=0;j<array.length;j++){
                //获取每次要比较的那个数字;不管是哪个位置上的;
                //获取对应位的值(i为0是个位,1是十位,2是百位);
                int x = array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
                ArrayList q = queue.get(x);
               //把元素添加至对应下标数组;在小数组中添加原array的数值;
                q.add(array[j]); 
                queue.set(x, q);
            }
            //开始收集;
            int count = 0;
            for (int j =0;j<10;j++){
                while (queue.get(j).size()>0){
                    ArrayList<Integer> q = queue.get(j);//拿到每一个数组;
                    array[count] = q.get(0);
                    q.remove(0);
                    count++;
                }
            }
        }
    }
}

  测试结果如下:

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/107149046
今日推荐