排序算法
复杂度比较
- 改图摘自:菜鸟教程
冒泡排序
思想
- 每一轮从左到右,相邻元素比较,满足条件则互换位置。每一轮右侧冒出符合条件的一个元素。
- 如果有n个数据,只需要比较n-1轮。
特点
- 冒泡排序是稳定的,对于同样的元素相对位置是不会改变的。
例子
-
对(7,10,4,6,3)排序
第一趟:7,4,6,3,10
第二趟:4,6,3,7,10
第三趟:4,3,6,7,10
第四趟:3,4,6,7,10
实现
@Test
public void test1(){
// 定义一个数组
List<Integer> list = Arrays.asList(1, 10, 3, 4, 6, 7, 8, 2, 3, 0);
Object[] objects = list.toArray();
int[] arr=new int[objects.length];
for (int i = 0; i < objects.length; i++) {
arr[i]= (int) objects[i];
}
// 冒泡法排序
for (int i = 0; i < arr.length; i++) {
// 定义临时变量并赋值为arr[0]
int temp=arr[0];
for (int j = 0; j < arr.length-1; j++) {
if(arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第:"+i+":趟"+"========"+temp);
}
System.out.println("===========================");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
选择排序
思想
- 在一个长度为N的数组中,第一次遍历n-1个数找到最小的和第一个交换。
- 第二次从下一个数开始遍历n-2个数,找到最小的数和第二个数交换。
- 重复操作直到n-1次遍历最小的数和第n-1个数交换。
特点
- 选择排序是不稳定的算法
例子
-
对(7,10,4,6,3)排序
第一趟:10,7,4,6,3
第二趟:10,7,4,6,3
第三趟:10,7,6,4,3
第四趟:10,7,6,4,3
实现
// 选择排序
for (int i = 0; i < arr.length; i++) {
// 假设第一个元素是最大的
int max =arr[i];
for (int j = i; j < arr.length; j++) {
if(max<arr[j]){
int temp=arr[j];
arr[j]=max;
max=temp;
}
}
arr[i]=max;
}
快速排序
思想
- 从数列中挑出一个元素作为基准。
- 重新排列数列,把比基准小的放在基准前面,反之放在基准后面。
- 通过递归调用把小于基准元素和大于基准元素的子序列进行排序。
- 分治思想。
例子
-
对(7,10,4,6,3)排序
第一趟:3,10,4,6,7
第二趟:3,10,4,6,7
第三趟:3,6,4,10,7
重复执行两边的
3,4,6,7,10
特点
- 平均性能好
- 不稳定,初始序列有序或者基本有序时,时间复杂度下降
实现
@Test
public void test3(){
// 定义一个数组
List<Integer> list = Arrays.asList(1, 10, 3, 4, 6, 7, 8, 2, 3, 0);
Object[] objects = list.toArray();
int[] arr=new int[objects.length];
for (int i = 0; i < objects.length; i++) {
arr[i]= (int) objects[i];
}
// 快速排序:首先获得一个基准
int low=0;
int high=arr.length-1;
// 定义key用于存放基准,理论上可以拿任何一个数作为基准
int key=arr[low];
for (int i : arr) {
System.out.print(i);
}
System.out.println("排序前===================");
unckSort(arr,low,high);
for (int i : arr) {
System.out.println(i);
}
}
public static int getMiddle(int[] list, int low, int high) {
// 数组的第一个值作为基准值(分界点或关键数据)
int tmp = list[low];
while (low < high) {
while (low < high && list[high] >= tmp) {
high--;
}
// 比基准值小的记录移到低端
list[low] = list[high];
while (low < high && list[low] <= tmp) {
low++;
}
// 比基准值大的记录移到高端
list[high] = list[low];
}
// 基准值记录到尾
list[low] = tmp;
// 返回基准值的位置
return low;
}
public static void unckSort(int[] list,int low,int high) {
if(low < high) {
// 将list数组一分为二
int middle = getMiddle(list,low,high);
// 对低字表进行递归排序
unckSort(list,low,middle-1);
// 对高字表进行递归排序
unckSort(list,middle+1,high);
}
}
插入排序
直接插入排序
思想
- 从第一个元素开始,如果后面的元素大于前面的元素,则标记该元素为待排序元素。
- 从后面遍历已经排好序的元素,如果排好序的元素中最后一个大于待排序的元素,则往后移动有序队列中的这个元素,依次类推。当条件不满足时候,将待排序元素放到有序元素队列的最后面。
特点
- 插入排序每次插入的位置都会大于或者等于前一个元素,所以为稳定排序。
例子
-
排序(3,1,2,5,4)
第一趟:1,3
第二趟:1,2,3
第三趟:1,2,3,5
第四趟:1,2,3,4,5
实现一
@Test
public void test5(){
// 无序数组
int arr[]={3,1,2,5,4,7,9,5,2};
int j; // 已排序列表下标
int num; // 待排序元素
for (int i = 1; i < arr.length; i++) {
if (arr[i] < arr[i - 1]) {
num = arr[i];
// 赋值给待排序元素
for (j = i - 1; j >= 0 && arr[j] > num; j--) {
arr[j + 1] = arr[j];
// 从后往前遍历已排序列表,逐个和待排序元素比较,如果已排序元素较大,则将它后移
}
arr[j + 1] = num;
// 将待排序元素插入到正确的位置
}
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
实现二
public void sort(int[] arr) {
// 从第二个元素开始
for (int i = 1; i < arr.length; i++) {
if (arr[i] < arr[i - 1]) {
// 当前待排序元素为temp
int temp = arr[i];
int j = i;
// 待排序元素和之前的每个元素做比较
while (j >0 && temp < arr[j - 1]) {
arr[j] = arr[j - 1];
j--;
}
arr[j] = temp;
}
}
}
时间复杂度
最坏情况下,整个数组是倒序的复杂度为O(n^2)
,最好的情况下整个数组的顺序就是想要的结果,时间复杂度为O(n)
希尔排序(三层for循环)
思想
是插入排序的一种,是对直接插入排序的一个优化。
- 将待排序的数组元素按下标的一定增量分组,分成多个子序列,然后对各个子序列进行直接插入排序。
- 缩减增量再排序,直到增量为1时,进行最后一次直接插入排序。
实现
public void sort(int[] arr) {
int num;
// temp 为增量
for (int temp = arr.length / 2; temp > 0; temp = temp / 2) {
for (int i = temp; i < arr.length; i++) {
// 增量的数据互相比较交换位置
for (int j = i; j >= temp; j = j - temp) {
if (arr[j] < arr[j - temp]) {
num = arr[j - temp];
arr[j - temp] = arr[j];
arr[j] = num;
}
}
}
}
}
时间复杂度
最好元素都是有序的:O(n)
最坏和直接插入排序一样:O(n^2)