目录
1、冒泡排序
描述:
1. 依次比较数组中相邻两个元素大小,若 a[j] > a[j+1],则交换两个元素,两两都比较一遍称为一轮冒泡,结果是让最大的元素排至最后
2. 重复以上步骤,直到整个数组有序
算法实现:
public class BubbleSort {
public static void main(String[] args) {
int[] a = {5,9,7,4,15,11,22,20};
System.out.println("数组长度为" + a.length);
// bubble(a);
bubble_v2(a);
}
/**
* 方法一:第一次优化:每经过一次循环 内层循环就减少一次
* @param a
*/
public static void bubble(int[] a) {
for (int j = 0; j < a.length-1; j++) {
boolean swapped = false;
for (int i = 0; i < a.length - 1 -j; i++) {
System.out.println("比较次数" + i);
if (a[i] > a[i + 1]) {
swap(a, i, i + 1);
swapped = true;
}
}
System.out.println("第"+ j + "轮冒泡"+Arrays.toString(a));
if(!swapped){
break;
}
}
}
/**
* 方法二:优化过后
* 每经过一次循环 内层循环就减少一次
* 如果某一轮冒泡没有发生交换,则表示所有数据有序,可以结束外层循环
* @param a
*/
public static void bubble_v2(int[] a) {
int n = a.length - 1;
while (true) {
int last = 0; //最后一次交换索引的位置
for (int i = 0 ; i < n ; i++){ //交换次数 == 最后最后一次比较时候i的索引
System.out.println("比较次数" + i);
if (a[i] > a[i+1]) {
swap(a,i,i+1);
last = i;
}
}
System.out.println("第轮冒泡"+Arrays.toString(a));
n = last;
if (last == 0) {
break;
}
}
}
public static void swap(int[] a, int i ,int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
2、选择排序
描述:
1. 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集
2. 重复以上步骤,直到整个数组有序
与冒泡比较
1. 二者平均时间复杂度都是 $O(n^2)$
2. 选择排序一般要快于冒泡,因为其交换次数少
3. 但如果集合有序度高,冒泡优于选择
4.冒泡稳定,选择不稳定
/**
* 选择排序:
* 将数组分为两个子集,排序和未排序,每一轮从未排序的子集中选出最小的元素,放入排序子集
* 重复以上步骤
*/
public class SelectionSort {
public static void main(String[] args) {
int[] a = {5,9,7,4,15,11,22,20};
selection(a);
}
public 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++) { //本轮找出最小值的索引 赋给s
if (a[s] > a[j]) {
s = j;
}
}
if (s != i) { //s不等于i表示 最小值索引变了
swap(a,s,i);
}
System.out.println(Arrays.toString(a));
}
}
public static void swap(int[] a, int i ,int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
3、插入排序
算法描述
1. 将数组分为两个区域,排序区域和未排序区域,每一轮从未排序区域中取出第一个元素,插入到排序区域(需保证顺序)
2. 重复以上步骤,直到整个数组有序
public class InsertSort {
public static void main(String[] args) {
int[] a = {9,3,7,2,5,8,1,4};
System.out.println("素组元素大小"+a.length);
insertsort(a);
System.out.println("最后结果"+ Arrays.toString(a));
}
private static void insertsort(int[] a) {
// i 表示等待插入的值,索引从1开始
for (int i = 1; i < a.length; i++) {
int t = a[i]; //t带插入的值
int j = i-1; //代表已经排序的索引
while (j >= 0 ){
if ( t < a[j]) { //插入的值小于前面已经排序的
a[j+1] = a[j]; //待插入的就往后移动
}else {
break; //否则就是t比前面都打
}
j--; //往前面去比较
}
a[j+1] = t; //while循环从break结束。将t插入j+1索引 也就是与t比较的后一位,因为此时t比j大
System.out.println(Arrays.toString(a));
}
}
}
4、快速排序
算法描述
1. 每一轮排序选择一个基准点(pivot)进行分区
1. 让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区
2. 当分区完成时,基准点元素的位置就是其最终位置
2. 在子分区内重复以上过程,直至子分区元素个数少于等于 1,这体现的是分而治之的思想 ([divide-and-conquer](https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm))
3. 从以上描述可以看出,一个关键在于分区算法,常见的有洛穆托分区方案、双边循环分区方案、霍尔分区方案
---------------单边循环快排(lomuto 洛穆托分区方案)
1. 选择最右元素作为基准点元素
2. j 指针负责找到比基准点小的元素,一旦找到则与 i 进行交换
3. i 指针维护小于基准点元素的边界,也是每次交换的目标索引
4. 最后基准点与 i 交换,i 即为分区位置
public class QuickSort1 {
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 h){
if (l >= h) {
return;
}
int p = partition(a,l,h);
System.out.println("quick里面的p="+p);
quick(a, l, p-1);
quick(a, p+1,h);
}
/**
* 以基准点做分区
* @param a 数组
* @param l 上边界
* @param h 下边界
* @return
*/
private static int partition(int[] a, int l, int h) {
int pv = a[h]; //选取最后一位元素做为比较基准点
int i = l; //维护小于基准点元素边界
for (int j = l; j < h; j++) { //j负责往后循环与pv比较
if (a[j] < pv) {
swap(a,i,j); //a[j]比pv小就放到i前面
i++; //小于基准点元素的边界往后+1
}
}
swap(a,h,i); //最后基准点与i交换,i就是分区位置
System.out.println(Arrays.toString(a) + " i=" + i);
//返回值为基准点元素所在的正确索引,用他确定下一轮分区边界
return i;
}
public static void swap(int[] a, int i ,int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
---------------双边循环快排-
1. 选择最左元素作为基准点元素
2. j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i,j 相交
3. 最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置
要点
1. 基准点在左边,并且要先 j 后 i