排序也称排序算法(Sort Algorithm)。排序将一组数据,依指定的顺序进行排列的过程。
2、排序的分类内部排序
指将需要处理的所有数据都加载到**内部存储器(内存)**中进行排序
外部排序
数据量过大,无法全部加载到内存中,需要借助**外部存储(文件等)**进行排序
常见的排序算法分类如下图:
3、时间复杂度 4、常见的时间复杂度(由小到大)常数阶O(1)对数阶O(log2n)线性阶O(n)线性对数阶O(nlog2n)平方阶O(n^2)立方阶O(n^3)k次方阶O(n^k)指数阶O(2^n)
5、平均时间复杂度和最坏时间复杂度
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。平均时间复杂度和最坏时间复杂度是否一致,和算法有关如图:
6、空间复杂度
类似于时间复杂度的讨论,一个算法的空间复杂度( Space Complexity)定义为该算法所耗费的存储空间,它也是问题规模n的函数。空间复杂度( Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法,基数排序就属于这种情况在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。一些缓存产品( redis, memcache)和算法(基数排序)本质就是用空间换时间 7、冒泡排序
冒泡排序( Bubble sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
优化:
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
思路图解:
小结上面的图解过程:
(1)一共进行数组的大小-1次大的循环
(2)每一趟排序的次数在逐渐的减少
(3)如果我们发现在某趟排序中,没有发生一次交换,可以提前结束冒泡排序。这个就是优化
代码实现:
举一个具体的案例来说明冒泡法。我们将五个无序的数:3,9,-1,10,-2使用冒泡排序法将其排成一个从小到大的有序数列
package sort;
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {3,9,-1,10,-2};
System.out.println("排序前");
System.out.println(Arrays.toString(arr));
//测试冒泡
bubbleSort(arr);
System.out.println("排序后");
System.out.println(Arrays.toString(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 (int i = 0; i < arr.length-1-1; i++) {
//如果前面的数比后面的大 则交换
if (arr[i] > arr[i+1]){
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
System.out.println("第二趟排序后的数组");
System.out.println(Arrays.toString(arr));
//第三趟 将第三大的数排在倒数第三位
for (int i = 0; i < arr.length-1-1-1; i++) {
//如果前面的数比后面的大 则交换
if (arr[i] > arr[i+1]){
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
System.out.println("第三趟排序后的数组");
System.out.println(Arrays.toString(arr));
//第四趟 将第四大的数排在倒数第四位
for (int i = 0; i < arr.length-1-3; i++) {
//如果前面的数比后面的大 则交换
if (arr[i] > arr[i+1]){
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
System.out.println("第四趟排序后的数组");
System.out.println(Arrays.toString(arr));
}
//将冒泡排序算法封装到一个方法
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!!! 进行下次交换
}
}
}
}
8、选择排序
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。
基本思想:
思路分析:
代码实现:
使用选择排序从低到高进行排序[101,34,119,1]
package sort;
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
int[] arr = {101,34,119,1};
System.out.println("排序前");
System.out.println(Arrays.toString(arr));
selectSort(arr);
System.out.println("排序后");
System.out.println(Arrays.toString(arr));
}
//方法:选择排序
public static void selectSort(int[] arr){
//从推导中可以看出 利用for循环解决
//选择排序复杂度O(n^2) 80000条数据排序使用时间2-3s 冒泡20s左右
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[0] 即交换
if (minIndex != i){
arr[minIndex] = arr[i];
arr[i] = min;
}
// System.out.println("第"+(i+1)+"轮");
// System.out.println(Arrays.toString(arr));
}
//使用逐步推导,
//第一轮
int minIndex = 0;
int min = arr[0];
for (int j = 1; j < arr.length; j++) {
if (min > arr[j]){//说明假定的最小值不是最小
min = arr[j];//重置min
minIndex = j;//重置minIndex
}
}
//将最小值放在arr[0] 即交换
if (minIndex != 0){
arr[minIndex] = arr[0];
arr[0] = min;
}
//第2轮
System.out.println("第一轮");
System.out.println(Arrays.toString(arr));
//第2轮
minIndex = 1;
min = arr[1];
for (int j = 2; j < arr.length; j++) {
if (min > arr[j]){//说明假定的最小值不是最小
min = arr[j];//重置min
minIndex = j;//重置minIndex
}
}
//将最小值放在arr[0] 即交换
if (minIndex != 1){
arr[minIndex] = arr[1];
arr[1] = min;
}
//第2轮
System.out.println("第2轮");
System.out.println(Arrays.toString(arr));
//第3轮
minIndex = 2;
min = arr[2];
for (int j = 3; j < arr.length; j++) {
if (min > arr[j]){//说明假定的最小值不是最小
min = arr[j];//重置min
minIndex = j;//重置minIndex
}
}
//将最小值放在arr[0] 即交换
if (minIndex != 2){
arr[minIndex] = arr[2];
arr[2] = min;
}
//第3轮
System.out.println("第3轮");
System.out.println(Arrays.toString(arr));
}
}
9、插入排序
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
基本思想:
插入排序( Insertion sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
思路分析:
代码实现:
package sort;
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = {104,34,119,1};
insertSort(arr);
}
//插入排序
public static void insertSort(int[] arr){
//插入排序时间复杂度O(n) 80000条数据 用时4-5s 选择 2-3s 冒泡 20s左右
//使用for循环简化代码
int insertVal = 0;
int insertIndex = 0;
for (int i = 1; i < arr.length; i++) {
insertVal = arr[i];
insertIndex = i - 1;//即arr[1]前面的下标
//给insertVal找到插入的位置
while (insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex];//arr[insertIndex] 后移
insertIndex--;
}
//退出while 插入的位置找到 insertIndex+1
arr[insertIndex + 1] = insertVal;
System.out.println("第"+ i+"轮插入");
System.out.println(Arrays.toString(arr));
}
//使用逐步推导
//第1轮
//定义待插入的数
int insertVal = arr[1];
int insertIndex = 0;//即arr[1]前面的下标
//给insertVal找到插入的位置
while (insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex];//arr[insertIndex] 后移
insertIndex--;
}
arr[insertIndex + 1] = insertVal;
System.out.println("第1轮插入");
System.out.println(Arrays.toString(arr));
//第2轮
//定义待插入的数
insertVal = arr[2];
insertIndex = 2 - 1;//即arr[1]前面的下标
//给insertVal找到插入的位置
while (insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex];//arr[insertIndex] 后移
insertIndex--;
}
arr[insertIndex + 1] = insertVal;
System.out.println("第2轮插入");
System.out.println(Arrays.toString(arr));
//第3轮
//定义待插入的数
insertVal = arr[3];
insertIndex = 3 -1;//即arr[1]前面的下标
//给insertVal找到插入的位置
while (insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex];//arr[insertIndex] 后移
insertIndex--;
}
arr[insertIndex + 1] = insertVal;
System.out.println("第3轮插入");
System.out.println(Arrays.toString(arr));
}
}
10、希尔排序
简单插入排序存在的问题:
希尔排序是希尔( Donald shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
基本思想:
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
思路分析:
代码实现:
package sort;
import java.sql.PreparedStatement;
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};
System.out.println("交换式");
shellSort(arr);
System.out.println(Arrays.toString(arr));
System.out.println("移位式");
shellSort2(arr);
System.out.println(Arrays.toString(arr));
}
//希尔排序 对有序序列再插入时采用交换法
public static void shellSort(int[] arr){
//根据推导,使用循环处理
int temp = 0;
int count = 0;
for (int gap = arr.length / 2; gap > 0 ; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
//遍历各组中所有元素共gap组 步长gap
for (int j = i - gap; j >= 0 ; j -= gap) {
//如果当前的元素大于步长的后的那个元素,说明交换
if (arr[j] > arr[j + gap]){
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
System.out.println("希尔排序第"+(++count)+"轮 = "+Arrays.toString(arr));
}
//使用逐步推导的方式实现希尔排序
//希尔排序
//第1轮 将10个数据分为10/2=5组
}
//对交换式的希尔排序进行优化 移位法
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;
}
}
}
}
}
11、快速排序
快速排序( Quick sort)是对冒泡排序的一种改进。
基本思想:
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
思路分析:
代码实现:
package sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {-9,78,0,23,-567,70};
quickSort(arr,0,arr.length-1);
System.out.println("arr = "+ Arrays.toString(arr));
}
//快速排序 80000数据用时1-2s
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值小放在左边
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);
}
}
}
12、归并排序
归并排序( MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(dide-and- conquer)策略(分治法将问题分( divide)成一些小的问题然后递归求解,而治( conquer)的阶段则将分的阶段得到的各答案“修补”在一起,即分而治之)。
基本思想(分-治):
基本思想-合并相邻有序子序列
代码实现:
package sort;
import java.util.Arrays;
public class MergetSort {
public static void main(String[] args) {
int[] arr = {8,4,5,7,1,3,6,2};
int[] temp = new int[arr.length];//归并排序需要额外的空间
mergeSort(arr,0,arr.length-1,temp);
System.out.println("归并排序 = " + Arrays.toString(arr));
}
//分 + 合的方法 80000数据用时1s左右
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);
}
}
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数组的当前索引
//1、先把左右两边的(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列有一边处理完毕为止
while (i <= mid && j <= right){
//如果左边的有序序列的当前元素小于或者等于右边有序序列的当前元素
//即将左边的当前元素拷贝到temp数组
//然后t++ i++
if (arr[i] <= arr[j]){
temp[t] = arr[i];
t++;
i++;
}else {//反之 将右边的有序序列的当前元素 填充到temp数组
temp[t] = arr[j];
t++;
j++;
}
}
//2、把剩余数据的一边的数据依次填充到temp
while (i <= mid){//左边还有剩余的元素
temp[t] = arr[i];
t++;
i++;
}
while (j <= right){//右边还有剩余的元素
temp[t] = arr[j];
t++;
j++;
}
//3、将temp数组的元素拷贝到arr
t = 0;
int tempLeft = left;
while (tempLeft <= right){
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
}
13、基数排序
基数排序( radix sort)属于“分配式排序”( distribution sort),又称“桶子法”( bucket sort)或 bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法基数排序( Radix Sort)是桶排序的扩展基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。
基本思想:
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
思路分析:
代码实现:
package sort;
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args) {
int[] arr = {53,3,542,748,14,214};
radixSort(arr);
}
//方法:基数排序
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
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中实际存放了多少个数据 定义一个一维数组来记录各个桶的每次放入的数据个数
int[] bucketElementCounts = new int[10];//bucketElementCounts[0]记录的是bucket[0]桶的放入数据个数
//使用循环处理
for (int i = 0,n = 1; i < maxLength; i++,n *= 10) {//i指的是位数
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));
}
}
}
基数排序的说明:
- 基数排序是对传统桶排序的扩展,速度很快基数排序是经典的空间换时间的方式,占用内存很大,当对海量数据排序时,容易造成 OutOfmemoryError基数排序时稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的,否则称为不稳定的]有负数的数组,我们不用基数排序来进行排序
相关术语解释:
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面内排序:所有排序操作都在内存中完成外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行时间复杂度:一个算法执行所耗费的时间空间复杂度:运行完一个程序所需内存的大小。n:数据规模k:“桶”的个数In- place:不占用额外内存Out- place:占用额外内存



