冒泡排序
- 冒泡排序是把大的数往后冒
- 思路:通过对待排序序列,从前往后(从下标值小的往下标值大的),依次比较相邻元素的值,若发现逆序(下标小的数据值 > 小标大的数据的值)则交换,使较大的值从前往后移。
- 优化的部分:如果一趟排序下来,发现没有数据进行交换,即序列的排列已经按序。可以在排序前设置一个标识位,用来描述这趟排序有没有交换数据,若没有交换数据,则直接退出循环。
- 有序序列的下标为 [n - 2,n);无序序列的下标为[0,n - 2)
- 代码:
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {-1, 0, 10, 9, 20};
int tmp = 0;
for(int i = 0; i < arr.length - 1; i++){ // 外层循环是用来表示需要进行多少趟排序
boolean flag = false; // 标识位,用来判断是否有进行过数据交换
for(int j = 0; j < arr.length - 1 - i; j++){ // 内层的 for 循环,用来遍历数组,交换数据
if(arr[j] > arr[j + 1]){ // 这里发现逆序(即下标小的 > 小标大的,需要将下标大的往后移动,而发生交换的代码)
flag = true; // 若有发生交换,则将标识位置为 正
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
System.out.println("第" + (i + 1) + "趟排序后的数组");
System.out.println(Arrays.toString(arr));
if(!flag){
break;
}
}
}
}
冒泡排序一定要看这个,我老是忘记,这个视频真的可有意思了
选择排序
- 在选择的过程中不发生交换数据,只有在遍历一遍确定最小值后才发生交换
- 思路:第一次从 arr[0] ~ arr[n - 1] 中挑选最小的数据,然后与 arr[0] 发生交换,,第二次从 arr[1] ~ arr[n - 1] 中挑选最小的数据,然后与 arr[1] 发生交换…… 第 n - 1 次从 arr[n - 2] ~ arr[n - 1] 中挑选最小的值与 arr[n - 2] 交换。
- 优化的部分:若 最小值的下标在这趟比较中,没有变化,那么就不用发生交换了
- 有序序列的下标[0,i);无序序列的下标[i,n) i 下标是从 0 开始的
- 代码:
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
int[] arr = {8, 3, 2, 1, 7, 4, 6, 5};
System.out.println("排序前~");
System.out.println(Arrays.toString(arr));
select(arr);
}
public static void select(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]){
minIndex = j;
min = arr[j];
}
}
if(minIndex != i){
arr[minIndex] = arr[i];
arr[i] = min;
}
System.out.println("第 " + (i + 1) +" 次排序后~");
System.out.println(Arrays.toString(arr));
}
}
}
插入排序
- 插入排序是前 i 个数是有序的,将第 i + 1 个数据放到有序队列中
- 思路:把 n 个待排序的元素看成为一个有序表和一个无序表,开始时有序表中包含一个元素,无序表中包含 n - 1 个元素,排序过程中每次从无序表中取出第一个元素,把他的排序码依次与有序表中的元素进行比较,将它插入到有序表中的合适位置,使之成为新的有序表
- 有序序列的下标[0,i);无序序列的下标[i,n) i 是从下标 1 开始的
- 代码:
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = {17,3,25,14,20,9};
insert(arr);
}
public static void insert(int[] arr){
// 原始数组为 [17,3,25,14,20,9]
for(int i = 1; i < arr.length; i++){
int insertVal = arr[i]; // 用来表示准备插入的值
int insertIndex = i - 1; // 用来表示开始比较的下标值
// insertIndex >= 0 保证了下标的合法性
// insertVal < arr[insertIndex] 我们要按照从小到大的顺序排列,
// 所以必须当要插入的值 > 当前数组的值时,才算找到合适的位置;当 要插入的值 < 当前数组的值时,必须接着往前比较,直到找到有序序列的开头或者合适的位置
while(insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex]; // 把当前下标的值往后移动
insertIndex--; // 下标值往前移动
// 示例:第一趟的[17,3,25,14,20,9]经过第一次while循环判断后
// [17,17,25,14,20,9] 原本的 1 下标的值,用 insertVal 保存了,所以不用担心
}
// 退出 while 循环则说明已经找到合适的下标,或者到了有序序列的开始位置
// if 判断是优化代码
// 就是有的数据可能在此次插入中,比有序序列的数据都大,insertIndex 的值没有变化 —— 没有进入 while 循环
// 但是经过 80000 个数据的测试,其实优化后和优化前的时间所差不大
if(insertIndex != i){
// 一定要注意:这里赋值的时候下标值一定 得是 insertIndex + 1!!!!!!!!
// 1)当数据遍历完毕后,要往有序序列第一个添加时,此时insertIndex = -1
// 2) 当while循环是遇到合适的值退出时,当前 insertVal > arr[insertIndex],就算插入也要插入到它的下个位置
arr[insertIndex + 1] = insertVal;
}
System.out.println("第 " + i +" 趟插入排序后");
System.out.println(Arrays.toString(arr));
}
}
}
插入排序的代码要注意的小细节特别多,在写的时候一定要小心,多多注意当前下标在哪,要往哪插
希尔排序
- 希尔排序是插入排序进行改进后的一个更高效的版本,也称为缩小增量排序
- 思路:把记录按下标的一定增量分组,对每组使用直接插入算法排序;随着增量逐渐减小,每组包含的关键字越来越多,当增量减至 1 时,整个文件被分为一组排序后,算法便终止
- 代码:
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = {8,9,1,7,2,3,5,4,6,0};
shell(arr);
}
public static void shell(int[] arr){
int count = 0;
for(int gap = arr.length / 2; gap > 0; gap /= 2){
for(int i = gap; i < arr.length; i++){
int j = i - gap; // 插入排序中的第一个待比较的下标
int tmp = arr[i]; // 准备插入的数
while(j >= 0 && tmp < arr[j]){
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = tmp;
}
System.out.println("第 "+(++count)+ " 次排序后的数组为 = " + Arrays.toString(arr));
}
}
}
快速排序
- 在无序序列选择一个基准值,从数组两端开始遍历数组,将比 基准值 小的数放到基准值的左边,比基准值大的数放到基准值的右边
- 步骤:
- 在待排序空间选择一个基准值
- 做 partition 使得小的数在基准值左,大的数在基准值右
- 分治处理左右两个小区间。
package sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {-9, 78, 0, 23, -567, 70};
quick(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
public static void quick(int[] arr, int left, int right){ // 用的是 几数取中法 + hoare
if(left == right){
return;
}
if(left > right){
return;
}
int pivotIndex = partition(arr, left, right);
quick(arr, left, pivotIndex - 1);
quick(arr, pivotIndex + 1, right);
}
private static int partition(int[] arr, int left, int right) {
int i = left;
int j = right;
int pivot = arr[left];
while(i < j){
while(i < j && arr[j] >= pivot){
j--;
}
while(i < j && arr[i] <= pivot){
i++;
}
swap(arr, i, j);
}
// 一旦退出while循环,则说明 i 和 j 相遇了
// 交换 i 或者 j 和 left 为下标的值,因为 基准值 pivot 在left 的位置,需要把基准值换到数组中间去
swap(arr, i, left);
return i;
}
private static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
归并排序
- 先把数组分开,然后合并。我自己理解的时候,在数组用递归分解的时候,感觉有点点难理解
- 思路:利用对并的思想实现的排序方法,该算法采用经典的分治策略,将问题分成一些小的问题然后递归求解,而治的阶段,则将分的阶段得到的各答案“ 修补 ”在一起。
- 代码:
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {8,4,5,7,1,3,6,2};
int[] tmp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, tmp);
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr, int left, int right, int[] tmp){
if(left < right){
int mid = (left + right) / 2;
// 开始分解数组
// 先分解 mid 的左边
mergeSort(arr, left, mid, tmp);
// 再分解有右边的
mergeSort(arr, mid + 1, right,tmp);
// 分解好之后再合并
merge(arr, left, mid, right, tmp);
}
}
private static void merge(int[] arr, int left, int mid, int right, int[] tmp) {
int i = left; // 遍历左边部分时的下标
int j = mid + 1; // 遍历右边部分时的下标
int index = 0; // 临时数组的下标变化
while(i <= mid && j <= right){ // 当左边的下标和右边的下边都符合条件时,才可能进行下边的
if(arr[i] <= arr[j]){
tmp[index++] = arr[i++];
}else{
tmp[index++] = arr[j++];
}
}
// 左半边没有剩余的数
while(i <= mid){
tmp[index++] = arr[i++];
}
// 右边还有剩余的数
while(j <= right){
tmp[index++] = arr[j++];
}
// 将 tmp 数组的值拷贝到 arr 中
index = 0;
int tmpIndex = left;
while(tmpIndex <= right){
arr[tmpIndex++] = tmp[index++];
}
}
}
基数排序
- 这是一个典型的用空间换时间的排序
- 思路:将所有待比较数值同一为同样的数位长度,数位较短的数前面补 0,然后从最低位开始,以此进行以此排序。这样从最低为排序一直到最高位排序完成后,数列就变成一个有序序列
- 演示:
- 代码:
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args) {
int[] arr = {53,3,542,748,14,214};
radixSort(arr);
System.out.println("排序后的数组 arr=" + Arrays.toString(arr));
}
private static void radixSort(int[] arr){
// 定义一个二维数组,表示 10 个桶(0 ~9)
// 1. 二维数组包含 10 个一维数组
// 2. 每个一维数组用来保存该位数下 值 为 一维数组下标的数
// 3. 因为不了解每个一维数组中应该放入多少个数,为了防止溢出,所以每个一维数组的长度设为 arr.length
int[][] bucket = new int[10][arr.length];
// 在从桶中取数据的时候不知道要从该桶中取出多少个数据,即不知道每个桶中有多少个数据
// 所以,设置 bucketElementCounts 这个数组用来记录 每个桶中有多少个数据
int[] bucketElementCounts = new int[10];
// 想知道应该遍历多少次,就应该先知道原始数组中最大数据的位数是多少
int max = arr[0];
for(int i = 1; i < arr.length; i++){ // 1)找出原始数组中的最大值
if(max < arr[i]){
max = arr[i];
}
}
// 2)确定最大数据的位数
int maxLength = (max + "").length();
// 循环开始
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]++;
}
// for 循环结束,意味着把数据给桶中放的过程完成
// 从桶中取数据
int index = 0; // 用来定位数组的下标
for(int k = 0; k < bucketElementCounts.length; k++){
if(bucketElementCounts[k] != 0) {
for (int l = 0; l < bucketElementCounts[k]; l++) {
arr[index++] = bucket[k][l];
}
}
// 假设:这一轮在比较 个位数的值,则下一轮开始十位数的值的比较
// 在开始之前,必须将桶中数据的个数置为 0,即将 bucketElementCounts 数组的每一个置为 0
bucketElementCounts[k] = 0;
}
// for 循环结束,意味着将桶中的数据也全部取出了,则开始下一轮
}
}
}
堆排序
排序算法的总结
还有基数排序,它的时间复杂度是O(n * k),其中 n 为数据的大小,k 为桶的个数,基数排序算法也是稳定的