目录
一 冒泡排序
二 选择排序
三 插入排序
四 希尔排序
五 快速排序
5.1 单边循环快速排序
5.2 双边循环快速排序
六 二分查找
七 总结
一 冒泡排序
- 依次比较数组中相邻的两个元素,若 arr[i] > arr[i+1],则交换两个元素,两两都比较一遍称为一轮冒泡,结果是最大的元素排在最后。
- 重复以上操作,直至整个数组有序。
- 每轮冒泡时,最后一次交换索引可以作为下一轮冒泡的比较次数,如果这个值为0,表示整个数组有序,直接退出外层循环。
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {5,9,7,4,1,2,8,3,6};
bubble(arr);
}
public static void bubble(int[] arr) {
int n = arr.length - 1;
while(true) {
int last = 0; // 最后一次交换索引位置
for(int i=0;i<n;i++) {
if(arr[i] > arr[i+1]) {
swap(arr, i, i+1);
last = i;
}
}
n = last;
if(n == 0) {
break;
}
System.out.println(Arrays.toString(arr));
}
}
public static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
二 选择排序
- 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集。
- 重复以上操作,直至整个数组有序。
- 每轮可以先找到最小的索引,在每轮最后在交换元素。
public class SelectionSort {
public static void main(String[] args) {
int[] arr = {5,9,7,4,1,2,8,3,6};
selection(arr);
}
public static void selection(int[] arr) {
for(int i=0;i<arr.length-1;i++) {
int s = i; // 记录最小元素索引
for(int j=s+1;j<arr.length;j++) {
if(arr[s] > arr[j]) {
s=j;
}
}
if(s != i) {
swap(arr, s, i);
}
System.out.println(Arrays.toString(arr));
}
}
public static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
三 插入排序
- 将数组分为两个区域,排序区和未排序区,每一轮从未排序区域选取第一个元素,插入到排序区域(需要保证顺序)。
- 重复以上操作,直至整个数组有序。
- 待插入元素进行比较时,遇到比自己小的元素,就代表找到了插入位置,无需进行后续比较。
public class InsertSort {
public static void main(String[] args) {
int[] arr = {5,9,7,4,1,2,8,3,6};
insert(arr);
}
public static void insert(int[] arr) {
// i 代表待插入元素的索引
for(int i=1;i<arr.length;i++) {
int t = arr[i]; // 待插入元素的值
int j = i-1; // 已排序元素的索引
while(j >= 0) {
if(t < arr[j]) {
arr[j+1] = arr[j];
} else {
break; // 退出循环,减少比较次数
}
j--;
}
arr[j+1] = t;
System.out.println(Arrays.toString(arr));
}
}
}
四 希尔排序
- 设置间隙序列。
- 将间隙相同元素划为一组,对每组使用插入排序。
- 重复以上操作,直至数组有序。
public class ShellSort {
public static void main(String[] args) {
int[] arr = {5,9,7,4,1,2,8,3,6};
shell(arr);
}
public static void shell(int[] arr) {
// 分而治之,循环为每次总数除二
for (int gap = arr.length/2; gap > 0; gap /= 2) {
// 循环分治的每一个分组使用插入排序
for (int i = gap; i < arr.length; i++) {
int t = arr[i];
int j = i-gap;
while (j >= 0) {
if (t < arr[j]) {
arr[j + gap] = arr[j];
} else {
break;
}
j -= gap;
}
arr[j+gap] = t;
System.out.println(Arrays.toString(arr));
}
System.out.println("gap="+gap+":"+Arrays.toString(arr));
}
}
}
五 快速排序
5.1 单边循环快速排序
- 选择最右边元素作为基准点元素。
- j 指针负责找到基准点小的元素,一旦找到则与 i 进行交换。
- i 指针维护小于基准点元素边界,也是每次交换的目标索引。
- 最后基准点与 i 交换,i 即为分区位置。
public class QuickSort {
public static void main(String[] args) {
int[] arr = {5,9,7,4,1,2,8,3,6};
quick(arr, 0, arr.length-1);
}
public static void quick(int[] arr, int l, int h) {
if(l >= h) {
return;
}
int pv = partition(arr, l, h);
quick(arr, l, pv-1); // 左边分区范围确定
quick(arr, pv+1, h); // 右边分区范围确定
}
public static int partition(int[] arr, int l, int h) {
int pv = arr[h];
int i = l;
for(int j=l;j<h;j++) {
if(arr[j] < pv) {
if(i != j) {
swap(arr, i, j);
}
i++;
}
}
if(i != h) {
swap(arr, h, i);
}
System.out.println(Arrays.toString(arr));
return i;
}
public static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
5.2 双边循环快速排序
- 选择最左边元素作为基准点元素。
- j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i、j 相交。
- 最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置。
public class QuickSort {
public static void main(String[] args) {
int[] arr = {5,9,7,4,1,2,8,3,6};
quick(arr, 0, arr.length-1);
}
public static void quick(int[] arr, int l, int h) {
if(l >= h) {
return;
}
int pv = partition(arr, l, h);
quick(arr, l, pv-1); // 左边分区范围确定
quick(arr, pv+1, h); // 右边分区范围确定
}
public static int partition(int[] arr, int l, int h) {
int pv = arr[l];
int i = l;
int j = h;
while(i < j) {
// j 从右找小的
while(i < j && arr[j] > pv) {
j--;
}
// i 从左找大的
while(i < j && arr[i] <= pv) {
i++;
}
swap(arr, i, j);
}
swap(arr, l, j);
System.out.println(Arrays.toString(arr));
return j;
}
public static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
六 二分查找
1、前提:有已排序的数组 arr。
2、定义左边界 l,右边界 r,确定搜索范围,循环执行二分查找(3、4步)。
3、获取中间索引 m = Floor((l+r)/2)。
4、中间索引的值 arr[m] 与待搜索的值 t 进行比较。
- arr[m] == t 表示找到,返回中间索引。
- arr[m] > t,中间值右侧的其它元素都大于 t,无需比较,中间索引左边去找,m-1 设置为右边界,重新查找。
- arr[m] < t,中间值左侧的其它元素都大于 t,无需比较,中间索引右边去找,m+1 设置为左边界,重新查找。
5、当 l > r,表示没有找到,结束循环。
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50};
int t = 48;
int index = binarySearch(arr, t);
System.out.println(index);
}
public static int binarySearch(int[] arr, int t) {
int l = 0, r = arr.length - 1, m;
while(l <= r) {
//m = l+(r-l)/2; // 解决整数溢出
m = (l+r) >>> 1;
if(arr[m] == t) {
return m;
} else if(arr[m] > t) {
r = m-1;
} else {
l = m+1;
}
}
return -1;
}
}
6、面试题
- 奇数二分取中间
- 偶数二分取中间靠左
1、有一个有序表为[1,5,8,11,19,22,31,35,40,48,49,50],当二分查找值为48的节点时,需经过几次比较?
2、有一个有序表为[1,4,6,7,15,33,50,64,75,78,81,89,96],当二分查找值为81的节点时,需经过几次比较?
3、在拥有128个元素的数组中二分查找一个数,需要比较的次数最多不超过几次?
- /
- 整数,则该整数即为最终结果
- 小数,舍去小数部分,整数+1即为最终结果
七 总结
- int[] arr = {5,9,7,4,1,2,8,3,6}
时间复杂度 | 实现 | 优化 | 排序过程 | |
冒泡排序 | O() |
|
| [5, 7, 4, 1, 2, 8, 3, 6, 9] [5, 4, 1, 2, 7, 3, 6, 8, 9] [4, 1, 2, 5, 3, 6, 7, 8, 9] [1, 2, 4, 3, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9] |
选择排序 | O() |
|
| [1, 9, 7, 4, 5, 2, 8, 3, 6] [1, 2, 7, 4, 5, 9, 8, 3, 6] [1, 2, 3, 4, 5, 9, 8, 7, 6] [1, 2, 3, 4, 5, 9, 8, 7, 6] [1, 2, 3, 4, 5, 9, 8, 7, 6] [1, 2, 3, 4, 5, 6, 8, 7, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9] |
插入排序 | O() |
|
| [3, 5, 1, 7, 2, 9, 8, 4, 6] [1, 3, 5, 7, 2, 9, 8, 4, 6] [1, 3, 5, 7, 2, 9, 8, 4, 6] [1, 2, 3, 5, 7, 9, 8, 4, 6] [1, 2, 3, 5, 7, 9, 8, 4, 6] [1, 2, 3, 5, 7, 8, 9, 4, 6] [1, 2, 3, 4, 5, 7, 8, 9, 6] [1, 2, 3, 4, 5, 6, 7, 8, 9] |
希尔排序 |
| [1, 9, 7, 4, 5, 2, 8, 3, 6] [1, 2, 7, 4, 5, 9, 8, 3, 6] [1, 2, 7, 4, 5, 9, 8, 3, 6] [1, 2, 7, 3, 5, 9, 8, 4, 6] [1, 2, 7, 3, 5, 9, 8, 4, 6] gap=4:[1, 2, 7, 3, 5, 9, 8, 4, 6] [1, 2, 7, 3, 5, 9, 8, 4, 6] [1, 2, 7, 3, 5, 9, 8, 4, 6] [1, 2, 5, 3, 7, 9, 8, 4, 6] [1, 2, 5, 3, 7, 9, 8, 4, 6] [1, 2, 5, 3, 7, 9, 8, 4, 6] [1, 2, 5, 3, 7, 4, 8, 9, 6] [1, 2, 5, 3, 6, 4, 7, 9, 8] gap=2:[1, 2, 5, 3, 6, 4, 7, 9, 8] [1, 2, 5, 3, 6, 4, 7, 9, 8] [1, 2, 5, 3, 6, 4, 7, 9, 8] [1, 2, 3, 5, 6, 4, 7, 9, 8] [1, 2, 3, 5, 6, 4, 7, 9, 8] [1, 2, 3, 4, 5, 6, 7, 9, 8] [1, 2, 3, 4, 5, 6, 7, 9, 8] [1, 2, 3, 4, 5, 6, 7, 9, 8] [1, 2, 3, 4, 5, 6, 7, 8, 9] gap=1:[1, 2, 3, 4, 5, 6, 7, 8, 9] | ||
快速排序-单边循环 | O() |
| [5, 4, 1, 2, 3, 6, 8, 7, 9] [1, 2, 3, 4, 5, 6, 8, 7, 9] [1, 2, 3, 4, 5, 6, 8, 7, 9] [1, 2, 3, 4, 5, 6, 8, 7, 9] [1, 2, 3, 4, 5, 6, 8, 7, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9] | |
快速排序-双边循环 | 平均:O() 最坏:O() |
| [1, 3, 2, 4, 5, 7, 8, 9, 6] [1, 3, 2, 4, 5, 7, 8, 9, 6] [1, 2, 3, 4, 5, 7, 8, 9, 6] [1, 2, 3, 4, 5, 6, 7, 9, 8] [1, 2, 3, 4, 5, 6, 7, 8, 9] |