最简单的文字与代码——了解七大排序算法

排序算法是最基础的算法之一,下面简单讲解下常用的七大算法的解题思路和相关的代码解法,包括冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、计数排序。

1.冒泡排序

  • 从第一个元素开始比较相邻元素,若第一个元素比第二个大,则交换他们的顺序
  • 重复操作直到最后一个元素,一组操作下来最后一个元素是这组元素的最大值
  • 重复上述操作,除了最后一个元素
  • 直到没有元素可以比较,结束排序
//已知将要比较的数组arr[],
for(int i=1;i<arr.length;i++){
	for(int j=0;j<arr.length-i;j++){	
		//比较相邻元素的大小
		if(a[j]>a[j+1]){
			int tmp=a[j];
			a[j]=a[j+1];
			a[j+1]=tmp;	
		}
	}
}

冒泡排序内外嵌套了两层循环,时间复杂度O(n²);

2.选择排序

  • 在未排序序列中选择最小的元素,将其放到最前面
  • 重复第一步的操作直到所有元素完成排序
//待排序的序列array
for(int i=0;i<array.length-1;i++){
	//初始化最小值
	int min=i;
	//开始比较
	for(int j=i;j<array.length-1;j++){
		if(array[j]<array[min]){
			min=j;
		}
	}
	if(min!=i){
		int tmp=array[j];
		array[j]=array[min];
		array[min]=tmp;
	}
}

仍然是双层循环,时间复杂度与冒泡排序一样

3.插入排序

  • 从未排序的元素中选中第一个元素与已排序的元素从后往前一一比大小,选择合适位置插入
  • 重复上述操作直到所有元素排序完成
//待排序的数组a;
for(int i=1;i<a.length;i++){
	int tmp=a[i];
	int j=i;
	//不断比较找到合适插入的地方
	while(tmp<a[j-1]){
		a[j]=a[j-1];
		j--;
	}
	if(i!=j){
		a[j]=tmp;
	}
}

类比打扑克,一手乱序的牌一张一张的插入他们合适的位置。
最好情况的时间复杂度是 O(n),最坏情况的时间复杂度是 O(n2),然而时间复杂度这个指标看的是最坏的情况,而不是最好的情况,所以插入排序的时间复杂度是 O(n2)。

4 希尔排序

  • 确定增量gap值(如数组长度的二分之一)
  • 相邻gap值的元素进行插入排序
  • 将gap值减小(如二分之一gap值),继续进行插入排序,直至gap值为1,完成所有排序
//待排序数组a;
for(int gap=a.length/2;gap>0;gap/=2){
	for(int i=gap;i<a.length;i++){
		int tmp=a[j];
		if(a[j]<a[j-gap]){
			while(j-gap>=0&&tmp<a[j-gap]){
				a[j]=a[j-gap];
				j-=gap;
			}
		}
		a[j]=tmp;
	}
}

希尔排序主要是为了解决当较小的数据大都出现在数组后面时导致的移动次数明显增多的问题
最优区间的选择如今并无定论,大部分的实验都选择了二分之一到三分之一这个区间。

5 归并排序

  • 采用分而治之的思想,先分为小的部分分别解决之后再将结果拼接
  • 将整个序列分为两个部分
  • 重复操作直至一个部分中只剩两个元素
  • 被拆分的两个部分头部分别添加一个指针,两者比大小,小者加入提前申请好的数组中同时指针加一,依次操作直至两个部分何为一个大部分
  • 依次操作直至整个数组排序完成
//待排序的数组a;
pubilc static main(){
	int left=0;
	int right=a.length-1;
	sort(a,left,right);

	public int[] sort(int[] a, int left, int right){
		int mid=(left+right)/2;
		if(a.length>2){
			//左半部分排序
			sort(a,left,mid);
			//右半部分排序
			sort(a,mid,right)
			//拆分完之后合并
			sortall(a,left,mid,right);
		}
	}

	public static void sortall(int a[],int left,int mid, int right){
	int[] tmp=new int[right-left+1];
	int l=left;
	int r=right;
	int k=0
		whlie(l<=mid&&r<=right){
			if(a[left]<a[mid]){
				tmp[k++]=a[left++];
			}else{
				tmp[k++]=a[right++];
			}
		}
		while(l<=mid){
			tmp[k++]=a[l++];
		}
		while(r<=right){
			tmp[k++]=a[r++];
		}
	return tmp[];
	}
}

方法中只有一个 for 循环,直接就可以得出每次合并的时间复杂度为 O(n) ,而分解数组每次对半切割,属于对数时间 O(log n) ,合起来等于 O(log2n) ,也就是说,总的时间复杂度为 O(nlogn) 。
但是对于空间复杂度,我们需要申请临时数组存储分割的数据,之后再释放空间

6 快速排序

  • 找出一个基准数(一般可用第一个)
  • 将比这个数大的元素放在这个数前面,比这个元素小的数放在这个数后面
  • 分而治之的思想再分别对这个前后两个数组重复做快速排序
//一次快速排序的代码,整个算法可嵌套快速排序使用
//待排序的数组a;
public void quicksort(int a[], int left,int right){
	int flag=left;
	int start=flag+1;
	int end=right;
	while(start<end){
		if(a[flag]>a[start]){
			swap(a[flag],a[start]);
			start++;
		}
		if(a[flag]<a[end]){
			swap(a[flag],a[end]);
			end--;
		}
	}
}

快速排序的时间复杂度是 O(nlogn),极端情况下会退化成 O(n²),为了避免极端情况的发生,选取基准值应该做到随机选取,或者是打乱一下数组再选取。

7 计数排序

  • 遍历整个数组,找出最大值max与最小值min,新建一个长度为(max-min+1)的数组
  • 再次遍历整个数组,每个数据每出现一次,新建的数组中对应索引的值加一
  • 按新建数组对应的索引和值输出排序好的数组
//待排序数组a;
int min=max=0;
for(int i=0;i<a.length-1;i++){
	max=a[i]>max?a[i]:max;
	min=a[i]<min?a[i]:min;
}
int b[]=new int[max-min+1];
for(i=0;i<a.length-1;i++){
	int j=a[i];
	b[j]++;
}
int k=0;
for(i=0;i<b.length-1;i++){
	//将排序好的数组返回a
	//a[i]为i的个数
	while(b[i]!=0){
		a[k++]=i;
		b[i]--;
	}
}

计数排序是一种非基于比较的排序算法,并非基于元素比较进行排序的。时间复杂度为O(m+n),即约等于O(n)。但他的速度快是基于空间占用大于前面所述的排序方法的。每次都需要新申请一个有可能非常大的新数组去存储元素,而且当最大值与最小值差距很大时,会造成非常大的浪费。所以这是比较经典的空间换时间的一种排序方法。