排序算法讲解:
选择排序:
选择排序的原理十分简单直观,通常使用两层 for 循环来实现:第一层 for 循环依次选定数组从 0 到 N 的每一个索引位置的值,
第二层 for 循环将该索引后的每个值依次与该索引的值进行比较,将较小值交换到第一层循环索引所在的位置。
这就使得第一层 for 每一次循环都是在将剩余数组的最小值排列在剩余数组的最前列,最终实现升序排列。
时间复杂度为 O(N^2)
冒泡排序:
冒泡排序同样使用双层 for 循环,第二层循环从 0 到 N 将相邻索引位置的值两两对比,将较大值交换到后一位,
第二层循环遍历完成,就将无序区的最大值推到了数组最后。
冒泡排序顾名思义逐渐将最大的值冒出来
时间复杂度为 O(N^2)
插入排序:
插入排序的原理是将数组中索引 0 既定为有序区,然后把索引 1 的值取出设定为插入值,
将插入值和索引 0 的值对比,如果索引 0 的值较大,则把索引 0 的值往后移动一位,插入值到空出的位置,则索引 0 和索引 1 就是有序的。
接着把索引 2 的值作为插入值依次往前对比,如果前面的值较大,则该值往后移动一位,直到找到一个更小值,将插入值放置到更小值之后,又形成了一个有序区。
以此类推,最终整个数组都变成有序。
时间复杂度为 O(N^2)
快速排序:
快速排序的原理是在数组中随机取一个基准数,一个最左索引 i 和一个最右索引 j 。
从索引 j 开始往左找到第一个小于基准值的值,将其赋值给 arr[i] ,然后将 i 往右移动一格;
接着从索引 i 开始往右找到第一个大于基准值的值,将其赋值给 arr[j] ,并把 j 往左移动一格。
依此类推,当 i 和 j 索引重合时停止,最后将基准值赋值给 arr[i] ,
至此 i 左边的值都比 i 小,i 右边的值都比 i 大 ,接着使用递归将左右两边的子数组分别进行快速排序,最终得到一个有序数组。
时间复杂度为 O(N*logN)
快速排序原始:
6 1 2 7 9 3 4 5 10 8
第一次:i=0 j=9 inde =a[i]=6
从右边 找小于index的数 j-- i=0 j=7 a[j]=5<index=6 a[i]=a[j]=5 i++ i=1 j=7
这时数组是
5 1 2 7 9 3 4 5 10 8
从左边 找大于index的数 i++ i=3 j=7 a[3]=7>index=6 a[j]=a[i]=7 j-- i=3 j=6
这时数组是
5 1 2 7 9 3 4 7 10 8
i=3 j=6 i<j 继续
从右边 找小于index的数 j-- i=3 j=6 a[6]=<index=6 a[i]=a[j]=4 i++ i=4 j=6
这时数组是
5 1 2 4 9 3 4 7 10 8
从左边 找大于index的数 i=4 j=6 a[5]=9>index=6 a[j]=a[i]=9 j-- i=4 j=5
这时数组是
5 1 2 4 9 3 9 7 10 8
从右边 找小于index的数 i=4 j=5 a[5]=3<index=6 a[i]=a[j]=3 i++ i=5 j=5
这时数组是
5 1 2 4 3 3 9 7 10 8
i=5 j=5 i<j不成立 a[i]=index=6
第一次遍历排序后为:
5 1 2 4 3 6 9 7 10 8
堆排序
堆排序的原理是建立一个完全二叉树,将数组的值填入其中,然后从右往左从下往上依次调整堆结构,
使父节点的值不小于其两个子节点的值,一轮调整过后,根结点的键值就是所有堆结点键值中最大者。
接着将根节点的值与最后一个叶子节点的值互换,并将得到最大值的叶子节点从二叉树中取出,置入数组的最后,然后再次调整堆结构,
依次类推,最终得到一个有序数组。
堆有最大堆也有最小堆
大根堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。
小根堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。
排序算法稳定性:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,
这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在
排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳
定的。
代码案例:
public class 排序算法 {
public static Logger logger = Logger.getLogger(排序算法.class);
/**
* 选择排序
*
* @param arr 数组
*/
public static void selectSort(int[] arr) {
logger.info("测试选择排序");
logger.info("排序算法原始数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
long starttime = Calendar.getInstance().getTimeInMillis();
//第一层for循环的arr[i]每次循环之后都会填入i之后的位置的最小值
for (int i = 0; i < arr.length - 1; i++) {//有序区
//将arr[i]之后每一个值依次与arr[i]比较,若较小则交换,保证arr[i]的值最小
for (int j = i + 1; j < arr.length; j++) {//无序区
if (arr[i] > arr[j]) {
swap(arr, i, j);
}
}
logger.info("第" + i + "次排序后数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
}
long endtime = Calendar.getInstance().getTimeInMillis();
System.out.println("冒泡排序总用时:" + (endtime - starttime) + "毫秒");
}
/**
* 冒泡排序
*
* @param arr
*/
public static void bubbleSort(int[] arr) {
logger.info("测试冒牌排序");
logger.info("排序算法原始数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
long starttime = Calendar.getInstance().getTimeInMillis();
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]) {
swap(arr, i, j);
}
}
logger.info("第" + i + "次排序后数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
}
long endtime = Calendar.getInstance().getTimeInMillis();
System.out.println("冒泡排序总用时:" + (endtime - starttime) + "毫秒");
}
/**
* 插入排序
*
* @param arr
*/
public static void insertSort(int[] arr) {
logger.info("测试插入排序");
logger.info("排序算法原始数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
long starttime = Calendar.getInstance().getTimeInMillis();
// j:插入位置的指针,insertNote:要插入的数据
int j, insertNote;
// 从数组的第二个元素开始循环将数组中的元素插入
for (int i = 1; i < arr.length; i++) {
// 设置数组中的第2个元素为第一次循环要插入的数据
insertNote = arr[i];
j = i - 1;
while (j >= 0 && insertNote < arr[j]) {
// 如果要插入的元素小于第j个元素,就将第j个元素向后移动
arr[j + 1] = arr[j];
// 将j-1,开始判断前一位与插入值的大小
j--;
}
// 直到要插入的元素不小于第j个元素,将insertNote插入到数组中
arr[j + 1] = insertNote;
logger.info("第" + i + "次排序后数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
}
long endtime = Calendar.getInstance().getTimeInMillis();
System.out.println("插入排序总用时:" + (endtime - starttime) + "毫秒");
}
/**
* 快速排序
*
* @param arr
*/
public static void quickSort(int[] arr) {
logger.info("测试快速排序");
logger.info("排序算法原始数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
if (arr.length == 0) return;
long starttime = Calendar.getInstance().getTimeInMillis();
quickSortArr(arr, 0, arr.length - 1);
long endtime = Calendar.getInstance().getTimeInMillis();
System.out.println("快速排序总用时:" + (endtime - starttime) + "毫秒");
}
private static void quickSortArr(int[] arr, int low, int high) {
if (low >= high) return;
//取左边的数作为基准数
int i = low, j = high, index = arr[i];
//i==j时跳出循环
while (i < j) {
//从最右开始向左寻找第一个小于index的数
while (i < j && arr[j] >= index) j--;
//将arr[j]放入arr[i],并将i右移
if (i < j) arr[i++] = arr[j];
//从最左开始向右寻找第一个大于index的数
while (i < j && arr[i] <= index) i++;
//将arr[i]放入arr[j],并将j左移
if (i < j) arr[j--] = arr[i];
logger.info("i=="+i+ " j=="+j);
logger.info("这时数组为:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
}
//最后将基数填入i
arr[i] = index;
logger.info("排序后数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
//至此 , i左边都比i小 ,i右边都比i大
quickSortArr(arr, low, i - 1);//递归,分治i左边
quickSortArr(arr, i + 1, high);//递归,分治i右边
}
/**
* 堆排序 大顶堆
*
* @param arr
*/
public static void heapSort(int[] arr) {
logger.info("测试堆排序");
logger.info("排序算法原始数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
//1.构建大顶堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for (int j = arr.length - 1; j > 0; j--) {
swap(arr, 0, j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr, 0, j);//重新对堆进行调整
}
}
//调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
public static void adjustHeap(int[] arr, int i, int length) {
logger.info("调整小顶堆");
int temp = arr[i];//先取出当前元素i
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {//从i结点的左子结点开始,也就是2i+1处开始
if (k + 1 < length && arr[k] < arr[k + 1]) {//如果左子结点小于右子结点,k指向右子结点
k++;
}
if (arr[k] > temp) {//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
} else {
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
logger.info("排序后数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
}
//交换元素
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
/**
* 快排
*
* @param arr
* @param left
* @param right
*/
public static void quickSortArr_bak(int[] arr, int left, int right) {
logger.info("排序算法原始数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
int pivot;
if (left < right) {
pivot = partition(arr, left, right);
quickSortArr_bak(arr, left, pivot - 1);
quickSortArr_bak(arr, pivot + 1, right);
logger.info("排序后数组:");
Arrays.stream(arr).mapToObj(item -> item + " ").forEach(System.out::print);
System.out.println("");
}
}
/**
* 将数组分成两部分,pivot之前的数都比key小,之后的都比key大
*
* @param arr
* @param left
* @param right
* @return pivot 中轴角标
*/
public static int partition(@NotNull int[] arr, int left, int right) {
//以数组的第一位作为比较基准值
int key = arr[left];
//循环遍历,直到left和right重合
while (left < right) {
//从后向前遍历,直到找到比key值小的数停止
while (left < right && arr[right] >= key) {
right--;
}
//将找到的数赋值给left角标
arr[left] = arr[right];
//从前向后遍历,直到找到比key值大的数停止
while (left < right && arr[left] <= key) {
left++;
}
//将找到的数赋值给此时的right角标
arr[right] = arr[left];
}
//此时left位是空缺的,将key值赋值给left
arr[left] = key;
return left;
}
}