快速排序
分治的思想,选择一个基准点,将小于这个基准点的都放在基准点的左边,大于这个基准点的都放在这个基准点的右边
实现方式:
- 1 挖坑填数
- 2 左右指针
特别注意!(基准点的选择一定要是随机的)
我们每次递归使用的是区间第一个(或最后一个)元素作为基准点。
如果数据是正序或者逆序时,我们这样选取的基准值显然是不对的,这样的其他数据都在基准值的一侧,我们是无法划分出两个区域来分开排序的。
快速排序为什么快,就是因为基准点的两边同时排序。如果只能分出一遍来,那么这个排序算法就退化了。
以下三个图,说明了选取基准点的重要性(数据为一百万个有序元素):
- 使用第一个元素作为基准点。栈溢出了
- 使用随机数
- 使用 三个点(l,r,mid)的中间值
代码:挖坑填数法实现
public class QuickSort {
static int[] quick_sort(int[] nums, int l, int r){
if (l + 1 >= r) {
return null;
}
int first = l, last = r - 1, key = nums[first];
while (first < last){
while(first < last && nums[last] >= key) {
--last;
}
nums[first] = nums[last];
while (first < last && nums[first] <= key) {
++first;
}
nums[last] = nums[first];
}
nums[first] = key;
quick_sort(nums, l, first);
quick_sort(nums, first + 1, r);
return nums;
}
}
// 调用方法示例
QuickSort.quick_sort(nums, 0, nums.length)
补充:选取基准点 pivot 的最好方式:
public static int getMid(int[] nums, int l, int r) {
int mid = (l+r) >>> 1;
// 将最大的元素交换到 r
if(nums[mid] > nums[r]) {
swap(nums, l, mid);
}
if(nums[l] > nums[r]) {
swap(nums, l, r);
}
// 剩下的两个元素中,将第二大的放在放在 l位置
if(nums[l] < nums[mid]) {
// 如果l是小一点的,交换
swap(nums, l, mid);
}
return l;
}
注意!快速排序并不是一个稳定的算法
例如:[5, 4 ,9 , 1, 1, 1]
基准点选定4,那么最右边的 1,就会跑到4那里取,这样破坏了原本的相同的数字之间的相对排序。
稳定意味着程序的健壮性,比方说数据库中对某一列进行排序,如果是不稳定的排序。就会破坏此列相同数据的的其它列的顺序。
归并排序
public class MergeSort {
static int[] merge_sort(int[] nums, int l, int r, int[] temp) {
if (l + 1 >= r) {
return null;
}
int m = l + (r - l) / 2;
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp);
int p = l, q = m, i = l;
while (p < m || q < r) {
if (q >= r || (p < m && nums[p] <= nums[q])) {
temp[i++] = nums[p++];
} else {
temp[i++] = nums[q++];
}
}
for (i = l; i < r; ++i) {
nums[i] = temp[i];
}
return nums;
}
}
插入排序
- 将数组分为两部分:[排序好了的][没有排序的]
- 每次将右边的第一个,从右到左扫描排序好了的,如果发现小于就交换位置,否则不变
public class InsertionSort {
static int[] insertion_sort(int[] nums, int n) {
for (int i = 0; i < n; ++i) {
for (int j = i; j > 0 && nums[j] < nums[j-1]; --j) {
int temp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = temp;
}
}
return nums;
}
}
冒泡排序
原理:比较两个相邻的元素,将值大的元素交换到右边
思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。
这样一来:
- 每一轮交换都会把最大的元素放在最后面,
- 下一轮交换的时候,已经确定了的(右边的)元素就不用在进行交换了。(
j<n-i+1
的由来) - 如果此轮没有进行交换,说明数组已经有序,退出for循环
public class BubbleSort {
static int[] bubble_sort(int[] nums, int n) {
boolean swapped;
for (int i = 1; i < n; ++i) {
swapped = false;
for (int j = 1; j < n - i + 1; ++j) {
if (nums[j] < nums[j-1]) {
int temp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = temp;
swapped = true;
}
}
// 如果没有发生交换,说明已经有序
if (!swapped) {
break;
}
}
return nums;
}
}
选择排序
算法思想:
从头至尾扫描序列,找出最小的一个元素,和第一个元素交换。
接着从剩下的元素中继续这种方式,最终得到一个有序序列。
public class SelectionSort {
static int[] selection_sort(int[] nums, int n) {
int mid;
for (int i = 0; i < n - 1; ++i) {
mid = i;
for (int j = i + 1; j < n; ++j) {
if (nums[j] < nums[mid]) {
mid = j;
}
}
int temp = nums[mid];
nums[mid] = nums[i];
nums[i] = temp;
}
return nums;
}
}
调用以上排序代码
public class Main {
public static void main(String[] args) {
int[] nums = {1, 3, 5, 7, 2, 6, 4, 8, 9, 2, 8, 7, 6, 0, 3, 5, 9, 4, 1, 0};
int[] temp = new int[nums.length];
printLog(QuickSort.quick_sort(nums, 0, nums.length), "quickSort");
printLog(MergeSort.merge_sort(nums, 0, nums.length, temp), "mergeSort");
printLog(InsertionSort.insertion_sort(nums, nums.length), "insertionSort");
printLog(BubbleSort.bubble_sort(nums, nums.length), "bubbleSort");
printLog(SelectionSort.selection_sort(nums, nums.length), "selectionSort");
}
public static void printLog(int[] rets, String desc) {
System.out.println(desc);
for (int num : rets) {
System.out.print(num + ", ");
}
System.out.println("\n");
}
}