七大排序算法简述及JAVA实现

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/weixin_43629719/article/details/88319974

一、算法分类。

(1) 按照时间复杂度分类

时间复杂度:指执行算法所需要的计算工作量,就是需要运算的次数。

  1. 平方阶:直接插入排序,直接选择排序,冒泡排序
  2. 线性对数阶:快速排序,堆排序,归并排序
  3. 线性阶:基数排序等(这篇文章不做描述)

(2)按照排序实现方式分类

  1. 插入排序:直接插入,希尔排序(也叫缩小增量排序)
  2. 选择排序:直接选择排序,堆排序
  3. 交换排序:冒泡排序,快速排序
  4. 分治思想排序:快速排序,归并排序

(3)按照算法的稳定性分类

稳定性概念:
若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。

  1. 稳定算法:直接插入排序,冒泡排序,归并排序
  2. 不稳定算法:直接选择排序,希尔排序,堆排序,快速排序

二、算法优劣点及其适用场景分析

(1)直接插入排序,直接选择排序,冒泡排序。

  1. 直接插入排序,算法复杂度为O(n^2).由于算法复杂度比较高,通常适用于数据量比较小的业务场景,在对数据量小的且数据基本有序的场景下,复杂度可以降低为O(n),从而提高效率,且实现比较简单。这个排序是个稳定的排序算法。
  2. 直接选择排序,算法复杂度为O(n^2),同样复杂度较高,适用于数据量比较小的业务场景,且算法不稳定。
  3. 冒泡排序,是个比较传统的排序,算法复杂度在O(n^2),同样适用于小业务场景,当原表有序或基本有序时,冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n).同样也是个稳定的算法

(2)希尔排序

   希尔排序是对直接插入排序的一种高效改进,从而将算法的复杂度降低,将直接插入排序的单个插入排序方式,改为分组多个插入,从而提高排序效率,降低运算量。其中希尔排序的算法复杂度在O(n^1.3~n^2)之间。适用于数据量比较适中的情况。

(3)堆排序、快速排序、归并排序

  1. 堆排序,是对选择排序的改进,实现方式是通过建立小顶堆或者是大顶堆,然后将最值和数组最后一个进行交换,这样最后一个就是最值,倒数第二个就是第二大(小)的值,按照这样的方式递归处理剩下的数据,数据就排好序了。算法复杂度在O(nlogn).程序运行时间比较短,且空间复杂度为O(1),不怎么占用内存,算法不稳定,适用于数据量比较大且不太要求稳定性的场景。
  2. 快速排序,可以说是冒泡排序的改进,采用分而治之的思想,首先选取一个pivot,通常选用第一个值或者是最后一个作为pivot,然后将数据按照pivot值进行分割,将大于pivot的值放在pivot的右边,小于piv的值放在pivot的左边,这样就确定了一个位置,就是pivot在整个数据中的位置。然后将pivot的左边的数据和右边的数据都进行快速排序,这样递归运行,直到所有的位置都确定了,这样的话,整个数据就已经排好序了,这个排序是一个不稳定的原地排序算法,算法复杂度在O(nlogn)~O(n^2),通常算法复杂度在O(nlogn),在极少数情况下,比如对基本有序的数据进行排序的场景下算法将退化为冒泡排序,复杂度上升为O(n ^ 2), 适用于数据量比较大的且不要求稳定性的场景,对随机分布的数据,使用快速排序效果最佳。
  3. 归并排序,是一个稳定的算法,其中算法复杂度在O(nlogn),运行时间比较段,但空间复杂度在O(n),说白了就是耗内存,该算法采用分治的思想,就是将大的数据切分成小块,一直细分下去,然后对细分的最小块进行排序,然后对小块进行合并排序成大块,递归运行,直到递归合并到最终的一块,整个数据就排好序了。该算法适用于数据量大且要求稳定性的,但是比较占用内存

三、算法的简单实现

  1. 冒泡排序。
public static void BubbleSort(int arr[]) {
	/*
	 * 冒泡排序。牺牲时间复杂度来换取稳定性.算法时间复杂度为O(N^2).这个是最简单的排序方式。第一次执行n-1次比较。第二次n-2比较。以此类推......
	 * 方法过于传统,适用于数据量比较小的场景。
	 */
	for (int i=0;i<arr.length;i++) {
		 for(int j=0;j<arr.length-i-1;j++) {	//执行完成一次循环之后,将最小值放在最右端。
			  if(arr[j]<arr[j+1]) {  //如果当前值比后面的值小,则进行交换。排序为降序。
						    int temp=arr[j];
						    arr[j]=arr[j+1];
						    arr[j+1]=temp;
			}
			}		
		}
}
  1. 直接选择排序。
	public static void SelectionSort(int [] arr) {
		/*
		 * 选择排序。选择排序的复杂度为O(n^2).思想 :从其中找出最值,然后和第一个位置进行比较,如果第一个值就是最值,不进行任何操作。否则,将最值和第一个进行交换。
		 * 第一次循环保证了第一个值一定是最值,依次类推,第二次循环,则是第二大的值。......知道循环结束为止。
		 */
		for (int i=0;i<arr.length;i++) {
			int max_index=i;				//最值索引赋值。
			for(int j=i;j<arr.length;j++) {
				
				if(arr[max_index]<arr[j]) {
					max_index=j;			//得到最大值的下标。
				}
			}
			if(max_index!=i) {			//如果最值不等于当前的下标,则对这数组里面的两个值进行交换。
				int temp=arr[i];
				arr[i]=arr[max_index];
				arr[max_index]=temp;
			}
		}
	}
  1. 直接插入排序(降序)
public static void InsertSort(int a[]) {
	/*
	 * 直接插入排序。思想:将无序序列插入有序序列中。算法复杂度为O(N^2).插入排序在运行过程中使得一部分是有序的。
	 * 这个排序是个稳定排序算法。其中对于基本有序的数据,算法复杂度可以降为O(n).适用于数据量比较小的场景,如果数据基本有序且要求稳定,则
	 * 选择插入排序.
	 */
	int key,i;
	for(int k=1;k<a.length;k++) { //从第二个元素进行遍历排序,默认第一个数字为有序序列。
		key=a[k];					
		i=k-1;
		while(i>=0 && a[i]<=key){ //如果key大于前面的值,且i>=0;则将前面的值放置到后面一个位置。
			a[i+1]=a[i];
			i-=1;				//每执行一次i自减一次。直到不满足条件为止。这样则保证前面的值都比当前的key大.后面的值都比当前的key值小。
		}
		a[i+1]=key; 	
	}
}
  1. 希尔排序
public static void ShellSort(int arr[]) {
	/*
	 * 希尔排序,在插入排序上的一次改进。现将整个序列划分成几个子序列,对其进行插入排序,然后在对整体进行插入排序。
	 * 希尔排序,又叫缩小增量排序。也是一个非稳定的排序算法。时间复杂度为O(n^(1.3—2))空间复杂度为O(1).
	 * 由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,
	 * 但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
	 * 希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)).比较适合中等规模。
	 * 希尔排序的时间复杂度与增量序列的选取有关
	 * 例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O(n^1.5)
	 */
		int increasement = arr.length;
		int i, j, k;
		do
		{
			// 确定分组的增量
			
			increasement = increasement / 3 + 1; //划分成多个组。
			for (i = 0; i < increasement; i++)//每个组都需要进行排序,对组进行循环。
			{
				for (j = i + increasement; j < arr.length; j += increasement) //每一组需要排序的次数。
				{
						/*
						 * 组内插入排序。
						 */
						int temp = arr[j];
						for (k = j - increasement; k >= 0 && temp < arr[k]; k -= increasement)
						{
							arr[k + increasement] = arr[k]; //将比temp大的数字都往后移动。直到temp不在小于arr[k]里面的值。
						}
						arr[k + increasement] = temp;
					}
				}
		} while (increasement > 1); //当组为1的时候排序结束。
	}
  1. 堆排序
public static void adjust(int arr[], int len, int index)
	{	
	/*
	 * 堆排序是一种选择排序,其时间复杂度为O(nlogn)。其中这里分为小顶堆和大顶推。
	 * 堆排序的时间复杂度为O(n*log(n)), 非稳定排序,原地排序(空间复杂度O(1))。
	 * 当数组中有相等元素时,堆排序算法对这些元素的处理方法不止一种,故是不稳定的。
	 * 堆排序大部分场景下没有快速排序快,同时占用的内存比快速排序大,但是相对快速比较稳定。
	*/
	    int left = 2*index + 1;  //左节点。
	    int right = 2*index + 2; //右节点.
	    int maxIdx = index;
	    if(left<len && arr[left] > arr[maxIdx]) {maxIdx = left;}
	    if(right<len && arr[right] > arr[maxIdx]) {maxIdx = right;}  // maxIdx是3个数中最大数的下标
	    if(maxIdx != index)                 // 如果maxIdx的值有更新,则对这其中的节点的值进行交换。
	    {	int tmp=arr[maxIdx];
	    	arr[maxIdx]=arr[index];
	    	arr[index]=tmp;
	        adjust(arr, len, maxIdx);       // 往下递归调整其他不满足堆性质的部分
	    }

	}

public static void HeapSort(int A[], int n)
	{
	    //先建立大顶堆,初始化操作,首先从最后一个非叶子节点开始。时间复杂度为O(n).
	    for(int i=n/2-1; i>=0; i--)
	    {
	        adjust(A,n,i);		
	    }//初始化完成后会在第一给位置生成最值。
	    
	    //进行排序	时间复杂度为O(nlogN).
	    for(int i=n-1; i>=1; i--)
	    {
	        //最后一个元素和第一元素进行交换
	    	int	 temp=A[i];
	        A[i] = A[0];
	        A[0] = temp;

	        //然后将剩下的无序元素继续调整为大顶堆
	        adjust(A,i,0);
	    }

	}
  1. 快速排序
public static void Quick_Sort(float data[], int left, int right)
	{
	/*快速排序。这个是目前应用最广泛的排序方法。高效率但是不稳定。算法复杂度在O(nlogn).适合数据量比较大的场合。
	 * 这个对于无序的数组的排序非常适用。对于有序或者基本有序的数组进行排序则会退化到O(n^2)复杂度。
	 * 填坑挖坑法。这里面采用最后一个为pivot。方法实现过程相对比较简单。
	 *
	 */
	    if (left < right)
	    {
	        int i = left, j = right;
	        float pivot = data[j];
	        while(i < j)
	        {
	            while(i < j && data[i] <= pivot) // 从左往右找到第一个比 pivot 大的数
	            {
	                i++;
	            }
	            if(i < j)
	            {
	                data[j--] = data[i];
	            }
	            while(i < j && data[j] >= pivot) // 从右往左找到第一个比 pivot 小的数
	            {
	                j--;
	            }
	            if(i < j)
	            {
	                data[i++] = data[j];
	            }
	        }
	        data[i] = pivot; // i=j
	        Quick_Sort(data, left, i-1);
	        Quick_Sort(data, i+1, right);
	    }
}
  1. 归并排序
public static void MergeSort(float sort_array[],int left,int right) {
		/*
		 * 归并排序的递归实现。算法的时间复杂度(O(nlogN)),空间复杂度为(O(N)).属于稳定的排序方法,但不是原地排序方法。
		 * 这个方法缺点就是需要大量的内存空间。O(n).
		 * 适用于数据量较大的场景,且要求稳定性的场景。
		 */
		if(left<right) {
			int mid=(left+right)/2;
			MergeSort(sort_array,left,mid);
			MergeSort(sort_array,mid+1,right);
			Merge_Array(sort_array,left,right);
			
		}
	}
	private static void Merge_Array(float[] sort_array, int left, int right) {
		int mid=(left+right)/2;
		int i=left;
		int j=mid+1;
		int k=0;
		float temp[]=new float [right-left+1];
		while(i<=mid && j<=right) {
			if(sort_array[i]>=sort_array[j]) {
				temp[k++]=sort_array[i++];
			}
			else {
				temp[k++]=sort_array[j++];
			}
		}
		while(i<=mid) {temp[k++]=sort_array[i++];}
		while(j<=right) {temp[k++]=sort_array[j++];}
		for (int f=0;f<k;f++) {
			sort_array[left++]=temp[f];	
		}
	}

结语:以上所有代码均用java语言实现,如有什么问题,请在下方留言.

猜你喜欢

转载自blog.csdn.net/weixin_43629719/article/details/88319974