排序(上)

如何分析一个排序算法的效率:

  1. 最好情况,最坏情况,平均情况时间复杂度

  2. 时间复杂度的系数,常数,低阶

  3. 比较次数和交换(移动)次数

排序算法的稳定性

如果待排序的序列中存在值相等的元素,经过排序之后,相等的元素之间原有的先后顺序不变,则为稳定的排序算法,反之则为不稳定的排序算法

排序算法的内存消耗

原地排序(Sorted in place) 原地排序算法特指空间复杂度O(1)的排序算法;

冒泡排序(Bubble Sort)

冒泡排序只会操作相邻的两个数据, 每次冒泡操作都会对相邻的两个元素进行比较,若不满足大小关系要求则互换,一次冒泡至少会让一个元素移动到它合适的位置,重复n次,就完成了n个数据的排序工作

例如: 一组数据: 4,5,6,3,2,1,从小到大进行排序,第一次冒泡 操作的详细过程:

 

要想完成所有数据的排序,只需要进行6次这样的冒泡操作即可

 

代码实现

// 冒泡排序,a 表示数组,n 表示数组大小
public void bubbleSort(int[] a, int n) {
  if (n <= 1) return;
 
 for (int i = 0; i < n; ++i) {
    // 提前退出冒泡循环的标志位
    boolean flag = false;
    for (int j = 0; j < n - i - 1; ++j) {
      if (a[j] > a[j+1]) { // 交换
        int tmp = a[j];
        a[j] = a[j+1];
        a[j+1] = tmp;
        flag = true;  // 表示有数据交换      
      }
    }
    if (!flag) break;  // 没有数据交换,提前退出
  }
}
​

//冒泡排序的优化: 在每一轮排序记录最后一次元素交换的位置,作为下次比较的边界,对于边界外的元素,元素在下次循环中无需比较
 public static void bubbleSort2(int[] a, int n) {
        if (n <= 1)
            return;
        int lastExchange = 0;
        int sortedBoard = n - 1;
        for (int i = 0; i < n; i++) {
            //无元素交换时跳出循环
            boolean flag = false;
            for (int j = 0; j < sortedBoard; ++j) {
                if (a[j] > a[j + 1]) {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    flag = true;
                    //记录最后一次交换的位置
                    lastExchange = j;
                }
            }
            if (!flag)
                break;
            sortedBoard = lastExchange;
​
        }
    }
​
冒泡排序的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,空间复杂度为O(1),是一个原地排序算法冒泡排序是一个稳定排序算法,相等的相邻元素大小相等的时候,不做交换

插入排序

将数组中的数据分为已排序区间和未排序区间,初始已排序区间只有一个元素,就是数组的第一个元素,插入算法的核心是取未排序区间的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序,重复这个过程,直到未排序区间中元素为空;

 

实现代码:

// 插入排序,a 表示数组,n 表示数组大小
public void insertionSort(int[] a, int n) {
  if (n <= 1) return;
​
  for (int i = 1; i < n; ++i) {
    int value = a[i];
    int j = i - 1;
    // 查找插入的位置
    for (; j >= 0; --j) {
      if (a[j] > value) {
        a[j+1] = a[j];  // 数据移动
      } else {
        break;
      }
    }
    a[j+1] = value; // 插入数据
  }
}
插入空间复杂度是O(1),是一个原地排序算法;插入排序对于值相同的元素,插入到前面出现的元素的后面,可以保持原有的 前后顺序不变,所以是稳定的排序算法,时间复杂度: 最好的情况下为O(n);最坏的情况下是O(n2 )

选择排序(Selection Sort)

选择排序算法思路类似插入排序,也分已排序区间和未排序区间,但是选择排序每次都会从未排序区间中找到最小的 元素,将其放入到已排序区间的末尾

 

选择排序是原地排序算法,空间复杂度为O(1),不是稳定的排序算法,每次排序都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性

代码实现:

package com.zach.algorithm;
​
import java.util.Arrays;
​
public class SelectionSort {
    public static void main(String[] args) {
        int[] arr = {6, 5, 4, 3, 2, 1};
        selectionSort(arr,6);
        System.out.println(Arrays.toString(arr));
    }
​
    public static void selectionSort(int[] a, int n) {
        if (n <= 1)
            return;
        for (int i = 0; i < n - 1; ++i) {
            //查找最小值
            int minIndex = i;
            for (int j = i + 1; j < n; j++) {
                if (a[j] < a[minIndex]) {
                    minIndex = j;
                }
            }
            //交换
            int tmp = a[i];
            a[i] = a[minIndex];
            a[minIndex] = tmp;
        }
    }
}
 

思考题:

冒泡排序和插入排序的时间复杂度都是O(n2 ),都是原地排序算法,为什么插入排序要比冒泡排序更受欢迎呢?

从代码实现上来看,冒泡排序数据交换比插入排序的数据移动要复杂,冒泡排序需要3个赋值操作,而插入排序只需要1个

冒泡排序中数据的交换操作:
if (a[j] > a[j+1]) { // 交换
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}
​
插入排序中数据的移动操作:
if (a[j] > value) {
  a[j+1] = a[j];  // 数据移动
} else {
  break;
}
​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值