目录
最近在准备面试,从今天开始整理并记录一下准备过程中学习的内容。
学习目标
掌握基本的排序算法。
1.掌握常见的排序算法的实现思路
2.手写冒泡、快排算法代码
3.了解各个排序算法的特性,比如时间复杂度、是否稳定
冒泡排序
文字描述
1.依次比较数组中相邻的两个元素,若是a[i]>a[i+1],那么将a[i]和a[i+1]两个元素进行交换,两两都比较一次成为一轮冒泡,结果是将数组中最大的元素交换至最后。
2.重复以上步骤,直至整个数组有序排列。
动图演示
特点:
1.冒泡排序的时间复杂度:O(n²)
2.冒泡排序是稳定排序算法
3.集合元素有序度较高时,可选择冒泡排序
代码实现
public class BubbleSort {
public static void main(String[] args) {
int[] a = {2, 5, 4, 8, 6, 7, 1};
bubble(a);
}
public static void bubble(int[] a) {
int n = a.length - 1;
while (true) {
int last = 0;//表示最后一次索引交换位置
//一轮冒泡
for (int i = 0; i < a.length - 1; i++) {
if (a[i] > a[i + 1]) {
// 交换数据
int tmp = a[i + 1];
a[i + 1] = a[i];
a[i] = tmp;
last = i;
}
}
System.out.println(Arrays.toString(a));
n = last;
if (n == 0 ) {
break;
}
}
}
}
执行结果:
选择排序
文字描述
1.将数组分为两个区域,排序区域和未排序区域,每轮从未排序区域找出最小的值放入有序区域。
2.重复上述步骤,直至整个数组有序排列。
优化思路:
1.待排序元素遇到比自己小的元素,意味着已经找到插入位置,无需继续比较下去。
动图演示
特点:
1.选择排序的时间复杂度:O(n²)
2.选择排序是不稳定排序算法(元素值相同的情况下顺序可能会发生改变)
3.选择排序一般快于冒泡排序,但是集合有序度高的情况下不如冒泡排序
代码实现
public class SelectionSort {
public static void main(String[] args) {
int[] a = {2, 5, 4, 8, 6, 7, 1};
selection(a);
}
private static void selection(int[] a) {
for (int i = 0; i < a.length-1; i++) {
//i为每轮最小值要交换的索引位置
int s = i;//每轮最小值的索引位置
for (int j = s+1; j < a.length; j++) {
if (a[s] > a[j]){
s = j ;
}
}
if (s != i) {
// 交换数据
int tmp = a[i];
a[i] = a[s];
a[s] = tmp;
System.out.println(Arrays.toString(a));
}
}
}
}
执行结果
插入排序
文字描述
1.将数组分为两个区域,排序区域和未排序区域,每轮从未排序区域选出第一个元素,按序插入到排序区域中。
2.重复上述步骤,直至整个数组有序排列。
优化思路:
1.待排序元素遇到比自己小的元素,意味着已经找到插入位置,无需继续比较下去。
2.插入时可直接移动元素,而不是交换元素
动图演示
特点:
1.插入排序的平均时间复杂度:O(n²),有序集合插入时间复杂度:O(n)
2.插入排序是稳定排序算法
3.大部分情况下,插入排序略优于选择排序。
代码实现
public class InsertSort {
public static void main(String[] args) {
int[] a = {2, 5, 4, 8, 6, 7, 1};
insert(a);
}
private static void insert(int[] a) {
for (int i = 1; i < a.length; i++) {
int t = a[i];
int j = i -1;
while (j >= 0){
if (t < a[j]){
//前面的元素往后移动一位
a[j+1] = a[j];
}else {
break;
}
j--;
}
//j的值为元素比较结束后的前一位,所以需要加1
a[j+1] = t;
System.out.println(Arrays.toString(a));
}
}
}
执行结果:
希尔排序(了解)
文字描述
1.每轮使用不同的间隙,比如N/2,对数组元素进行分割,两两对比大小,如果a[i]>a[i+N/2-1],将两个元素进行交换。
2.不断缩小间隙范围,N/2,N/4……,1,重复上述步骤,直至整个数组有序排列。
上图仅为希尔算法的间隙切分方式之一,也是最原始的切分方式,它的平均时间复杂度并不是最优的,不同的分割方式的平均时间复杂度不同。
快速排序
文字描述
1.每轮排序选择一个基准点进行分区。
1. 让小于基点的元素进入一个分区,大于基点的元素进入另一个分区。
2.分区完成后,基准点就是其最终位置
2. 在子分区内重复上面的过程,直到子分区内元素个数小于等于1,这是一种分而治之的思想(divide-and-conquer)。
特点
1.平均复杂度是O(n㏒₂n) ,最坏时间复杂度O(n²)
2.数据量比较大时,优势非常明显
3.属于不稳定排序。
实现方式
快排的实现方式很多,这里主要罗列三种:1.单边快排 ,2.双边快排,3.霍尔分区
单边快排
1.选择最右元素作为基准点元素。
2.j指针负责找出比基准点小的元素,一旦找到则与i交换。
3.i指针维护小于基准点元素的边界,也是每次交换的目标索引。
动图演示
代码实现
public class QuickSort {
public static void main(String[] args) {
int[] a = {5, 3, 7, 2, 9, 8, 1, 4};
quick(a,0,a.length - 1);
}
public static void quick(int[] a, int l, int r){
if (l >= r) {
return;
}
int p = partition(a, l, r);
quick(a, l, p - 1);
quick(a, p + 1, r);
}
private static int partition(int[] a, int l, int r) {
int pv = a[r];
int i = l;
for (int j = i; j < r; j++) {
if (a[j] < pv){
if (i != j ) {
swap(a, i, j);
}
i++;
}
}
if ( i != r ) {
swap(a, i, r);
System.out.println(Arrays.toString(a)+" i = "+i);
}
return i;
}
static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
执行结果:
双边快排
1.选择最左元素作为基准点元素
2. j 指针负责从右往左找出比基准点小的元素,i 指针负责从左向右找出比基准点大的元素,一旦找到二者交换,直至 i,j 相交
3.最后基准点与 i(此时 i 和 j 相等)交换,i即为分区位置
动图演示
代码实现:
public class QuickSort2 {
public static void main(String[] args) {
int[] a = {5, 3, 7, 2, 9, 8, 1, 4};
quick(a,0,a.length - 1);
}
public static void quick(int[] a, int l, int r){
if (l >= r) {
return;
}
int p = partition(a, l, r);
quick(a, l, p - 1);
quick(a, p + 1, r);
}
private static int partition(int[] a, int l, int r) {
int pv = a[l];
int i = l;
int j = r;
while (i < j){
while (i < j && a[j] > pv){
j--;
}
while (i < j && a[i] <= pv){
i++;
}
swap(a, i, j);
}
swap(a, l, j);
System.out.println(Arrays.toString(a));
return j;
}
static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
执行结果: