数据结构与算法:排序算法
- 排序算法简介
- 时间复杂度
- 2.1、时间复杂度的度量方法
- 2.2、常见时间复杂度
- 2.3、 时间复杂度解释
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 希尔排序
- 快速排序
排序算法简介
排序也称排序算法(Sort Algorithm), 排序是将一组数据, 依指定的顺序进行排列的过程。
1:排序算法分类
- 内部排序:指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
- 外部排序法:数据量过大, 无法全部加载到内存中, 需要借助外部存储(文件等)进行排序。
时间复杂度
2.1、时间复杂度的度量方法
- 事后统计的方法:这种方法可行, 但是有两个问题:
一是要想对设计的算法的运行性能进行评测, 需要实际运行该程序;
二是所得时间的统计量依赖于计算机的硬件、 软件等环境因素, 这种方式, 要在同一台计算机的相同状态下运行, 才能比较哪个算法速度更快。 - 事前估算的方法:通过分析某个算法的时间复杂度来判断哪个算法更优
2.2、常见时间复杂度
- 常数阶 O(1)
- 对数阶 O(log2n)
- 线性阶 O(n)
- 线性对数阶 O(nlog2n)
- 平方阶 O(n^2)
- 立方阶 O(n^3)
- k 次方阶 O(n^k)
- 指数阶 O(2^n)
常见的算法时间复杂度由小到大依次为: Ο (1)<Ο (log2n)<Ο (n)<Ο (nlog2n)<Ο (n2)<Ο (n3)< Ο (nk) < Ο (2n) , 随着问题规模 n 的不断增大, 上述时间复杂度不断增大, 算法的执行效率越低
2.3、 时间复杂度解释
- 常数阶 O(1)
- 无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
- 代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。
- 对数阶 O(log2n)
- 线性阶 O(n)
说明:这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度
- 线性对数阶 O(nlogN)
说明:线性对数阶O(nlogN) ,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)
- 平方阶 O(n²)
说明:平方阶O(n²) ,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(nn),即 O(n²) 如果将其中一层循环的n改成m,那它的时间复杂度就变成了 O(mn)
排序算法
创建测试主方法
public static void main(String[] args) {
//创建要给80000个的随机的数组
// int[] arr = new int[80000];
// for (int i = 0; i < 80000; i++) {
// arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
// }
int[] arr = {5, 4, 8, 7, 9, 3, 1};
int[] ints = selectSort(arr);
System.out.println(Arrays.toString(ints));
}
冒泡排序
冒泡排序:
1:循环集合,每次元素与相邻元素比对交换位置,
2:每轮循环结束都会排出最大或最小那个元素。
3:每轮结束都可以缩小比对范围把最大或最小的元素在下轮比对时排除。
4:测试8w数据用时8秒
/*
时间复杂度O(n^2) 80000数据大概8秒排完
*/
public static int[] bubbleSort(int[] arry) {
boolean flag = false; //标记是否进行过排序
for (int i = 0; i < arry.length - 1; i++) {
for (int j = 0; j < arry.length - i - 1; j++) {
if (arry[j] > arry[j + 1]) {
flag = true;
arry[j] = (arry[j] + arry[j + 1]) - (arry[j + 1] = arry[j]);
}
}
if (!flag){
break; //本轮没有进行排序,退出冒泡
}else {
flag = true;
}
}
return arry;
}
选择排序
选择排序:
1:假定第i个元素为最小或最小元素取其下标index。
2:然后循环其他的元素逐一比对。符合条件则替换index。这样在本轮结 束后就可以找到最大or最小元素的下标
3:将第i元素与下标为index的进行替换
4:重复length-i轮循环
5:测试8w数据2.5秒左右
// 选择排序时间复杂度是 O(n^2) 80000数据大概 2.5秒排完
public static int[] selectSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i+1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]){
minIndex = j;
//这里不像冒泡每次都要交换
}
}
if (minIndex != i){
arr[i] = (arr[i] + arr[minIndex]) - (arr[minIndex] = arr[i]);
}
}
return arr;
}
插入排序
插入排序
1:从i=1开始循环容器,暂定最小下标index=i。暂存最小元素temp=arr[i]
2:temp与左侧元素比较量。若temp小于左侧元素则arr[index]=arr[index-1]
此时index-- 向左移动。
3: 当条件不成立时退出循环。此时将arr[index]=temp
4:测试8w数据0.5秒
public static int[] insertSort(int[] array){
long l = System.currentTimeMillis();
for (int i = 1; i < array.length; i++) {
int index = i;
int temp = array[i];
while (index > 0){
if (temp < array[index - 1]){
array[index] = array[index - 1];
index --;
}else {
break;
}
}
if (index != i){
array[index] = temp;
}
}
System.out.println(System.currentTimeMillis()-l);
return array;
}
希尔排序
1:插入排序的缺点:当后边的数较小的时候后移的次数明显增多
2:相当于对插入排序的优化:对容器元素的一个宏观调控。减少元素移动
1:希尔排序交换法
1:测试结果8w数据用时6秒。
2:原因:虽已对容器元素进行宏观调控。但是本质上元素交换次数还是很多。与冒泡一样进行了大规模的元素交换
public static int[] shellSort(int[] array) {
long l = System.currentTimeMillis();
int temp = 0;
for (int gap = array.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < array.length; i++) {
for (int j = i - gap; j >= 0; j -= gap) {
if (array[j] > array[j + gap]) {
temp = array[j];
array[j] = array[j + gap];
array[j + gap] = temp;
}
}
}
}
System.out.println(System.currentTimeMillis()-l);
return array;
}
2:希尔排序移位法
测试结果:8w数据0.2秒
原因:本身对容器进行宏观调控优化了插入排序的缺点。减少了元素位置。又结合插入排序优化了元素间大量交换
public static int[] shellSort02(int[] array) {
//int[] array = {8, 1, 6, 9, 5, 4, -1, 7, 2, 3};
long l = System.currentTimeMillis();
for (int gap = array.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < array.length; i++) {
int index = i;
int temp = array[i];
while (index >= gap){
if (temp < array[index - gap]){
array[index] = array[index - gap];
index -= gap;
}else {
break;
}
}
array[index] = temp;
}
}
System.out.println(System.currentTimeMillis()-l);
return array;
}
快速排序
1:二分法和递归的思想
2:每次数据分层次再不断收缩范围
3:测试结果:8w 15ms
private static int[] quickSort(int[] arr, int left, int right) {
if (left < right) {
int partitionIndex = partition(arr, left, right);
//排左边的
quickSort(arr, left, partitionIndex - 1);
//排右边的
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
//每轮循环参考以下数据移动
// 3 5 8 4 2
// 2 5 8 4 2
// 2 5 8 4 5
// 2 5 8 4 5
// 2 3 8 4 5
private static int partition(int[] arr, int left, int right) {
//取一个临界点做中间点。左侧都为小于该值的。右侧反之
int temp = arr[left];
while (left < right){
while (left < right && arr[right] >= temp){
right--;
}
arr[left]=arr[right];
while (left < right && arr[left] <= temp){
//小于临界点则下标指向移动
left++;
}
//找到了比temp大的,移动到右侧
arr[right] = arr[left];
//到这里就顺理成章的完成了一轮排序。
}
arr[left] = temp;
//System.out.println(Arrays.toString(arr));
return left;
}