Java | 数据结构 - 排序算法汇总【直接插入排序、希尔排序、冒泡排序、快速排序、直接选择排序……】(代码含注释,超详细)

所谓排序,就是将一组“无序”的记录序列调整为“有序”的记录序列的一种操作。

一、插入类排序

1. 直接插入排序(Straight Insertion Sort)

每趟将一条待排序的记录,按其关键字值的大小插入到前面已经排好序的记录序列中的适当位置,直到全部记录插入完成为止。

/*直接插入排序*/
public static void InsertSort(int[] arr) {
	for(int i = 1;i < arr.length;i++) {  //n-1趟扫描
		int insertVal = arr[i];  //待插入元素
		int insertIndex = i-1;  //初始下标
		//将前面比待插入元素大的记录后移
		while(insertIndex >= 0 && insertVal < arr[insertIndex]) {  
			arr[insertIndex + 1] = arr[insertIndex];
			insertIndex--;
		}
		//插入
		if(insertIndex+1 != i) {
			arr[insertIndex + 1] = insertVal;  
		}
	}
	System.out.println("直接插入排序:" + Arrays.toString(arr));
}

2. 希尔排序(Shell Sort)

先选取一个小于n的整数d(称为增量),然后把排序表中的n个记录氛围d个子表,从下标为0的记录开始,建个为d的记录组成一个子表,在各个子表内进行直接插入排序。在一趟之后,建个为d的记录组成的子表已然有序,随着有序性的改善,逐步减小增量d,重复进行上述操作,知道d=1,使得建个为1的记录有序,也就是整个序列都达到有序。

/*希尔排序*/
public static void ShellSort(int[] arr) {
	int j;
	//增量gap,并逐步缩小增量
	for(int gap=arr.length/2;gap>0;gap/=2) {
		//从第gap个元素逐个对其所在的组进行插入排序
		for(int i=gap;i<arr.length;i++) {
			int temp = arr[i];
			 for(j=i;j>=gap && temp<arr[j-gap];j-=gap) {
                    arr[j] = arr[j-gap];
                }
                arr[j] = temp;
            }
		}
	System.out.println("希尔排序:" + Arrays.toString(arr));
}

二、交换类排序

1. 冒泡排序(Bubble Sort)

将待排序的数组看成从上到下排放,把关键字值较小的记录看成“较轻的”,关键字值比较大的记录看成“较重的”,较小关键字值的记录好像水中的气泡一样,向上浮;较大关键字值的记录如水中的石块向下沉,当所有的气泡都浮到了相应的位置,并且所有的石块都沉到了水中,排序就结束了。

/*冒泡排序*/
public static void BubbleSort(int[] arr) {
	int temp = 0;  //辅助变量
	boolean flag = true;  //标志该元素是否发生交换
	for(int i = 0;i < arr.length-1 && flag;i++) {  //有交换再进行下一趟,最多进行n-1趟
		flag = false;  //记录未交换
		for(int j = 0;j < arr.length-1-i;j++) {  //一次比较、交换
			if(arr[j] > arr[j+1]) {  //逆序时交换
				flag = true;
				temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}
	System.out.println("冒泡排序:" + Arrays.toString(arr));
}

 2. 快速排序(Quick Sort)

通过一趟排序将要排序的记录分割成独立的两个部分,其中一部分的所有记录的关键字值都比另外一部分的所有记录的关键字值小,然后再按此方法对着两部分记录分别进行快速排序,整个排序过程可以递归进行,以此达到整个记录序列变成有序。

/*快速排序*/
public static void randomizedQuickSort(int[] a,int p,int r) {
	if(p < r) {
		int q = randomizedPartion(a,p,r);
		randomizedQuickSort(a,p,q-1);  //对数组左半段排序
		randomizedQuickSort(a,q+1,r);  //对数组右半段排序
	}
}
//随机选择支点
public static int randomizedPartion(int[] a,int p,int r) {
	Random random = new Random();
	int i = random.nextInt(r) % (r-p+1) + p;
	swap(a,i,p);
	return partion(a,p,r);
}
//划分
public static int partion(int[] a,int p,int r) {
	int x = a[p];  //基准元素
	int i = p;
	int j = r+1;
	while(true) {
		while(a[++i] < x && i < r);  //将小于基准元素的交换到左边区域
		while(a[--j] > x) ;  //将大于基准元素的交换到右边区域
		if(i >= j) {
			break;
		}
		swap(a,i,j);  //交换
	}
	a[p] = a[j];
	a[j] = x;
	return j;
}
//交换
private static void swap(int[] a,int i,int j) {
	int temp = a[i];
	a[i] = a[j];
	a[j] = temp;
}

三、选择类排序

1. 直接选择排序(Straight Selection Sort)

在第1趟中,从n个记录中找出关键字值最小的记录与第1个记录交换;在第2趟中,从第2个记录开始的n-1个记录中再选出关键字最小的记录与第二个记录交换;以此类推,在第i趟中,从第i个记录开始的n-i+1个记录中选出关键字值最小的记录与第i个记录交换,直到整个序列按关键字值有序为止。

/*直接选择排序*/
public static void SelectSort(int[] arr) {
	for(int i = 0;i < arr.length;i++) {  //n-1趟排序
		//假设第i条记录的关键字值最小
		int minIndex = i;  
		int min = arr[i];
		//在子序列中选择剩余记录中最小的元素
		for(int j = i + 1;j < arr.length;j++) {	
			if(arr[j] < min) {
				minIndex = j;
				min = arr[j];
			}
		}
		//将本趟关键字值最小的记录与第i条记录交换
		if(minIndex != i) {
			arr[minIndex] = arr[i];
			arr[i] = min;
		}
	}
	System.out.println("直接选择排序:" + Arrays.toString(arr));
}

2. 堆排序(Heap Sort)

首先将这n条记录按关键字值的大小建成堆(称为初始堆),将堆顶元素r[0]与r[n-1]交换;然后,将剩下的r[0]..r[n-2]序列调整成堆,再将r[0]与r[n-2]交换,又将剩下的r[0]..r[n-3]序列调整成堆,如此反复,便可得到一个关键字值有序的记录序列。

/*堆排序*/
public static void HeapSort(int[] arr) {
	int temp = 0;  //辅助变量
	//创建堆
	for(int i = arr.length / 2;i >= 0;i--) {
		adjustHeap(arr,i,arr.length);
	}
	//每趟将最小关键字值换到后面,再调整堆
	for(int j = arr.length - 1;j > 0;j--) {
		temp = arr[j];
		arr[j] = arr[0];
		arr[0] = temp;
		adjustHeap(arr,0,j);
	}
	System.out.println("堆排序:" + Arrays.toString(arr));
}
//调整堆
public static void adjustHeap(int[] arr,int i,int length) {
	int temp = arr[i];
	//沿较小值孩子结点向下筛选
	for(int k = i*2+1;k < length;k = k*2+1) {
		//记录比较,k为左右孩子的较小者
		if(k+1 < length && arr[k] < arr[k+1]) {
			k++;
		}
		//若父母结点值较大,孩子结点中的较小值上移
		if(arr[k] > temp) {
			arr[i] = arr[k];
			i = k;
		}else {
			break;  //退出循环
		}
	}
	arr[i] = temp;  //当前子树的原根值调整后的位置
}

四、归并类排序

1. 归并排序(Merging Sort)

将待排序记录r[0]到r[n-1]看成是一个含有n个长度为1的有序子表,把这些子表依次进行两两归并,得到[n/2]个有序的子表;然后,再把这[n/2]个有序的子表进行两两归并,如此重复,直到最后得到一个长度为n的有序表为止。

/*归并排序*/
public static void MergeSort(int[] a,int left,int right) {
	int[] b = new int[a.length];  //辅助数组
	if(left < right) {
		int mid = (left + right)/2;  //中间元素下标
		MergeSort(a,left,mid);  //对左半段元素进行递归划分
		MergeSort(a,mid+1,right);  //对右半段元素进行递归划分
		Merge(a,b,left,mid,right);  //左右合并
		Copy(a,b,left,right);  //复制到新的数组
	}
}
//合并
public static void Merge(int c[],int d[],int l,int m,int r) {
	int i=l,j=m+1,k=l;
	while((i<=m)&&(j<=r)) {  //左右下标同时增加
		//将较小的元素先加入d[]中
		if(c[i]<=c[j]) {  //左小于右
			d[k++]=c[i++];  
		}
		else {  //右小于左
			d[k++]=c[j++];
		}
	}
	//将剩余部分归并到d[]中
	if(i>m) {  //多余元素在右半段
		for(int q=j;q<=r;q++) {
			d[k++]=c[q];
		}
	}
	else {  //多余元素在左半段
		for(int q=i;q<=m;q++) {
			d[k++]=c[q];
		}
	}
}
//复制
public static void Copy(int[] a,int[] b,int l,int r) {
	for(int i = l;i <= r;i++) {
		a[i] = b[i];
	}
}

五、基数类排序

1. 基数排序(Radix Sort)

将一个序列中的逻辑关键字看成是由d个关键字复合而成,并采用最低位优先方法对该序列进行多关键字排序,即从最低关键字开始,将整个序列中的元素“分配”到rd个队列中,再依次“收集”成一个新的序列,如此重复进行d次,即完成排序过程。

/*基数排序*/
public static void RadixSort(int a[]){
	//求最大数
	int max=a[0];
	for (int i = 1; i < a.length ; i++) {
		if (a[i] > max){
			max = a[i];
		}
	}
	int maxNum=String.valueOf(max).length();  //求出最大数的位数
	int bucket[][]=new int[10][a.length];  //定义10个桶
	int bucketmany[]=new int[10];  //定义每一个桶中装了多少元素的数组
	//将原数组的值放入到桶中,排序i趟
   for (int z=0;z<maxNum;z++){
	   for (int i=0;i<a.length;i++) {
		   //根据i求出当前是多少位数的值
		   int k= (int)(a[i]/Math.pow(10,z)%10);
		   //几位数是多少就放到那个桶里,并放在那个桶里的第几个位置上
		   bucket[k][bucketmany[k]] = a[i];
		   bucketmany[k]++;
		   }
	   int index=0;
	   //将每个桶中的值顺序放回原数组,遍历所有的桶
	   for (int i = 0; i < bucket.length ; i++) {
		   //如果当前桶个数不为0
		   if (bucketmany[i]!=0) {
			   for (int j=0;j<bucketmany[i];j++) {
				   //把桶里的数据放回原数组
                   a[index]=bucket[i][j];
                   index++;
                   }
			   }
		   //第i轮,将当前桶置空,因为上面已经将值赋给原数组了
		   bucketmany[i]=0;
		   }
	   }
   System.out.println("基数排序后:" + Arrays.toString(a));
}

六、几种排序算法的性能比较

排序算法 时间复杂度 空间复杂度 稳定性
平均情况 最好情况 最坏情况
直接插入排序 O(n^2) O(n) O(n^2) O(1) 稳定
希尔排序 与增量序列选择有关 O(1) 不稳定
冒泡排序 O(n^2) O(n^2) O(n^2) O(1) 稳定
快速排序 O(nlog2n) O(nlog2n) O(n^2) O(log2n) 不稳定
直接选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定
归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定
基数排序 O(d(n+rd)) O(d(n+rd)) O(d(n+rd)) O(rd) 稳定

猜你喜欢

转载自blog.csdn.net/weixin_49851451/article/details/126174386