快速排序算法
算法步骤:
对于这种递归调用的程序,我认为只需要关注运行一个周期的过程。
- 首先任意选择一个基准点,方便起见,对于每个段都选择第一个元素作为基准
- 为什么说给这个元素起名基准点,因为我们接下来要以这个元素应该在的位置为界,划分原数组为左,界,右三个部分。那么如何确定这个元素应该在的位置呢?所有比它小的元素都在它左侧,比它大的元素都在它右侧,而左侧右侧内部是否顺序并不影响对该元素应在位置的判定。
- 实现上述描述的方法其实就是,将左边大数向右扔,将右边小数向左扔。快速排序在这一步骤上的想法是:申请左指针和右指针,当两个指针还没有相遇时,每次先从右边开始,移动右指针直到所指元素小于基准元素或与左指针相遇,再移动左指针直到所指元素大于基准元素或与右指针相遇。如果左右指针还未相遇,那么就交换大小数,并开始下一轮循环直到左右指针相遇。
- 左右指针相遇的位置就是基准元素的应在位置,交换初始位置与经过调整数组后符合左小右大的相遇位置上的元素。(注意这里不是说所有左侧都满足左小,而是基准数左侧都小于基准数;右大,同理)
- 以基准元素现在的位置为界,将数组分为左段,它,右段
- 对左段和右段分别再递归调用快排函数
- 接下来,其实该想起来写递归函数第一时间要写出的逻辑——跳出语句。这里当输入参数的左右指针相等时,就可以跳出递归,也就是返回空,不再对数组做任何处理。(我在写代码时,发现Java对于的判定和C++不一样,所以**不能用,而是用左>=右**的方式来终止两指针重合下的快排调用)
代码:
package sort;
public class quicksort {
public static void quickSort(int[] arr , int low , int high) {
if (low >= high) return ;//跳出语句
int key = arr[low];
int i = low;
int j = high;
while (i < j) {
while (i < j && arr[j] >= key) j --;//找小数
while (i < j && arr[i] <= key) i ++;//找大数
if (i < j) { //交换
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
arr[low] = arr[i];
arr[i] = key;//符合左小右大的基准数位置
quickSort(arr , low , i - 1);//递归调用
quickSort(arr , i + 1 , high);
}
public static void main(String[] args) {
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr , 0 , arr.length - 1);
for (int i = 0; i < arr.length; i ++) {
System.out.print(arr[i] + " ");
}
}
}
运行结果:
算法评价:
- 初始序列有序时,效率最低;右指针第一次比较 n - 1 次,每轮减 1 ,故此时总比较次数为 n*(n - 1) / 2
- 快速排序需要栈的辅助
- 最好时间复杂度:O(nlogn)
- 最坏时间复杂度:O(n²)
- 平均时间复杂度:O(nlogn)
- 空间复杂度:O(logn)
冒泡排序
算法步骤:
- 第 i 轮可以使第 i 大的数处在倒数第 i 位上。
- 一共进行 length - 1次
- 每一轮,对无序部分,从左向右扫描,相邻逆序则交换
代码:
package sort;
public class bubblesort {
public static void bubbleSort(int[] arr) {
int len = arr.length;
for (int i = 0; i < len - 1; i ++) {
int tmp;
for (int j = 0; j < len - 1 - i; j ++) { // n个数从左往右依次是 n-1 对
if (arr[j] > arr[j + 1]) { //相邻逆序交换
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
bubbleSort(arr);
for (int i = 0; i < arr.length; i ++) {
System.out.print(arr[i] + " ");
}
}
}
运行结果:
算法评价:
- 初始序列有序时,效率最高,此时关键字的比较次数为 n - 1
- 初始序列逆序时,效率最低,此时关键字的比较次数为 n*(n - 1) / 2
- 不必完成所有的排序就可以得到最大/小元素
- 最好时间复杂度:O(n)
- 最差时间复杂度:O(n²)
- 平均时间复杂度:O(n²)
- 空间复杂度:O(1)
直接插入排序
算法步骤:
- 将数组划分为有序部分和无序部分
- 初始状态:第一个数构成有序部分,无序部分从第二个数开始
- 选择无序部分的第一个元素,在有序部分中从右向左比较,比该元素大的,向后覆盖数据,直到不再比该元素大,有序部分的查找终止,同时将终止时的查找位置保存下来。刚刚保存的位置及以左的元素就是比该元素小的有序部分,后一位就是该元素放置的地方
- 一共直接插入 length - 1 次即可
代码:
package sort;
public class Inlinesort {
public static void InlineSort(int[] arr) {
int len = arr.length;
for (int i = 1; i < len; i ++) {
int temp = arr[i];
int j;
for (j = i - 1; j >= 0 && arr[j] > temp; j --) { //从右向左扫描
arr[j + 1] = arr[j];
}
arr[j + 1] = temp;
}
}
public static void main(String[] args) {
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
InlineSort(arr);
for (int i = 0; i < arr.length; i ++) {
System.out.print(arr[i] + " ");
}
}
}
运行结果:
算法评价:
- 初始序列有序时,效率最高,此时关键字的比较次数为 n - 1
- 初始序列逆序时,效率最低,此时关键字的比较次数为 n*(n - 1) / 2
- 最好时间复杂度:O(n)
- 最差时间复杂度:O(n²)
- 平均时间复杂度:O(n²)
- 空间复杂度:O(1)
折半插入排序
算法步骤:
-
折半插入是对直接插入的优化。直接插入的比较次数与移动次数相当,而折半插入将查找应放位置和移动应放位置后续元素分为两大步。在查找应放位置时使用了折半查找的方法,选择使用这种方法的原因是直接插入算法思想的有序无序结构带来的前半段查找的有序性。这样就对比较次数进行了优化。
-
通过对有序部分的折半查找确定该元素应在位置。将后续元素依次向后覆盖,将该元素放入应在位置。
-
注意:只有当无序部分的第一个元素小于有序部分的最后元素(有序部分max)执行覆盖,否则会因为左右指针相遇导致原本无需变动的无序部分的第一个元素与有序部分的最后一个数交换(因为左右指针的活动范围是有序部分,并不会向后延伸)
-
关于折半查找,我总结了一个结构
-
while (l < r) { int m = (l + r) >> 1; if (arr[m] > arr[i]) r = m; // 中间大则缩小右界至中界 else l = m + 1; // 中间小则放大左界至中界 + 1 }
-
代码:
package sort;
public class halfsort {
public static void halfSort(int[] arr) {
int len = arr.length;
if (arr[0] > arr[1]) {
int tmp = arr[0];
arr[0] = arr[1];
arr[1] = tmp;
}
for (int i = 2; i < len; i ++) {
int l = 0;
int r = i - 1;
while (l < r) {
int m = (l + r) >> 1;
if (arr[m] > arr[i]) r = m;
else l = m + 1;
}
int temp = arr[i];
if (temp < arr[l]) {
for (int j = i - 1; j >= l; j --) {
arr[j + 1] = arr[j];
}
arr[l] = temp;
}
}
}
public static void main(String[] args) {
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
halfSort(arr);
for (int i = 0; i < arr.length; i ++) {
System.out.print(arr[i] + " ");
}
}
}
运行结果:
算法评价:
- 关键字的比较次数与初始序列无关
- 最好时间复杂度:O(nlogn)
- 最差时间复杂度:O(n²)
- 平均时间复杂度:O(n²)
- 空间复杂度:O(1)
直接选择排序
算法步骤:
- 每次选择无序部分最小/大的数放在有序部分的末尾
- 每轮查找无序部分的最小/大数无需频繁交换,只需要记录下该数的位置,查找结束后,将该位置上的元素交换到有序部分的末尾
代码:
package sort;
public class directSort {
public static void directSort (int[] arr) {
int len = arr.length;
for (int i = 0; i < len - 1; i ++) {
int flag = i; // 保存位置
for (int j = i + 1; j < len; j ++) {
if (arr[j] < arr[flag]) {
flag = j;
}
}
int temp = arr[i];
arr[i] = arr[flag];
arr[flag] = temp;
}
}
public static void main (String[] args) {
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
directSort(arr);
for (int i = 0; i < arr.length; i ++) {
System.out.print(arr[i] + " ");
}
}
}
运行结果:
算法评价:
- 不必完成所有的排序就可以得到最大/小元素
- 关键字比较次数与初始序列无关,总是 n*(n - 1) / 2
- 最好时间复杂度:O(n²)
- 最差时间复杂度:O(n²)
- 平均时间复杂度:O(n²)
- 空间复杂度:O(1)