Java学习 排序算法—冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序
排序就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。
前言
今天讲Java中常用的几种排序算法原理以及代码,常见的排序算法有冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序
排序算法可以分为内部排序和外部排序。
内部排序是数据记录在内存中进行排序。
外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
(1)时间复杂度:即从序列的初始状态到经过排序算法的变换移位等操作变到最终排序好的结果状态的过程所花费的时间度量。
(2)空间复杂度:就是从序列的初始状态经过排序移位变换的过程一直到最终的状态所花费的空间开销。
(3)稳定性:稳定的算法在排序的过程中不会改变元素彼此的位置的相对次序,反之不稳定的排序算法经常会改变这个次序。
先比较一下几种算法的时间复杂度
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 内部排序 | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 内部排序 | 不稳定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 内部排序 | 稳定 |
希尔排序 | O(nlog n) | O(n log^2 n) | O(n log^2 n) | O(1) | 内部排序 | 不稳定 |
快速排序 | O(nlog n) | O(nlog n) | O(n^2) | O(log n) | 内部排序 | 不稳定 |
归并排序 | O(nlog n) | O(nlog n) | O(nlog n) | O(n) | 外部排序 | 稳定 |
提示:以下是本篇文章正文内容,下面案例可供参考
一、冒泡排序
- 原理
a、冒泡排序,是通过每一次遍历获取最大值
b、将最大值放在尾部
c、然后除最大值,剩下的数据在进行遍历获取最大值
- 图解
- 代码
public static void main(String[] args) {
//冒泡排序:数组中的元素两两比较,大的往后放,经过一轮比较后,最大的元素就会出现的最后面,如此往复,数组元素就会排序好。
int[] arr = {24, 69, 80, 57, 13, 5, 3, 5, 8};
//外层循环控制轮次
for (int j = 0; j < arr.length - 1; j++) {
//内层循环元素两两比较交换位置
for (int i = 0; i < arr.length - 1 - j; i++) {
if (arr[i] > arr[i + 1]) {
//交换位置
int t = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = t;
}
}
}
System.out.println(Arrays.toString(arr));
}
二、选择排序
- 原理
a、将第一个值看成最小值
b、然后和后续的比较找出最小值和下标
c、交换本次遍历的起始值和最小值
d、说明:每次遍历的时候,将前面找出的最小值,看成一个有序的列表,后面的看成无序的列表,然后每次遍历无序列表找出最小值。
- 图解
- 代码
public static void main(String[] args) {
//选择排序:每次拿一个元素,跟他后面的元素挨个比较,小的往前放,经过一轮比较,最小的元素就会出现在最前面,如此往复,经过几轮后,就会排序好。
int[] arr = {24, 69, 80, 57, 13, 2, 0, 3, 8};
//tuiDao(arr);
for (int index = 0; index < arr.length - 1; index++) {
for (int i = 1 + index; i < arr.length; i++) {
if (arr[i] < arr[index]) {
int t = arr[i];
arr[i] = arr[index];
arr[index] = t;
}
}
}
System.out.println(Arrays.toString(arr));
}
三、插入排序
- 原理
a、默认从第二个数据开始比较。
b、如果第二个数据比第一个小,则交换。然后在用第三个数据比较,如果比前面小,则插入。否则,退出循环
c、说明:默认将第一数据看成有序列表,后面无序的列表循环每一个数据,如果比前面的数据小则插入(交换)。否则退出。
- 图解
- 代码
public static void main(String[] args) {
//直接插入排序:从1索引处开始,把后面的元素插入到之前的一个有序序列中,使之仍保持有序。
//{1,5,0,6,9,20,85,68,922}
//[1],5,0,6,9,20,85,68,922
//[1,5],0,6,9,20,85,68,922
//[0,1,5],6,9,20,85,68,922
//[0,1,5,6],-1,20,85,68,922
//定义外层循环控制轮次
int[] arr = {24, 6, 80, 57, 13, 2, 0, 3, 8, -1, -2, 300, 80, 93};
for (int i = 1; i < arr.length; i++) {
//arr[i] 当前元素 arr[i-1] 他前面的一个元素
//如果当前元素小于我前一个元素,那就交换位置
int j = i;
while (j > 0 && arr[j] < arr[j - 1]) {
int t = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = t;
j--;
}
}
System.out.println(Arrays.toString(arr));
}
四、希尔排序
- 原理
a、基本上和插入排序一样的道理
b、不一样的地方在于,每次循环的步长,通过减半的方式来实现
c、说明:基本原理和插入排序类似,不一样的地方在于。通过间隔多个数据来进行插入排序。
public static void main(String[] args) {
int arr[] = {7, 5, 3, 2, 4};
//希尔排序(插入排序变种版)
for (int i = arr.length / 2; i > 0; i /= 2) {
//i层循环控制步长
for (int j = i; j < arr.length; j++) {
//j控制无序端的起始位置
for (int k = j; k > 0 && k - i >= 0; k -= i) {
if (arr[k] < arr[k - i]) {
int temp = arr[k - i];
arr[k - i] = arr[k];
arr[k] = temp;
} else {
break;
}
}
}
//j,k为插入排序,不过步长为i
}
}
五、快速排序
- 原理
分治法:比大小,再分区
1.从数组中取出一个数,作为基准数。
2.分区:将比这个数大或等于的数全放到他的右边,小于他的数全放到他的左边。
2.分区:将比这个数大或等于的数全放到他的右边,小于他的数全放到他的左边。
- 图解
- 代码
public class QuickSort {
//start 默认是0
//end 是数组长度-1
public void quickSort(int[] arr, int start, int end) {
if (start < end) {
//获取分区索引
int index = getIndex(arr, start, end);
//对左右两个分区 再进行同样的步骤 ,即是递归调用
quickSort(arr, start, index - 1);//左半部分
quickSort(arr, index + 1, end);//右半部分
}
}
private int getIndex(int[] arr, int start, int end) {
int i = start;
int j = end;
//定义基准数
int x = arr[i];
//循环
while (i < j) {
//从右往左比较
while (i < j && arr[j] >= x) {
j--;
}
//从右往左找到比基准数小的数了后,填坑
if (i < j) {
//把这个数填到上一个坑位
arr[i] = arr[j];
//让 i++;
i++;
}
//从左往右找
while (i < j && arr[i] < x) {
i++;
}
// 找比基准数大的数,找到后填坑
if (i < j) {
arr[j] = arr[i];
j--;
}
}
//当上面的循环结束后把基准数填到最后一个坑位,也就一基准数为界,分成了左右两部分
arr[i] = x; //把基准数填进去
return i; //返回基准数所在位置的索引
}
}
六、归并排序
- 原理
a、将列表按照对等的方式进行拆分
b、拆分小最小快的时候,在将最小块按照原来的拆分,进行合并
c、合并的时候,通过左右两块的左边开始比较大小。小的数据放入新的块中
d、说明:简单一点就是先对半拆成最小单位,然后将两半数据合并成一个有序的列表。
- 代码实现
public static void main(String[] args) {
int arr[] = {7, 5, 3, 2, 4, 1,6};
//归并排序
int start = 0;
int end = arr.length - 1;
mergeSort(arr, start, end);
}
public static void mergeSort(int[] arr, int start, int end) {
//判断拆分的不为最小单位
if (end - start > 0) {
//再一次拆分,知道拆成一个一个的数据
mergeSort(arr, start, (start + end) / 2);
mergeSort(arr, (start + end) / 2 + 1, end);
//记录开始/结束位置
int left = start;
int right = (start + end) / 2 + 1;
//记录每个小单位的排序结果
int index = 0;
int[] result = new int[end - start + 1];
//如果查分后的两块数据,都还存在
while (left <= (start + end) / 2 && right <= end) {
//比较两块数据的大小,然后赋值,并且移动下标
if (arr[left] <= arr[right]) {
result[index] = arr[left];
left++;
} else {
result[index] = arr[right];
right++;
}
//移动单位记录的下标
index++;
}
//当某一块数据不存在了时
while (left <= (start + end) / 2 || right <= end) {
//直接赋值到记录下标
if (left <= (start + end) / 2) {
result[index] = arr[left];
left++;
} else {
result[index] = arr[right];
right++;
}
index++;
}
//最后将新的数据赋值给原来的列表,并且是对应分块后的下标。
for (int i = start; i <= end; i++) {
arr[i] = result[i - start];
}
}
}
总结
排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。