前言
花了将近一天的时间复盘了一下常用的十大排序算法,涉及到的内容也是蛮多的,如果有哪些地方有错误,希望大佬们多多指点指点。
术语解释
这里涉及到了一些名词:时间复杂度、空间复杂度等,那么,这是什么意思?
时间复杂度:执行一个算法所需要的时间。
空间复杂度:运行完一个算法所需要的内存空间。
稳定性:假如a==b,并且未排序时,a在b的前面,排序结束后,a仍在b的前面,那么我们就称之为稳定。反之,我们就称为不稳定。
内排序:所有排序操作都在内存中完成。
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行。
如何计算时间复杂度?
以冒泡算法为例:数组中含有n个数,共需比较n-1轮,第一轮比较n-1次,第二轮比较n-2次,第三轮比较n-3次,直至排完序,可以看为是一个等差数列,所以共比较1/2n^2-1/2n,根据复杂度的规则,去掉低阶项,去掉常数系数,所以时间复杂度就是O(n2)。
如何计算空间复杂度?
算法性质
一、冒泡排序
1.1、算法描述:
将第一个元素与第二个元素进行比较,如果第一个大于第二个,则交换位置,后面每两个相邻的元素做同样的操作,谁大谁就放到后面,第一轮比较完毕后,可以得到最大的。
此时除去最右边最大的元素,将剩下的元素重复上面的步骤直至排序完成。
1.2、代码实现
我这里直接将算法采用个例子实现了,方便观察运行结果,原理是都一样的。
package com.hpu.edu.dong;
public class BubbleSort {
/*冒泡排序*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={1,5,3,2,9,6,4,7,8};
//总共比较的圈数
for (int i = 0; i < arr.length-1; i++) {
//每圈内比较的次数
for (int j = 0; j < arr.length-1-i; j++) {
//如果前面的数大于后面的数,则交换两个数的位置
if(arr[j]>arr[j+1]){
int a=arr[j];
arr[j]=arr[j+1];
arr[j+1]=a;
}
}
}
//打印
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
二、选择排序
2.1、算法描述
首先在数组中搜索一遍,找到最小的元素,让其与第一个元素进行交换,我们令最小的元素为有序区,后面的为无序区。第二轮排序,找到最小的元素,让它与无序区的第一个元素进行交换。依次类推,完成数组的排序。
但是需要注意的是,在寻找最小值的过程中,只是交换了元素之间的坐标,还没有交换值,等此轮比较完成之后,再依据坐标完成值的交换。这一点和冒泡排序是不同的。冒泡排序是比较完之后立马交换值,而选择排序则是先交换坐标,本轮比较完成之后,再交换值。
2.2、代码实现
package com.hpu.edu.dong;
public class SelectionSort {
/*选择排序*/
//选择排序和冒泡排序的区别:冒泡排序比较完数值之后,立马交换位置,
//而选择排序则是交换了数值所在的位置,等到比较完之后,再交换数值,在一定程度上减少了数值交换的次数
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={1,5,9,3,7,4,2,6,8};
for (int i = 0; i < arr.length; i++) {
int minindex=i;
for (int j = i; j < arr.length; j++) {
if(arr[minindex]>arr[j]){
minindex=j;
}
}
if(minindex!=i){
int a=arr[minindex];
arr[minindex]=arr[i];
arr[i]=a;
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
三、插入排序
3.1、算法描述
选取数组第二个元素,令第一个元素为有序数组。从第二个元素开始为无序数组。将第二个元素与有序数组的元素进行比较,如果大于有序数组中的元素,则放在有序数组中的元素的后面,如果小于,则放在前面。此时有序数组大小增大为两个。继续令无序数组第一个元素与有序数组中的元素进行比较,直至排序完毕。
3.2、代码实现
package com.hpu.edu.dong;
public class InsertSort {
//插入排序:将一个数组分为未排序和已排序两个数组,令第一个数为已排序,第二个数为未排序的第一个数,
//让他们进行比较,并依次类推
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={1,5,9,3,7,4,2,8,6};
for (int i = 1; i < arr.length; i++) {
int temp=arr[i];//未排序的第一个数
int a=i-1;//已排序的最后一个数的位置
while(a>=0&&arr[a]>temp){
arr[a+1]=arr[a];
a--;
}
arr[a+1]=temp;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
四、希尔排序
4.1、算法描述
希尔排序可以说是插入排序的一种。那么,希尔排序是什么呢?
我们假定一个数组有六个元素,长度为8。第一次我们将其各元素间隔为8/2=4,即a[0]与a[4]、a[1]与a[5]、a[2]与a[6]、a[3]与a[7]。每组采用插入排序的算法进行排序。然后再将各元素划分为间隔4/2=2。并按照之前的步骤进行排序。当间隔为1时,此时数组各元素已经不是太无序了。再进行简单的排序即可。
4.2、代码实现
package com.hpu.edu.dong;
public class ShellSort {
//希尔排序
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={1,3,5,7,9};
int a=arr.length;
int half=a/2;
while(half>0){
for (int i = half; i < arr.length; i++) {
int temp=arr[i];
int pindex=i-half;
while(pindex>=0&&arr[pindex]>temp){
arr[pindex+half]=arr[pindex];
pindex-=half;
}
arr[pindex+half]=temp;
}
half/=2;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
五、归并排序
5.1、算法描述
归并排序是建立在归并操作的一种高效的排序方法,该方法采用了分治的思想。
通过递归的方式将大的数组分割为两个,并不断分割,直至每个组划分为只有一个元素。此时每个数组只有一个元素,我们可以认为是有序的。然后再将这两个元素进行排序合并为两个元素大小的数组,并不断重复此步骤,合并为大小为四个、八个。直至大小为原先的数组。
5.2、代码实现
package com.hpu.edu.dong;
public class MergeSort {
//归并排序
public static void main(String[] args){
int arr[]={5,4,3,2,1};
int temp[]=new int[arr.length];//新建一个临时数组存放
mergeSort(arr,0,arr.length-1,temp);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
public static void merge(int arr[],int low,int mid,int high,int temp[]){
int i=0;
int j=low,k=mid+1;//左边序列和右边序列起始索引
while(j<=mid&&k<=high){
if(arr[j]<arr[k]){
temp[i++]=arr[j++];
}else{
temp[i++]=arr[k++];
}
}
//若左边序列还有剩余,则将其全部拷贝进temp[]中
while(j<=mid){
temp[i++]=arr[j++];
}
while(k<=high){
temp[i++]=arr[k++];
}
for(int t=0;t<i;t++){
arr[t+low]=temp[t];
}
}
public static void mergeSort(int arr[],int low,int high,int temp[]){
if(low<high){
int mid=(high+low)/2;
mergeSort(arr,low,mid,temp);//对左边序列进行归并排序
mergeSort(arr,mid+1,high,temp);//对右边序列进行归并排序
merge(arr,low,mid,high,temp);//合并两个有序序列
}
}
}
六、快速排序
6.1、算法描述
从数组中选取一个元素,让它为中轴元素,让数组中的元素和它进行对比,数组中比他小的元素放在左边,比它大的元素放在右边。这样一个数组就分为两个数组,并且中轴元素的位置是确定不会改变的。对左右两边的数组分别进行同样的操作直至完成数组的排序。
6.2、代码实现
package com.hpu.edu.dong;
import java.util.Arrays;
public class QuickSort {
//快速排序
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={5,3,1,7,9};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int arr[],int low,int high){
//递归出口
if(low>high){
return ;
}
//标记
int i=low;
int j=high;
int key=arr[low];
//完成一趟快排
while(i<j){
while(i<j&&arr[j]>key){
j--;
}
while(i<j&&arr[i]<=key){
i++;
}
if(i<j){
int temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
//调整key的位置
arr[low]=arr[i];
arr[i]=key;
//对key左边的数快排
quickSort(arr,low,i-1);
//对key右边的数快排
quickSort(arr,i+1,high);
}
}
}
七、堆排序
7.1、算法描述
堆的特点就是堆的顶的元素是个最值,大顶堆的堆顶是最大值,小顶堆的堆顶是最小值。
堆排序就是将堆顶的元素和最后一个元素交换位置,交换完成之后破坏了堆的特性。将堆中剩余的元素再次构成一个大顶堆。然后将剩余的大顶堆和最后一个元素交换位置,重复之前的操作,即可完成排序。
7.2、代码实现
package com.hpu.edu.dong;
public class HeapSort {
// 堆排序
public static int[] headSort(int[] arr) {
int n = arr.length;
//构建大顶堆
for (int i = (n - 2) / 2; i >= 0; i--) {
downAdjust(arr, i, n - 1);
}
//进行堆排序
for (int i = n - 1; i >= 1; i--) {
// 把堆顶元素与最后一个元素交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
// 把打乱的堆进行调整,恢复堆的特性
downAdjust(arr, 0, i - 1);
}
return arr;
}
//下沉操作
public static void downAdjust(int[] arr, int parent, int n) {
//临时保存要下沉的元素
int temp = arr[parent];
//定位左孩子节点的位置
int child = 2 * parent + 1;
//开始下沉
while (child <= n) {
// 如果右孩子节点比左孩子大,则定位到右孩子
if(child + 1 <= n && arr[child] < arr[child + 1]){
child++;
}
// 如果孩子节点小于或等于父节点,则下沉结束
if (arr[child] <= temp )
break;
// 父节点进行下沉
arr[parent] = arr[child];
parent = child;
child = 2 * parent + 1;
}
arr[parent] = temp;
}
}
八、计数排序
8.1、算法描述
额外创建一个数组,统计原先数组中各个元素出现的次数,最后将临时数组中的元素按照顺序从小到大进行排序。
8.2、代码实现
package com.hpu.edu.dong;
public class CountSort {
//计数排序
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={3,5,4,4,6,9};
int max=arr[0];
for (int i = 0; i < arr.length; i++) {
if(max<arr[i]){
max=arr[i];
}
}
int temp[]=new int[max+1];
for (int i = 0; i < arr.length; i++) {
temp[arr[i]]++;
}
int k=0;
for (int i = 0; i <= max; i++) {
for(int j=temp[i];j>0;j--){
arr[k++]=i;
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
九、桶排序
9.1、算法描述
桶排序将最大值与最小值之间的数进行划分为多个区间,每个区间可以认为是一个桶,然后将每个区间对应的数分别放进每个桶中,然后对每个桶中的元素进行排序,归并、快排都是可以的。
9.2、代码实现
package com.hpu.edu.dong;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
public class BucketSort {
//桶排序
public static void main(String[] args) {
// TODO Auto-generated method stub
//定义数组
int arr[]={2,1,2,1,3,6,5,4};
//定义最小值
int min=arr[0];
//定义最大值
int max=arr[0];
//获取数组中的最大值与最小值
for (int i = 0; i < arr.length; i++) {
if(min>arr[i]){
min=arr[i];
}
if(max<arr[i]){
max=arr[i];
}
}
//定义桶数
int d=max-min;
int bucketNum=d/5+1;
ArrayList<LinkedList<Integer>> bucketList=new ArrayList<>(bucketNum);
//初始化桶
for (int i = 0; i < bucketNum; i++) {
bucketList.add(new LinkedList<Integer>());
}
//向各个桶中添加元素
for (int i = 0; i < arr.length; i++) {
bucketList.get((arr[i]-min)/d).add(arr[i]-min);
}
//各个桶中进行排序
for (int i = 0; i < bucketNum; i++) {
Collections.sort(bucketList.get(i));
}
//将各个桶中的元素返回给原数组
int k=0;
for (int i = 0; i < bucketNum; i++) {
for (Integer t : bucketList.get(i)) {
arr[k++]=t+min;
}
}
//打印数组中的元素
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
十、基数排序
10.1、算法描述
先以个位数的大小来进行排序,然后将数返回,再以十位数大小来进行排序,再以百位数的大小来进行排序。排到最后就是一个完整有序的数组了。
10.2、代码实现
package com.hpu.edu.dong;
import java.util.ArrayList;
import java.util.LinkedList;
public class RadioSort {
//基数排序
public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={2,6,11,54,32,66,22,71};
int n=arr.length;
int max=arr[0];
//找出最大值
for (int i = 0; i < arr.length; i++) {
if(max<arr[i]){
max=arr[i];
}
}
//计算最大值是几位数
int num=1;
while(max/10>0){
num++;
max=max/10;
}
//创建10个桶
ArrayList<LinkedList<Integer>> bucketList=new ArrayList<>(10);
//初始化桶
for(int i=0;i<10;i++){
bucketList.add(new LinkedList<Integer>());
}
//进行每一趟的排序,从个位数开始排
for(int i=1;i<=num;i++){
for(int j=0;j<n;j++){
//获取每个数最后第i位是数组
int radio=(arr[j]/(int)Math.pow(10, i-1))%10;
//放进对应的桶里
bucketList.get(radio).add(arr[j]);
}
//合并放回原数组
int k=0;
for(int j=0;j<10;j++){
for(Integer t:bucketList.get(j)){
arr[k++]=t;
}
//取出来合并之后把桶清光数据
bucketList.get(j).clear();
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}