DataStructures - 01:排序算法

一、冒泡排序算法

1、冒泡排序思想:

对要排序的序列从前到后依次比较相邻的元素的值,若发现逆序则交换,使值较大的元素从前往后移。

2、冒泡排序图解:

  1. 总共arr.length-1次大的循环
  2. 每一次大循环代表一趟排序,每次排序比较相邻两个两个元素的值,比较后两个元素都后移(这里想象成两个索引都后移)
    在这里插入图片描述

3、冒泡排序算法:

public static void bubbleSort(int[] arr){
    //第一层循环代表总共进行多少趟排序
    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;
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}
public class _1_冒泡排序算法 {
    public static void main(String[] args) {
        int arr[] = {3, 9, -1, 10, 20};
        System.out.println(Arrays.toString(bubbleSort((arr))));
    }
}

4、冒泡排序算法优化:

如果某趟排序中没有发生一次交换,就提前结束冒泡排序

public static void bubbleSort(int[] arr){
    //第一层循环代表总共进行多少趟排序
    for(int i=0;i<arr.length-1;i++){
        boolean flag = false;
        //第二层循环代表每趟进行多少次比较
        for(int j=0;j<arr.length-1-i;j++){
            if(arr[j]>arr[j+1]){
                flag = true;
                int temp;
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
        if(!flag){
            break;
        }
    }
}

二、选择排序算法

1、选择排序思想:

1. 第1次从arr[0]~arr[n-1]中选取最小的值与arr[0]交换
2. 第2次从arr[1]~arr[n-1]中选取最小的值与arr[1]交换
3. 第3次从arr[2]~arr[n-1]中选取最小的值与arr[2]交换
以此类推。。。。。。。
4. 第n-1次从arr[n-2]~arr[n-1]中选取最小的值与arr[n-2]交换

2、选择排序图解:
在这里插入图片描述
具体步骤:

  1. 共有arr.length-1次排序
  2. 先假定当前这个数是最小数,然后和后面的每个数进行比较,如果发现后面的有比当前数更小,就重新确定最小数,并得到下标
  3. 遍历到数组最后时,就得到本轮最小数和下标
  4. 交换当前这个数与最小数

3、选择排序算法实现:

扫描二维码关注公众号,回复: 10660542 查看本文章
public static void selectSort(int[] arr){
    for(int i=0;i<arr.length-1;i++) {
        //假定当前数为最小数,记录最小数和对应索引
        int minIndex = i;
        int min = arr[i];

        //将arr[i]与后面的数比较,如果后面的数更小重置min和对应下标
        for (int j = i + 1; j < arr.length; j++) {
            if (min > arr[j]) {
                min = arr[j];
                minIndex = j;
            }
        }

       //交换arr[i]和最小值
       if(minIndex!=i){
           arr[minIndex] = arr[i];
           arr[i] = min;
       }
    }
}

测试代码:

public class _2_选择排序算法 {
    public static void main(String[] args) {
        int[] arr = {34,26,78,60,15};
        System.out.println(Arrays.toString(selectSort(arr)));
    }
}

三、插入排序算法

1、插入排序思想:

把n个待排序的元素看成一个有序表和一个无序表,开始时有序表只包含一个元素,无序表中含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码一次与有序元素的排序码比较,将他插入到有序表中的适当位置,使之成为新的有序表。

2、插入排序图解:
在这里插入图片描述3、插入排序算法实现:

public static void insertSort(int[] arr){
   for(int i=1;i<arr.length;i++){
       int insertValue = arr[i];
       int insertIndex =i-1;

       while(insertIndex>=0 && insertValue<arr[insertIndex]){
           arr[insertIndex+1] = arr[insertIndex];
           insertIndex--;
       }
       if(insertIndex+1!=i){
           arr[insertIndex+1] = insertValue;
       }  
   }
}

在这里插入图片描述
建议Debug来理解。

四、希尔排序算法

1、希尔排序思想:

插入排序,当需要插入的数较小是,后移的次数就会增多,对效率有所影响。

希尔排序是一种改进的插入排序,它不需要执行太多的数据搬移操作。可以减少插入排序法中数据搬移的次数,以加速排序的进行。排序的原则是将数据区分成特定间隔的几个小区块,以插入排序法排完区块内的数据后,再逐渐减少间隔的距离。

2、希尔排序图解:
在这里插入图片描述
3、希尔排序算法(交换式)实现:

对有序序列插入时使用交换法(不推荐,和我们的希尔排序思想违背):

public static void shellSort(int[] arr){
   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]){
                   int temp;
                   temp = arr[j];
                   arr[j] = arr[j+gap];
                   arr[j+gap] =temp;
               }
           }
       }
   }
}

测试:结果发现使用交换式,效率反而降低了,时间复杂度更高了

public class _4_希尔排序算法 {
    public static void main(String[] args) {
        int[] arr = {63,92,27,36,45,71,58,7};
        System.out.println(Arrays.toString(shellSort(arr)));
    }
}

4、希尔排序算法(移位式)实现:

对有序序列插入时使用移动法:对上面交换法的改进(在每一组内进行插入排序,正宗的希尔排序,即插入排序的高版本)

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 insertIndex = i;
           int insertValue = arr[insertIndex];//定义待插入的数,并保存

           while(insertIndex-gap>=0 && insertValue<arr[insertIndex-gap]){
               arr[insertIndex] = arr[insertIndex-gap];
               insertIndex-=gap;
           }
           arr[insertIndex] = insertValue;
       }
   }
}

五、快速排序算法

1、快速排序算法思想:

先在数据中找到一个虚拟的中间值,并按此中间值将所有打算排序的数据分为两部分。其中,小于中间值的数据放在左边,大于中间值的数据放在右边,再以同样的方式分别处理左、右两边的数据,直到排序完为止。

快速排序的步骤:
(1) 选择基准值。
(2) 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。
(3) 对这两个子数组进行快速排序。

2、快速排序图解:
在这里插入图片描述
3、快速排序算法实现:

public static void quickSort(int[] arr, int left, int right) {
    int l = left; //左下标
    int r = right; //右下标
    int pivot = arr[(left + right) / 2];//pivot 中轴值
    int temp = 0;
    
    //while循环的目的是让比pivot 值小放到左边比pivot 值大放到右边
    while (l < r) {
        //在pivot的左边一直找,找到大于等于pivot值,才退出
        while (arr[l] < pivot) {
            l += 1;
        }
        //在pivot的右边一直找,找到小于等于pivot值,才退出
        while (arr[r] > pivot) {
            r -= 1;
        }
        //如果l >= r说明pivot 的左右两的值,已经按照左边全部是
        //小于等于pivot值,右边全部是大于等于pivot值
        if (l >= r) {
            break;
        }
        //交换
        temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;

        //如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
        if (arr[l] == pivot) {
            r -= 1;
        }
        //如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
        if (arr[r] == pivot) {
            l += 1;
        }
    }
    // 如果 l == r, 必须l++, r--, 否则为出现栈溢出
    if (l == r) {
        l += 1;
        r -= 1;
    }
    //向左递归
    if (left < r) {
        quickSort(arr, left, r);
    }
    //向右递归
    if (right > l) {
        quickSort(arr, l, right);
    }
}

在这里插入图片描述

六、归并排序算法

1、算法思想:
在这里插入图片描述
在这里插入图片描述
2、算法实现:

//分+合方法
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; // 初始化i, 左边有序序列的初始索引
    int j = mid + 1; //初始化j, 右边有序序列的初始索引
    int t = 0; // 指向temp数组的当前索引

    //(一)
    //先把左右两边(有序)的数据按照规则填充到temp数组
    //直到左右两边的有序序列,有一边处理完毕为止
    while (i <= mid && j <= right) {//继续
        //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
        //即将左边的当前元素,填充到 temp数组
        //然后 t++, i++
        if(arr[i] <= arr[j]) {
            temp[t] = arr[i];
            t += 1;
            i += 1;
        } else { //反之,将右边有序序列的当前元素,填充到temp数组
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }
    }

    //(二)
    //把有剩余数据的一边的数据依次全部填充到temp
    while( i <= mid) { //左边的有序序列还有剩余的元素,就全部填充到temp
        temp[t] = arr[i];
        t += 1;
        i += 1;
    }

    while( j <= right) { //右边的有序序列还有剩余的元素,就全部填充到temp
        temp[t] = arr[j];
        t += 1;
        j += 1;
    }

    //(三)
    //将temp数组的元素拷贝到arr
    //注意,并不是每次都拷贝所有
    t = 0;
    int tempLeft = left; //
    //第一次合并 tempLeft = 0 , right = 1 //  tempLeft = 2  right = 3 // tL=0 ri=3
    //最后一次 tempLeft = 0  right = 7
    while(tempLeft <= right) {
        arr[tempLeft] = temp[t];
        t += 1;
        tempLeft += 1;
    }
}

这个算法运用了递归调用,如果想真正的理解他,建议手动Debug几遍,一遍不懂就多几次。

七、基数排序算法

1、基数排序思想:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2、算法实现:

//基数排序方法
public static void radixSort(int[] arr) {
	//1. 得到数组中最大的数
	int max = arr[0]; 
	for(int i = 1; i < arr.length; i++) {
		if (arr[i] > max) {
			max = arr[i];
		}
	}
	//得到最大数是几位数
	int maxLength = (max + "").length();

	//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
	//1. 二维数组包含10个一维数组
	//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
	//3. 名明确,基数排序是使用空间换时间的经典算法
	int[][] bucket = new int[10][arr.length];
	
	//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
	//比如:bucketElementCounts[0] , 记录的就是  bucket[0] 桶的放入数据个数
	int[] bucketElementCounts = new int[10];
	
	for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
		//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
		for(int j = 0; j < arr.length; j++) {
			//取出每个元素的对应位的值
			int digitOfElement = arr[j] / n % 10;
			//放入到对应的桶中
			bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
			bucketElementCounts[digitOfElement]++;
		}
		//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
		int index = 0;
		//遍历每一桶,并将桶中是数据,放入到原数组
		for(int k = 0; k < bucket.length; k++) {
			//如果桶中,有数据,我们才放入到原数组
			if(bucketElementCounts[k] != 0) {
				//循环该桶即第k个桶(即第k个一维数组), 放入
				for(int l = 0; l < bucketElementCounts[k]; l++) {
					//取出元素放入到arr
					arr[index++] = bucket[k][l];
				}
			}
			//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
			bucketElementCounts[k] = 0;
		}
	}
}

同样那句话,理解算法最快速的方法是在理解思路的基础上,多进行debug。

发布了716 篇原创文章 · 获赞 130 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_42764468/article/details/105175512
今日推荐