算法 经典的八大排序算法详解和代码实现
排序算法的介绍
排序也称排序算法,排序时将一组数据,依指定的顺序进行排列的过程
排序的分类
内部排序,指将需要处理的所有数据都加载到内部内存中进行排序
外部排序,数据量很大,无法全部加载到内存中,需要借助外部存储进行排序,内存和外存结合
算法的时间复杂度
时间频度
时间频度:一个算法花费的时间与算法中语句的执行次数成正比,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。即为T(n).
示例
比如计算1-100所有数字之和,我们设计两种算法:
1.使用for循环计算
int total=0;
int end=100;
for(int i=1;i <=end;i++){
total+=i;
}
使用for循环的时间频度:T(n)=n+1
2.直接计算
total=(1+end)*end/2;
直接计算的时间频度:T(n)=1
图表理解时间复杂度的特点
1.常数项可以忽略
2.低次项可以忽略
3.系数可以忽略
随着n增大的结果如下图:
结论:
2n+20和2n随着n变大,执行曲线无限接近,20可以忽略
3n+10和3n随着n变大,执行曲线无限接近,10可以忽略
结论:
算式随着n变大,同次方项执行曲线无限接近,低次项可以忽略
结论:
随着n变大,同次方项曲线近似重合,系数可以忽略
时间复杂度
1)一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用T(n)表示,若有个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度,简称时间复杂度。
2)T(n)不同,但时间复杂度可能相同。如:T(n)=n2+7n+6与T(n)=3n2+2n+2它们的T(n)不同,但是时间复杂度相同都为O(n^2)
3)计算时间复杂度的方法:
- 用常数1代替运行时间中的所有加法常熟
- 修改后的运行次数函数中,只保留最高阶项
- 去除最高阶项的系数
常见的时间复杂度
空间复杂度
空间复杂度该算法所耗费的存储空间
在做算法分析时,主要讨论的是时间复杂度。
排序算法的时间复杂度
冒泡排序
基本思想
基本思想:通过对待排序序列从前向后,依次比较元素相邻元素的值,若发现逆序则变换,使值较大的元素逐渐从前移向后部,就像水底的气泡一样逐渐向上冒;
图解
小结:
- 一共进行数组长度的减1次循环
- 每一趟排序的次数在逐渐减少
- 如果相邻的逆序就交换
代码示例
public class BubbleSort {
public static void main(String[] args) {
int arr[] = {11, 3, 17, 5, 9, 20,-1};
System.out.println("排序前:");
System.out.println(Arrays.toString(arr));
System.out.println("过程展示:");
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 temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println(Arrays.toString(arr));
}
System.out.println("排序后:");
System.out.println(Arrays.toString(arr));
}
}
结果展示
代码优化
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {11, 3, 17, 5, 9, 20,21};
System.out.println("排序前:");
System.out.println(Arrays.toString(arr));
System.out.println("过程展示:");
boolean flag=false;
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]) {
flag=true;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (!flag) {//在一趟排序中,一次交换都没有发生过
break;
}else{
flag=false;
}
System.out.println(Arrays.toString(arr));
}
System.out.println("排序后:");
System.out.println(Arrays.toString(arr));
}
}
结果展示
选择排序
基本思想
每次找到最大值(最小值),从最后一位(最前一位)进行交换,依次向前(向后)交换,得到一个从小到大排列的有序序列
图解
结论:
- 选择排序一共有数组的长度减1轮排序
- 每一轮排序又是一个循环
- 假定第一个数是最小数,然后和后面依次进行比较,从而确定最小数,并得到下标,然后进行交换
代码实现
public class SelectSort {
public static void main(String[] args) {
int[] arr={1,4,2,76,34,12};
System.out.println("原数据:");
System.out.println(Arrays.toString(arr));
selectSort(arr);
System.out.println("排序后:");
System.out.println(Arrays.toString(arr));
}
//选择排序
public static void selectSort(int arr[]) {
for (int i = 0; i < arr.length - 1; i++) {
int min = arr[i];
int minIndex = i;
for (int j = i+1; j < arr.length; j++) {
if (min > arr[j]) {
min = arr[j];
minIndex = j;
}
}
if (minIndex!=i) {
arr[minIndex]=arr[i];
arr[i]=min;
}
System.out.println("第"+(i+1)+"轮后~~");
System.out.println(Arrays.toString(arr));
}
}
}
结果展示
插入排序
基本思想
把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表
图解
代码实现
public class InsertSort {
public static void main(String[] args) {
int[] arr = {2, 4, 32, 12, 45, 2, 1, 4};
System.out.println("排序前:");
System.out.println(Arrays.toString(arr));
System.out.println("排序过程:");
insetSort(arr);
System.out.println("排序后:");
System.out.println(Arrays.toString(arr));
}
public static void insetSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int value = arr[i];//假设是插入的数
int index = i-1;//插入的数的前面一个数
while (index >= 0 && value < arr[index]) {
arr[index + 1] = arr[index];
index--;
}
arr[index + 1] = value;
System.out.println(Arrays.toString(arr));
}
}
}
结果展示
希尔排序
基本思想
希尔也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键字越来越多,当增量减至1时,整个文件恰被分成一组,算法并终止
图解
在这里插入图片描述
代码实现
交换法:
public class ShellSort {
public static void main(String[] args) {
int[] arr={1,4,2,3,8,5,9,6,7};
System.out.println("排序前:");
System.out.println(Arrays.toString(arr));
shellSort(arr);
System.out.println("排序后:");
System.out.println(Arrays.toString(arr));
}
private static void shellSort(int[] arr){
int temp = 0;
int count = 0;
//一共gap组
for(int gap=arr.length/2;gap>0;gap/=2){
//遍历各组中的所有元素
for(int i=gap;i < arr.length;i++){
//如果当前元素大于加上步长后的那个元素,说明交换
for(int j = i -gap;j>=0;j-=gap){
if(arr[j]>arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
System.out.println("希尔排序第"+(++count)+"轮");
System.out.println(Arrays.toString(arr));
}
}
}
移位法:
public static void shellSort2(int[] arr) {
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
System.out.println(Arrays.toString(arr));
}
}
}
结果展示
1.交换法
2.移动法
快速排序
基本思想
快速排序是对冒泡排序的一种改进;通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有的数据都要小,然后再按此方法对者两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
图解
代码实现
public class QuickSort {
public static void main(String[] args) {
int[] arr={1,5,3,-1,544,3,45,6,23,22,15};
quickSort(arr,0,arr.length-1);
System.out.println("arr"+ Arrays.toString(arr));
}
public static void quickSort(int[] arr,int left,int right){
int temp=0;
int l=left;//左下标
int r=right;//右下标
int pivot=arr[(left+right)/2];//中轴值
while (l < r) {
while (arr[l] < pivot) {
l+=1;
}
while (arr[r] > pivot) {
r-=1;
}
if (l >= r) {
break;
}
temp=arr[l];
arr[l]=arr[r];
arr[r]=temp;
if (arr[l]==pivot) {
r-=1;
}
if (arr[r]==pivot) {
l+=1;
}
}
if(l==r){
l+=1;
r-=1;
}
//向左递归
if (left < r) {
quickSort(arr,left,r);
}
//向右递归
if (right > l) {
quickSort(arr,l,right);
}
}
}
结果展示
归并排序
基本思想
本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解位就是递归拆分子序列的过程
图解
代码实现
public class MergeSort {
public static void main(String[] args) {
int[] arr={8,4,5,7,1,3,6,2};
System.out.println("排序前:"+Arrays.toString(arr));
int temp[]=new int[arr.length];
mergeSort(arr,0,arr.length-1,temp);
System.out.println("归并排序后:"+ Arrays.toString(arr));
}
//分+和方法
public static void mergeSort(int[] arr,int left,int right,int[] temp){
if(left<right){
int mid=(left+right)/2;
mergeSort(arr,left,mid,temp);
mergeSort(arr,mid+1,right,temp);
merge(arr,left,mid,right,temp);
}
}
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr,int left,int mid,int right ,int[] temp){
int i= left;
int j=mid+1;
int t=0;//指向temp数组的当前索引
while(i<=mid && j<=right){
if (arr[i]<=arr[j]) {
temp[t]=arr[i];
t+=1;
i+=1;
}else{
temp[t]=arr[j];
t+=1;
j+=1;
}
}
while (i <= mid) {
temp[t]=arr[i];
t+=1;
i+=1;
}
while (j <= right) {
temp[t]=arr[j];
t+=1;
j+=1;
}
t=0;
int tempLeft=left;
while (tempLeft <= right) {
arr[tempLeft]=temp[t];
t +=1;
tempLeft+=1;
}
}
结果展示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e47XKM5S-1640004020334)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211220192324138.png)]
基数排序
基本思想
基数排序属于‘’分配式排序‘’,又称‘’桶子法‘’或bin sort,它是通过键值的各个位的值,将要排序的元素分配至某些‘’桶‘’中,达到排序的作用
是桶排序的扩展,属于稳定性的排序
将所有带比较数值统一为同样的数位长度,数位较短的数前面补零。然后从最低位开始,依次进行依次排序。这样从最低为排序一直到最高位排序完成以后,数列就变成一个有序序列。
图解
代码实现
public class RadixSort {
public static void main(String[] args) {
int arr[]={53,3,542,748,14,214};
radixSort(arr);
}
public static void radixSort(int[] arr){
int max=arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max=arr[i];
}
}
int maxLength=(max+"").length();
int[][] bucket=new int[10][arr.length];
int[] bucketElementCounts=new int[10];
for(int j=0,n=1;j<maxLength;j++,n*=10) {
for (int i = 0; i < arr.length; i++) {
int digitOfElement = arr[i]/n % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
bucketElementCounts[digitOfElement]++;
}
int index = 0;
for (int k = 0; k < bucket.length; k++) {
if (bucketElementCounts[k] != 0) {
for (int l = 0; l < bucketElementCounts[k]; l++) {
arr[index] = bucket[k][l];
}
}
bucketElementCounts[k]=0;
}
System.out.println(Arrays.toString(arr));
}
}
}