七种排序算法总结

七种排序算法总结

排序算法 稳定性 原地排序 时间复杂度 空间复杂度 特性
简单插入 O(n)~O(n2) O(1) 小规模,部分有序速度快
希尔 O(n3/2)?O(nlogn )? O(1) 取决于元素排列情况
简单选择 O(n2) O(1) 取决于元素排列情况
堆排序 O(nlogn) O(1) /
冒泡 O(n2) O(1) /
快速 O(nlogn) O(logn) /
归并 O(nlogn) O(n) /

插入排序

简单插入排序

算法思想:

  1. 第一个外循环i遍历数组a,在每一个内循环j中,索引a[j]值与左边有序数列从后往前进行一个个比较,插入到合适的位置,即a[j]<a[j-1]时,交换位置。
  2. 直到外循环结束。
public class InsertaionSort
	{public static void sort(Comparable[] a)
		{ int N = a.length ;
		  int temp = 0;
		for(int i=0; i<N; i++) //外循环
			{ for(j=i;j>0&&a[j]<a[j-1];j--) //内循环
				{ temp = a[j-1]; //交换位置
				  a[j-1] = a[j];
				  a[j] = a[j-1];
				}
			}
		}
	}
	 

时间空间复杂度:

  • 最好的情况:本身有序的情况下,比较了n-1次,没有移动的次数,时间复杂度为O(n)。
  • 最坏的情况:本身逆序的情况下,比较了n*(n-1)/2次,时间复杂度为O(n2)。
  • 平均的情况:时间复杂度为O(n2)。
  • 空间的复杂度: O(1)。

排序特点:

  • 是原地排列,具有稳定性。
  • 对于部分有序数组十分高效,适合小规模数组。

希尔排序

如果数组的规模变大而变得不有序时,使用插入排序效率很低,而希尔排序就是想将值大的元素往后移动,元素值小的往前移动,从而形成部分有序。希尔排序是交换不相邻的元素,对数组进行局部排序。

算法思想:

  1. 设定一个h, 将数组等分为h个子数组,例如h=4, 索引为 0,4,8,12…为一组。用插入排序将h个子数组进行独立的排序。排序后,在每个子数组中大元素在后,小元素在前。
  2. 以一个特定的递减方式,缩小h,如h/3。再循环进行步骤1,直到h=1。
  3. 这是一个类似于插入排序而使用不同增量的进程。
public class ShellSort
{	
	public static void sort(Comparable[] a)
	{
		int N = a.length;
		int h = 1;
		while(h<N) h=3*h+1;
		while(h>=1)
		{ for(i=h;i<N;i++)
			{ for(j=i;j>=h&&a[j]<a[j-h];j-=h)
				{ temp = a[j-1]; //交换位置
			      a[j-1] = a[j];
				  a[j] = a[j-1];
				}
			}
		 h = h/3;
		}
}

时间空间复杂度:

  • 时间的复杂度: O(n3/2)。
  • 空间的复杂度: O(1)。

排序特点:

  • 是原地排列,希尔排序因为是跳跃式记录,故不是一个稳定的排序算法
  • 可用于大规模的数据,对于任意数组的表现也很好,比插入和选择排序快很多,数组越大,优势越明显。

选择排序

简单选择排序

算法思想:
数组a=[a1,a2,a3….an], 一共遍历n-1次。

  1. 第一次遍历中,从a1遍历到an,找出最小值ai,交换ai和a1的位置和数值。
  2. 第二次遍历中,从a2遍历到an,找出最小值ai,交换ai和a2的位置和数值。
  3. 以此类推,第n-1次遍历中,从an-1遍历到an,找出最小值ai,交换ai和an-1的位置。
public class SelectionSort
{
	public static void sort(Comparable[] a)
	{
		int N = a.length;
		for(int i =0;i<N-1;i++)
		{int min_ind = i;
			for(int j=i+1;j<N;j++)
			{if (a[j]<a[min_ind])
				min_ind=j;
			}
			if(min_ind!=i)
			{int temp = a[i]; //交换位置
			 a[i] = a[min_ind];
			 a[min_ind] = temp;
			}
		}
	}	
}
	

时间空间复杂度:

  • 时间的复杂度: 最好、最坏、平均都为O(n2)。运行时间和输入无关,无论输入是否有序,都需要做遍历找出最小值。
  • 空间的复杂度: O(1),只用到了min_ind和temp两个变量。

排序特点:

  • 是原地排列,具有不稳定性,因为找出最小值时使用了交换,会打乱顺序
  • 数据移动是最少的。一共都用了N次交换,交换次数和数组的大小成线性关系。

堆排序

堆是一个完全二叉树。分为大根堆和小根堆。
大根堆:每个父节点不小于所有子节点。
小根堆:每个父节点不大于所有子节点。
此处以大根堆为例。
注意:我们用长度为N+1的私有数组a[ ]来表示一个大小为N的堆,不会使用a[0]。

堆有序化:

可以使用下沉或者上游的办法进行堆有序化

下沉(sink)

private void sink(Comparable[]a, int k, int N)
{ 
	while (2*k<=N)
	{
		j= 2*k;
		if (j<N &&(a[j]<a[j+1])) j++;
		if(a[k]>=a[j]) break;
		// 交换
		int temp = a[k];
		a[k] = a[j];
		a[j] = temp;
		k = j; 
		
	}
}

上游(swim)

private void swim(Comparable[]a, int k, int N)
{
	while(k>1)
	{
		if(a[k]>a[k/2])
		{
			int temp = a[k/2];
			a[k/2] = a[k];
			a[k] = temp;
			k = k/2; 
		}
	}
}

堆有序化:找到最后一个非叶子节点, 进行sink

for(k=N/2; k>=1;k--) sink(a,k,N);

堆排序算法思想:

  1. 对数组进行堆有序化。
  2. 删除最大元素(将最大元素,即根节点。与堆中最后一个元素互换),互换以后,对根节点进行下沉(sink)操作。
  3. 循环步骤2,共进行n-1次循环后,使得数组变得从小到大排序。
public class HeapSort
{
	public static void sort(Comparable[] a)
	{	int N=a.length;
		for(int k=N/2;k>0;k--)
			sink(a,k,N);
		while(N>1)
		{
			exch(a,1,N--);
			sink(a,1,N);
		}
	}	

	private void swim(Comparable[]a, int k, int N)
	{
		while(k>1)
		{
			if(a[k]>a[k/2])
			{
				int temp = a[k/2];
				a[k/2] = a[k];
				a[k] = temp;
				k = k/2; 
			}
		}
	}
	
	private static void exch(Comparable[] a, int j, int j)
	{
		int temp = a[i];
		a[j]= a[i];
		a[i] = temp;
	}
}
	

时间空间复杂度:

  • 时间的复杂度: 最好、最坏、平均都为O(nlogn)。
    步骤1中,有序化,为O(n), 步骤2中,堆平衡为重复n-1次,每次时间为nlogn,得O((n-1)*(nlogn))。所以总的时间复杂度为O(n)+O(nlogn)=O(nlogn)

  • 空间的复杂度: O(1)

排序特点:

  • 是原地排列,具有不稳定性,进行了值的交换。
  • 同时最优的利用时间和空间的方法。
  • 无法利用缓存,数组元素很少和相邻的元素进行比较。

交换排序

冒泡排序

算法思想:
数组a=[a1,a2,a3….an], 一共遍历n-1次。

  1. 第一次遍历中,索引j从1到n-1, 比较a1和a2,如果啊a1>a2, 交换它们位置,否则不变。索引i+1,循环比较。当到j=n-1时,得到最大数放在an。
  2. 第二次遍历中,索引j从1到n-2, 比较a1和a2,如果啊a1>a2, 交换它们位置,否则不变。索引i+1,循环比较。当到j=n-2时,得到最大数放在an-1。
  3. 以此类推,第n-1次遍历中,比较a1和a2,如果啊a1>a2, 交换它们位置,否则不变。
public class BubbleSort
{
	public static void sort(Comparable[] a)
	{ 	int last_exchange_index = 0;
		int N = a.length;
		int sortlength = N-1;
		// step 1
		for(int i = 0; i<N; i++)
		{	boolean flag = true// 优化判断每次排序是否发生交换
			// step 2
			for(j=0; j<sortlength; j++)
			{   //bubble, compare
				if (a[j]>a[j+1])
				{  // exchange
					int temp = a[j];
					a[j] = a[j+1];
					a[j+1] = temp;
					last_exchange_index = j;//每次排序中最后的交换位置
					flag = flase;
				}
			}
			sortlength = last_exchange_index;
			
			if(flag) break;
		}
	}
}

优化:

  • 每个外循环引入一个flag,判断在一次排序中有没有发生交换。
  • 引入变量,记录每次排序中最后的交换位置。

时间空间复杂度:

  • 时间的复杂度: 最好O(n),最坏O(n2),平均O(n2)
    最好的情况:顺序数组,不交换,只遍历n-1次,O(n)。
    最好的情况:逆序数组,依次交换,n-1,n-2……,1次。总和为n(n-1)/2,为O(n2)。
    平均的情况:n-1到n(n-1)/2,递增求平均。
    E(x) = (n-1)n(2n-1)/6/(n-1)= (2n-1)*n/6,故为O(n2)。
  • 空间的复杂度: O(1),只用了几个变量。

排序特点:

  • 是原地排列,具有稳定性,虽然有值的交换,但是设定的是等号不交换。

快速排序

选基数切分+分治的思想。

算法思想:

  1. 选一个数为基数x(一般是第一个数或者中间数,a[initial])。
  2. 数组a中从左往右遍历(i++),找到第一个大于基数x的数a[i]; 从右往左找到(j–)第一个小于基数x的数a[j] 。交换a[i]和a[j]。
  3. 继续遍历i,j,重复步骤2,直到结束条件i=j达到。交换a[initial],a[j]。到这里就完成了一个切分的所有过程。
  4. 对于基数的左边和右边区域切分,重复步骤1,2,3。直到每个部分只有一个元素为止。
public class QuickSort
{
	public static void sort(Comparable[] a)
	{sort(a,0,a.length-1);}
	
	// 分治 递归 主函数
	private static void sort(Comparable[] a, int lo, int hi)
	{	
		if(hi<=lo) return;
		int j= partition(a,lo,hi);
		sort(a,0,j);
		sort(a,j+1,hi);	
	}
	
	// 选基数 切分
	private static int partition(Comparable[] a, int lo, int hi)
	{	
		int i=l0;
		int j=hi;	
		int key = a[lo]while(true)
		{	// 从左往右找到第一个大于key的数
			while(a[i]<key) i++;
			// 从右往左找到第一个小于key的数
			while(a[j]>=key) j--;
			// 循环终止条件,i,j相遇
			if (i>=j) break;
			// 交换
			exch(a,i,j)
		}
		exch(a,io,j)return j;
	}
	
	private static void exch(Comparable[] a, int j, int j)
	{
		int temp = a[i];
		a[j]= a[i];
		a[i] = temp;
	}
}

非递归的方式:待续。。。。。。

时间空间复杂度:

  • 时间的复杂度:
    最好的情况:每次都是对半分,O(nlogn)
    最好的情况:退化成冒泡,O(n2)。
    平均的情况:O(nlogn)
  • 空间的复杂度: O(logn),递归的深度。

排序特点:

  • 是原地排列,具有不稳定性,有值的交换。
  • 内循环简洁,在内循环中移动数据。速度快。

归并排序

算法思想:

假设初始序列含有n个记录,则可以看成是n个能有序的子序列,每个子序列的长度为1,然后两两归并,形成有序子序列,再两两归并,…,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为两路归并排序。

public class MergeSort
{
	private static Comparable[] aux;
	public stativc void sort(Comparable[] a)
	{
		aux = new Comparable[a.length];
		sort(a,0,a.length-1);
	}
	private static void sort(Comparable[] a,int lo, int hi)
   	{
   		if(hi>=lo) return;
   		int mid= lo+(hi-lo)/2;
   		sort(a,lo,mid);
   		sort(a,mid,hi);
   		merge(a,lo,mid,hi);
   	}

	private static void merge(Comparable[] a,int lo, int mid, int hi)
	{
	   	int i=lo,j=mid+1;
	   	for(int k=lo; k<=hi, k++)
	   		aux[k] = a[k];
	   	
	   	for(int k=l0,k<=hi, k++)
		   	{
		   		if (i>mid)          a[k] = aux[j++];
		   		else if (j<mid)     a[k] = aux[i++];
		   		else if (a[i]<a[j]) a[k] = a[i++];
		   		else                a[k] = a[j++];
		   	}
	   	
	 }
}

迭代

public class MergeSort
{
	private static Comparable[] aux;
	public stativc void sort(Comparable[] a)
	{
		aux = new Comparable[a.length];
		sort(a);
	}
	
	private static void sort(Comparable[] a)
   	{
   		int N=a.length; 
   		for(int size = 1;size<N;size =size+size)
   		{
   			for(int i =0;i<N-size;i+=2*size)
   				merge(a,i,i+size-1,min(i+2*size-1,N-1));
   		}
   	}
   	
	private static void merge(Comparable []a, int lo, int mid, int hi)
	{见上}
}

时间空间复杂度:

  • 最好、最坏、平均都是O(nlogn)
    有log2n层,一层时间O(n),得到时间复杂度O(nlogn)
  • 空间的复杂度: O(n),递归的深度。

排序特点:

  • 不是原地排列,具有稳定性。

至此,本人整理的七大排序算法已全部完成,因本人水平希望大家给予批评指正,共同进步~
如果觉得这文章还算用心,请劳驾点个赞留言,这是对我做开源分享的最大的肯定,谢谢。

发布了12 篇原创文章 · 获赞 0 · 访问量 903

猜你喜欢

转载自blog.csdn.net/EPFL_Panda/article/details/100566862