数据结构与算法—排序总结

一、冒泡排序:

1.介绍:冒泡排序将最大的值像泡泡一样一个一个的冒出来,其基本思想是从第一个数开始和它的下一个数开始比较,如果逆序就将它们的位置互换,直到比较到之后一个数值,这样最大的那个数就到了最后;接下来重复之前的步骤,知道最后剩下一个数。
2.过程演示:
第一轮:
0)5 、8 、3、7、0
1)5、8、3、7、0
2)5、3、8、7、0
3)5、3、7、8、0
1)5、3、7、0、||8
第一轮结束后8就到了最后的位置

第二轮过程和第一轮的过程类似,不一样的地方是只要比较前四个数值即可,第二轮的结果是:
3、5、0、||7、8
第三轮:
3、0、||5、7、8
第四轮:
0、||3、5、7、8
只剩一个数值就排序完成。
3.代码实现:

public static void bubbleSort(int[] arr) {
		// 冒泡排序 的时间复杂度 O(n^2), 自己写出
		int temp = 0; // 临时变量
		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;
					temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
			//System.out.println("第" + (i + 1) + "趟排序后的数组");
			//System.out.println(Arrays.toString(arr));

			if (!flag) { // 在一趟排序中,一次交换都没有发生过
				break;
			} else {
				flag = false; // 重置flag!!!, 进行下次判断
			}
		}
	}

代码理解:有两层for循环,最里面的是每一轮对数值的遍历及交换,外层是进行遍历;
代码优化:添加flag值进行判断有没有进行交换,若没有则说明这一轮就提前完成了排序,直接break;
4.时间复杂度:两层for循环,所以为n^2

二、选择排序

1.介绍:也叫内部排序法,意思是先对一串数字的第二个及第二个以后的数和第一个数进行比较,如果调出来最小的那个与第一个交换位置;然后按照这个思路再与第二个进行交换;依次往下。
2.过程演示:
0)8、5、3、1、9、0
0和8互换位置
1)0、|| 5、3、1、9、8
1和5互换位置
2)0、1、|| 3、5、9、8
没有比3小的不换
3)0、1、3、|| 5、9、8
没有比5小的不换
4)0、1、3、5、|| 9、8
8、9互换位置
5)0、1、3、5、8、|| 9
3.代码实现:

public static void selectSort(int[] arr) {
		
		
		
		//在推导的过程,我们发现了规律,因此,可以使用for来解决
		//选择排序时间复杂度是 O(n^2)
		for (int i = 0; i < arr.length - 1; i++) {
			int minIndex = i;
			int min = arr[i];
			for (int j = i + 1; j < arr.length; j++) {
				if (min > arr[j]) { // 说明假定的最小值,并不是最小
					min = arr[j]; // 重置min
					minIndex = j; // 重置minIndex
				}
			}

			// 将最小值,放在arr[i], 即交换
			if (minIndex != i) {
				arr[minIndex] = arr[i];
				arr[i] = min;
			}
		}
}

代码理解及要点:在第二层for循环的外边要设置两个变量一个要记录最小值,一个要记录最小值的下标。、
时间复杂度:两层for循环,所以为n^2

三、插入排序

1.介绍:大概意思是从一堆无序的数字中依次挑选出来插入有序的数字中。
2.过程演示:
0)9、6、0、3、8
1)将6取出插入9中:
6、9、||0、3、8
2)将0取出插入前边:
0、6、9、||3、8
3)将3取出,插入
0、3、6、9、||8
4)取出8,插入
0、3、6、8、9
完毕
3.代码演示:

public static void insertSort(int[] arr) {
		int insertVal = 0;
		int insertIndex = 0;
		//使用for循环来把代码简化
		for(int i = 1; i < arr.length; i++) {
			//定义待插入的数
			insertVal = arr[i];
			insertIndex = i - 1; // 即arr[1]的前面这个数的下标
	
			// 给insertVal 找到插入的位置
			// 说明
			// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
			// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
			// 3. 就需要将 arr[insertIndex] 后移
			while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
				arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
				insertIndex--;
			}
			// 当退出while循环时,说明插入的位置找到, insertIndex + 1
			// 举例:理解不了,我们一会 debug
			//这里我们判断是否需要赋值
			if(insertIndex + 1 != i) {
				arr[insertIndex + 1] = insertVal;
			}
	
			//System.out.println("第"+i+"轮插入");
			//System.out.println(Arrays.toString(arr));
		}
}

代码解析:这段代码的主要思想是while循环的内容:while的判定语句的意思是,如果待比较的数字存在的话并且带比较的数字比待插入的数字大,那么执行循环语句;xian
循环语句的意思是:将比较的数字往后挪一位,执行完毕的情况举例:执行循环语句前:6、9、||0、3、8
执行循环语句后:6、9、||9、3、8;此时的下标已经指到了6的那里(不要担心0去哪里了,因为之前已经保存到insertVal里面了),然后继续执行语句:
6、||6、9、3、8;然后下标指向-1,不符合条件,执行下面的if语句,下标加一不等于待插入的下标,直接赋值即可。
4.时间复杂度:

希尔排序

1.介绍:希尔排序是插入排序的一个升级版,即将待排序的数字分成几组,然后组内再进行插入排序,再不断减少分组,再进行插入排序;这样做的好处是比插入排序的效率更高;
2.演示过程:
(如图)
在这里插入图片描述
3.代码演示:

public static void shellSort2(int[] arr) {
		
		// 增量gap, 并逐步的缩小增量
		for (int gap = arr.length / 2; gap > 0; gap /= 2) {
			// 从第gap个元素,逐个对其所在的组进行直接插入排序
			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;
					}
					//当退出while后,就给temp找到插入的位置
					arr[j] = temp;
				}

			}
		}
	}

代码解析:最外边的一层for循环是将数字分组的分组数,不断除以二,不断减少分组的数量最后为一组;第二层for循环是不断遍历每一组,i每加一是遍历同层次的下一组;第二层for循环就是对每一组的数据进行一个插入排序。
4.时间复杂度:

快速排序

1.介绍:快速排序是对冒泡排序的一个升级,其基本思想是选取这个数组最中间的一个数为基本数,比它大的排在右边,比他小的排在左边,然后再对左右两边以同样的方法进行排序
2.排序演示:
0)2、9、1、3、6、0、7、5、4、8
第一步选取length/2为标准值进行排序,标准值为6
1)2、1、3、0、5、4、6 、 9、7、8
第二步在左右两边经行同样的操作
2)2、1、0、3 、5、4、67 、9、8
3)0、1 、2、3 、4、567 、8、9
排序完毕
3.代码演示:

public static void quickSort(int[] arr,int left, int right) {
		int l = left; //左下标
		int r = right; //右下标
		//pivot 中轴值
		int pivot = arr[(left + right) / 2];
		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);
		}	
	}

代码解析:要注意的地方是while语句里面没有等号,所以***l和r不会超过中间值***,如果左边要换的数比右边多的话那么就和中间值换
4.时间复杂度:

归并排序

1.介绍:归并排序主要是两个阶段,分和治;分就是将数组不断等分直至两两一组,治就是分别将这两个排序然后再和后边的合并再排序,直到最后将整个数组合并成一个数组;
2.过程演示:
在这里插入图片描述
以最后一次合并为例
在这里插入图片描述
在这里插入图片描述
3.代码演示:
分+合

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;
		}
		
	}

4.时间复杂度:

基数排序

1.介绍:我觉得这个排序最为神奇,它是桶排序的一个扩展,之所以认为它神奇是因为它没有像前几次排序一样去比较大小就将顺序排了出来。基本思路是:事先放好从0——9的10个桶,然后将数组的每一个数字按照从个位的数字依次放入对应的桶,然后再按顺序将桶里的数字取出来,再按十位数字的大小放入桶中,依次类推。
2.过程演示:
在这里插入图片描述
在这里插入图片描述
3.代码实现:

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 < bucketElementCounts.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;
				
			}
			//System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr));
			
		}
}

使劲按复杂度:这个代码不好理解
先对循环设置的变量进行解释:规定了一个二维数组(表示10个桶,然后每列是每个桶放的数字,这个桶行length是10,列length是数组中数字的个数,所以占用的空间比较大,以空间的代价换取时间)
再规定一个一维数组表示每个桶中放数据的个数,所以此数组的长度为10;
将数据放到桶中的过程:先将对应位数的数值取出,然后放到相应的桶中,然后再将桶中个数加1(先前规定的一维数组);
取数据的过程:在对应的桶中取该桶拥有的数据(个数在先前规定的一维数组中记录了),取完之后再将那个一维数组的数据都设为0。这步很关键;将取到的数据重新写入到数组中;
例如:int arr[] = { 53, 3, 542, 748, 14, 214,20};
第一次放完数据的桶:
在这里插入图片描述
第二次放完数据的桶:
在这里插入图片描述
注意:发现里面有多余的数据,但是无妨!!因为我们有一维数组在记录每次放数据时每个桶的数据总数,它会把之前的数据覆盖掉,如果没覆盖则这个数据也不用取;

第三次放完数据的桶:
在这里插入图片描述
时间复杂度:

对比

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值