必学的排序算法

排序算法

冒泡排序

说明

冒泡排序是交换类的排序算法,它的排序是通过一系列的交换动作完成的.
它会依次比较相邻的元素之间的值的大小,以升序排序,如果前边的值大于后边的值,则交换这两个相邻的元素的值.每一轮都会有一个较大值移动到后面,经过n-1轮排序,最终整个数组变成一个有序数组.

时间复杂度 O(n^2)
最好情况下的时间复杂度 O(n)

代码实现

	/**
	 * 冒泡排序(升序) 时间复杂度为平方阶
	 *
	 * @param arr 要排序的数组
	 *            数值小的向前移动,数值向后移动,依次交换顺序.
	 *            如果在某一轮排序中,没有发生过元素的顺序的交换,则可以认为此时的数组已经是一个有序数组了
	 */
	public void bubbleSort(int[] arr) {
		int temp = 0;
		boolean flag = false;
		for (int i = 0; i < arr.length - 1; i++) {
			flag = false;
			for (int j = 0; j < arr.length - i - 1; j++) {
				if (arr[j] > arr[j + 1]) {
					flag = true;
					temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
			if (!flag) {
				break;
			}
		}

	}

选择排序

说明

选择和冒泡有些类似,从头至尾扫描整个数组,找出最小的元素和前边的第一个元素交换,接着从剩下的元素中
继续这种选择和交换,最终使整个数组有序.

代码实现

/**
	 * 选择排序
	 * 每次开始的时候,假定当前位置的元素就是最小值,
	 * 从当前的下一个位置开始遍历剩余的所有元素,如果遍历到的元素比当前假定的最小值还要小,
	 * 那么就交换二者的位置,这样每一轮就有有一个较小的值向前移动,重复元素个数-1轮即可完成排序
	 *
	 * @param arr 待排序的数组
	 */
	public void selectSort(int[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			int min = arr[i];
			// j = i+1 ,不需要和自己比较了
			for (int j = i + 1; j < arr.length; j++) {
				if (min > arr[j]) {
					arr[i] = arr[j];
					arr[j] = min;
					min = arr[i];
				}
			}
		}

	}

插入排序

说明

我这里的插入排序借助于一个临时数组实现的,
将原数组的第一个元素存入到这个临时数组中,临时数组中只有一个元素,那么它也是一个有序序列.
循环原数组的所有元素,每一次都会将原数组中的一个元素插入到有序序列的适当位置上去,直到原数组的所有元素都插入到了临时数组为止.


代码实现

/**
	 * 插入排序(升序)
	 * 将无序列表中的数据插入到一个有序列表中,在插入的过程中完成排序
	 * 如果插入的值要小于有序列表的最后一个有效数据,
	 * 则将前面所有大于当前要插入数据的元素向后移动一个位置,直到找到小于当前插入的值的位置,
	 * 则直接让当前元素在这个元素后面插入,直到所有的元素都插入到这个有序列表后,就已经完成了排序
	 *
	 * @param arr 待排序数组
	 */
	public int[] insertSort(int[] arr) {
		int[] newArr = new int[arr.length];
		// 创建一个有序列表,列表的第一个元素就是arr的第一个元素,此时一个元素自成有序列表
		System.arraycopy(arr, 0, newArr, 0, 1);
		int index = 0;
		int value = 0;
		for (int i = 1; i < arr.length; i++) {
			// 每次都需要重置临时数组的index值,保证每次都是从这个数组的最后开始比较
			index = i - 1;
			value = arr[i];
			// 如果当前要插入的值小于index位置保存的值,则需要向前查找,
			// 直到找到大于或者没有找到比当前插入值小的位置时,就可以将当前的值直接插入
			while (index >= 0 && value < newArr[index]) {
				newArr[index + 1] = newArr[index];
				index--;
			}
			newArr[index + 1] = value;
		}
		return newArr;
	}

希尔排序

说明

希尔排序又叫缩小增量排序,其本质还是插入排序,其实现就是将整个待排序的序列分为若干个子序列,
分别对这若干个子序列进行插入排序,每一趟排序都会让整个序列更加有序,最后一次排序就是在整个序列基本有序的情况下进行一次插入排序,效率非常高.

代码实现

	/**
	 * 希尔排序---交换法
	 *
	 * @param arr 待排序的数组
	 */
	public void shelloSort(int[] arr) {
		int temp = 0;
		// 依照数组元素的个数/2来计算步长,每计算一次,数组就会变得更加有序,
		// 直到步长为1,就是在进行交换,是数组变成一个有序的序列
		for (int step = arr.length / 2; step > 0; step /= 2) {
			for (int i = step; i < arr.length; i++) {
				for (int j = i - step; j >= 0; j -= step) {
					if (arr[j] > arr[j + step]) {
						temp = arr[j];
						arr[j] = arr[j + step];
						arr[j + step] = temp;
					}
				}
			}
		}
	}


	/**
	 * 希尔排序---插入排序
	 * 依照数组元素的个数对数组不断地分组,每一次分组就对分组的每一个子序列进行一次插入排序
	 * 直到步长为1,对数组的所有元素进行一次插入排序,这样整个数组就是有序的序列了
	 *
	 * @param arr 待排序的数组
	 */
	public void shelloSortInsert(int[] arr) {
		int insertIndex = 0;
		int value = 0;
		// 最外层循环负责分组
		for (int step = arr.length / 2; step > 0; step /= 2) {
			// 循环处理每一个分组中所有子序列
			for (int i = step; i < arr.length; i++) {
				insertIndex = i;
				value = arr[i];
				// 如果左边的值大于右边的值,就需要将左边的值插入到右边对应的位置上去,
				// 并且将索引移动到左边元素的位置上
				if (arr[insertIndex] < arr[insertIndex - step]) {
					while (insertIndex - step >= 0 && value < arr[insertIndex - step]) {
						arr[insertIndex] = arr[insertIndex - step];
						insertIndex -= step;
					}
					arr[insertIndex] = value;
				}

			}
		}
	}

快速排序

说明

快速排序属于交换排序的一种.
执行流程:
每一轮选取一个随机元素(头元素或者尾都可以)作为枢轴(pivot),将序列中比枢轴小的元素移动到枢轴左边
比枢轴大的元素移动到枢轴后边,当本轮的排序完成后,就可以得到一组更短的子序列(也有可能只有一个子序列),它们将成为下一轮排序的初始序列.


代码实现

/**
	 * 快速排序
	 * 有两个指针,一左一右,可以随意选取一个值作为pivot参照值,
	 * 如我这里选取用left位置的元素作为参照值,
	 * 首先从最右端开始扫描,找到小于pivot的元素,将其插入到左边指针left所在的位置,
	 * 然后移动左指针,找到大于pivot值的元素,将其插入到右指针right的位置,
	 * 如此循环,直到left和right两个指针相遇,此时左边的元素就都是小于pivot的,右边都是大于pivot的,
	 * 然后递归对左子序列和右子序列进行排序,最终就可以得到一个有序数组.
	 *
	 * @param arr   待排序数组
	 * @param left  左指针
	 * @param right 右指针
	 */
	public void quickSort(int[] arr, int left, int right) {
		int l = left;
		int r = right;
		if (l >= r) {
			return;
		}
		int pivot = arr[l];
		while (l < r) {
			// 右指针,因为pivot取的是左边的第一个值,因此需要先从右边开始扫描
			// 当扫描到比pivot小的值才会停下,并将右边的值插入到左边l的位置上
			while (l < r && arr[r] >= pivot) {
				r--;
			}
			if (l < r) {
				arr[l] = arr[r];
				l++;
			}
			//左指针,从左边扫描大于pivot值的元素,有就将其插入到右边r的位置上,
			// 并且无论是左边扫描到还是右边扫描到插入元素之后都需要更新索引,以保证从修改之后的位置扫描
			while (l < r && arr[l] < pivot) {
				l++;
			}
			if (l < r) {
				arr[r] = arr[l];
				r--;
			}
			// 当l==r时,证明此时两边的子序列都是满足左边的比pivot小,右边的比pivot大,
			// 当前二者相遇的位置就是pivot需要插入的位置
			if (l == r) {
				arr[l] = pivot;
			}
		}
		// 对左子序列继续排序,因为上面只能保证左边的小于pivot,右边的大于pivot,但是不能保证元素是有序的
		// 判断左子序列是否还有递归的空间,如果有指针已经小于0索引,那么压根就没有递归的必要了
		if (r > left) {
			quickSort(arr, left, l - 1);
		}
		// 如果左指针已经和arr.length-1重合,也没有递归的必要了
		if (l < right) {
			quickSort(arr, l + 1, right);
		}

	}

归并排序

说明

归并排序,归并排序使用的就是分治法,将一个序列分解成两个子序列,每一个子序列还可以继续分成两个子序列,直到只有一个元素时才停止,最终将排好序的子序列合并得到一个有序序列.

代码实现

/**
	 * 
	 * 将数组分解成若干个子序列
	 *
	 * @param arr   原数组
	 * @param temp  临时数组
	 * @param left  开始索引
	 * @param right 结束索引
	 */
	public void mergeSort(int[] arr, int[] temp, int left, int right) {
		if (left < right) {
			int mid = (left + right) / 2;
			// 对左子序列进行归并排序
			mergeSort(arr, temp, left, mid);
			// 对右子序列进行归并排序
			mergeSort(arr, temp, mid + 1, right);
			merge(arr, temp, left, right, mid);
		}
	}

	/**
	 * 合并有序序列
	 *
	 * @param arr   原数组
	 * @param temp  临时数组
	 * @param left  开始索引
	 * @param right 结束索引
	 * @param mid   中间索引,用来区分左右的边界,左子序列left-mid,右mid+1 - right
	 */
	public void merge(int[] arr, int[] temp, int left, int right, int mid) {
		int l = left;
		int r = mid + 1;
		int tempIndex = 0;
		// 对元素做排序处理
		while (l <= mid && r <= right) {
			// 在向临时数组插入元素时,需要比较两端指针指向的元素的大小,小的先插入
			if (arr[l] < arr[r]) {
				temp[tempIndex++] = arr[l++];
			} else {
				temp[tempIndex++] = arr[r++];
			}
		}
		// 将左边的子序列的剩余数据填充到临时数组中
		while (l <= mid) {
			temp[tempIndex++] = arr[l++];
		}
		// 将右子序列的剩余数据填充到临时数组中去
		while (r <= right) {
			temp[tempIndex++] = arr[r++];
		}
		tempIndex = 0;
		l = left;
		// 将数据拷贝回原数组
		while (l <= right) {
			arr[l++] = temp[tempIndex++];
		}
	}

基数排序

说明

基数排序就是典型的空间换时间策略.
核心就是分配和收集,分配就是将每个元素分配到它的指定位数的值对应的桶中,

核心思想:
将所有的待排序元素看做是拥有同样数位长度的元素,数位较短的前面补0,然后从最低位开始,依次进行排序,
位数的值就对应着元素应该被分配到哪个桶中,每一轮排序过后在重新按照桶的顺序收集,就这样从最低位的比较到最高位的比较完成之后,整个序列就是一个有序序列了.

代码实现

/**
	 * 求个数的值: 	如 52 	52 / 1 % 10 = 2
	 * 求十分位的值:	如 52 	52 / 10 % 10 = 5
	 * 求百分位的值:  如 520 	520 / 100 % 10 = 5
	 * 求千分位的值:  如 5200  5200 / 1000 % 10 = 5
	 *
	 * @param arr 待排序数组
	 */
	public void radixSort(int[] arr) {
		// 10个桶0-9,其序号就对应着存储指定位数的值等于序号的元素,
		// 如十分位为5的520,其就应该存储在2号桶中
		int[][] buckets = new int[10][arr.length];
		// 用来记录10个桶插入元素的索引值变化
		int[] bucketIndex = new int[10];
		int max = arr[0];
		// 查找数组的最大值,因为基数排序需要最大值的位数轮才能完成排序
		for (int i = 0; i < arr.length; i++) {
			if (arr[i] > max) {
				max = arr[i];
			}
		}
		int maxLength = (max + "").length();
		// 这个是用来求指定位数的值要使用到的辅助变量
		int base = 1;
		int tempValue = 0;
		while (maxLength-- > 0) {
			int arrIndex = 0;
			// 将元素放入到不同的桶中
			for (int i = 0; i < arr.length; i++) {
				tempValue = arr[i] / base % 10;
				buckets[tempValue][bucketIndex[tempValue]] = arr[i];
				bucketIndex[tempValue] = ++bucketIndex[tempValue];
			}
			// 按照桶的顺序取出所有的元素放回到原数组中
			for (int i = 0; i < buckets.length; i++) {
				// 判断指定桶的索引值是否为0,不等于0则是有元素需要取出
				if (bucketIndex[i] >= 0) {
					for (int j = 0; j < bucketIndex[i]; j++) {
						arr[arrIndex++] = buckets[i][j];
					}
				}
			}
			// 重置bucketIndex数组,否则会读取到脏数据,影响最终的排序结果
			Arrays.fill(bucketIndex, 0);
			base *= 10;
		}

	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值