文章目录
排序算法概述
内部排序:
将所有数据都加载到内存中排序,也就是我们常用的排序
外部排序:
因为可能数据量很大,所以无法全部加载到内存中,那么我们需要借助外部存储器来进行排序。
八大排序算法:
交换:冒泡排序,快速排序(前一个优化)
插入:插入排序,希尔排序(前一个优化)
选择:简单选择排序,堆排序(前一个优化)
归并排序
基数排序
希尔排序相当于直接插入排序的优化,它们同属于插入排序类,堆排序相当于简单选择排序的优化,它们同属于选择排序类。而快速排序其实就是冒泡排序的升级,它们都属于交换排序类
1. 冒泡排序
基本思想:
相邻俩俩比较,交换,最大值置后。
相邻的比较,让最小的或者最大的浮动到最顶端。
每次循环会查到一个最值,然后循环继续第二次查到第二大值,直到全部查完。
时间复杂度为两层O(n^2 )
代码实现:
package algrithm;
//冒泡排序
public class BubbleSort {
public static void bubbleSort(int[] arr) {
//双层循环:i表示比较趟数,j表示俩俩数进行比较
for (int i = 0; i < arr.length - 1; i++) {//一共多少趟,一共需要N-1趟
//每一趟相邻两个数比较,大数置后,每一趟比完最值放到边上
for (int j = 0; j < arr.length - 1 - i; j++) {//一共需要N-i比完的个数
//如果前一个数比后一个数大就交换
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
算法效率:
冒泡排序是稳定的排序算法,最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间因为是两层循环:复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1)。
2. 快速排序
基本思想:
分治,中间值置位,交换。
快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,通过一趟排序将要排序的数据分割成独立的两部分, 其中一部分的所有数据都比另外一部分的所有数据都要小 ,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。
实现步骤
找个基准数第一个数比如为6
- 用指针从后找到小于6的数,再从前面找到大于 6 的数。交换他俩。让小的到左边,大的到右边。
- 依次循环找,当两个指针相遇时,则把基准数6放到这个指针位置,也就是把6放中间。这时左边都小于6,右边都大于6。
递归左右两边的操作。
左右指针法
用伪代码描述如下:
(1)判断左边若大于右边则退出
(2)定义基准数为第一个元素,并定义左右下标
(3)while循环,当左下标小于右时
(4)while循环当右大于基准数则继续找,while当左小于基准数则继续找
(5)当l<r则满足条件交换arr[l]和arr[r]
6)循环结束把该位置和第一个基准数交换
7)递归
图文详解
https://tryenough.com/arithmetic-quitsort
https://blog.csdn.net/Advance_/article/details/81880509
代码实现
//快速排序
public class QuickSort {
//left为左边下标,right为右下标
public static void quickSort(int arr[], int first, int last) {
//如果输入了左边大于右边则有问题直接退出
if (first > last)
return;
int piviot = arr[first];//第一个数作为基准数
int l = first; //左下标
int r = last; //右下标
//进行循环,因为每次left和right都移动一个,所以终究会碰头
while (l < r) {
//先找右边
//当右边的数大于中间数,则继续移动找,直到找到小的
while ((arr[r] >= piviot) && (l < r)) {
r--;
}
//当左边有数比基数大则停止,小则继续移动
while ((arr[l] <= piviot) && (l < r)) {
l++;
}
//上面两个循环之后就找到了下标,进行交换
//这时查找到的数左边大右边小,则交换元素,让小的去左边,大的去右边
if (l < r) {//如果l<r则满足条件,交换
int t = arr[l];
arr[l] = arr[r];
arr[r] = t;
}
//交换完了进行下一次循环继续找
}
//循环结束之后,交换基准数和第一个数
arr[first] = arr[l];
arr[l] = piviot;
//进行递归处理
quickSort(arr, first, l - 1);//继续处理左边的,这里是一个递归的过程
quickSort(arr, l + 1, last);//继续处理右边的 ,这里是一个递归的过程
}
}
算法效率
快速排序并不稳定,快速排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序。
3. 选择排序
插入排序于冒泡排序相似,不同点是,冒泡排序每次比较相邻元素,而选择排序是每次用一个和后面都元素分别比较。最值放置到最前面
算法效率
不稳定排序算法,选择排序的简单和直观名副其实,这也造就了它出了名的慢性子,无论是哪种情况,哪怕原数组已排序完成,它也将花费将近n²/2次遍历来确认一遍。 唯一值得高兴的是,它并不耗费额外的内存空间。
时间复杂度也是O(n^2 )
package algrithm;
//选择排序
public class SelectionSort {
public static void selectionSort(int[] arr) {
//所有数遍历,每个i跟内层的j也就是后面的每个数进行比较
for (int i = 0; i < arr.length - 1; i++) {//所有数遍历
//从前往后,第一个和第二个比,第一个再和第三个比
for (int j = i + 1; j < arr.length - 1; j++) {//从i后一个开始比
//如果第一个大于第三个,则交换,小的放前
if (arr[i] > arr[j]) {
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
//排好后再从第二个数开始,分别和后面的比较
}
}
}
}
选择排序优化:减少交换次数。
public class SelectionBetter {
public static void selectionSort(int[] arr) {
//所有数遍历,每个i跟内层的j也就是后面的每个数进行比较
for (int i = 0; i < arr.length - 1; i++) {//所有数遍历
int min = arr[i];
int minIndex = i;
//从前往后,第一个和第二个比,第一个再和第三个比
for (int j = i + 1; j < arr.length - 1; j++) {//从i后一个开始比
//每次存储最小数
if (arr[i] > arr[j]) {
min = arr[j];
minIndex = j;
}
//排好后再从第二个数开始,分别和后面的比较
}
//最小数下标交换
if(minIndex != i){
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
}
4. 插入排序
基本思想:
分为有序表和无序表,每次把无序表中第一个元素拿出来,依次和有序表中进行比较,插入到有序表中适当位置。
步骤:
1)遍历从第二个数开始到数组长度
2)定义待插入值和已排序数组下标
3)循环和前面已排好数组比较,直到找到比他小的
//插入排序
public class InsertSort {
public static void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {//从第二个数开始
//待插入的值
int insertVal = arr[i];
int j = i - 1;//j为已排序数组下标
//保证下标>=0且待插入值小于前面排序好的数组值
while (j >= 0 && insertVal < arr[j]) {
arr[j + 1] = arr[j];//数组往后移一位
j--;//然后继续往前比较
}
arr[j + 1] = insertVal;//比较完之后,将待插入值插到当前角标位置后面一个
//这时序列都往后移了一位,前面都就排好了,继续下次循环
}
//应该比冒泡排序执行次数少,因为如果该数大,则前面都不需要移动。
}
}