3.3 排序算法
1.什么是排序算法
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。
2.评价标准
时间复杂度
:即从序列的初始状态到经过排序算法的变换移位等操作变到最终排序好的结果状态的过程所花费的时间度量空间复杂度
:就是从序列的初始状态经过排序移位变换的过程一直到最终的状态所花费的空间开销使用场景
:排序算法有很多,不同种类的排序算法适合不同种类的情景,可能有时候需要节省空间对时间要求没那么多,反之,有时候则是希望多考虑一些时间,对空间要求没那么高,总之一般都会必须从某一方面做出抉择稳定性
:稳定性是不管考虑时间和空间必须要考虑的问题,往往也是非常重要的影响选择的因素。
3.常见排序算法
3.1冒泡排序
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从小到大)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
冒泡排序最好的时间复杂度为O(n) 、最坏时间复杂度为O(n*n)、平均时间复杂度为O(n*n)、稳定的排序算法
public class BubbleSortTest {
public static void main(String[] args) {
int nums[] = {9, 3, 5, 4, 8, 2, 3, 1};
sort(nums);
for (int num : nums) {
System.out.println(num);
}
}
private static void sort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums.length - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}
}
算法稳定性
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法
3.2插入排序
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动
插入排序的工作方式像许多人排序一手扑克牌。开始时,我们的左手为空并且桌子上的牌面向下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。拿在左手上的牌总是排序好的,原来这些牌是桌子上牌堆中顶部的牌。
代码实现
public class InsertSortTest {
private static void printArr(int[] arr, int n) {
System.out.print("第" + n + "次:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println("");
}
private static void insertSort(int[] arr) {
int n = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0; j--) {
n++;
if (arr[j + 1] < arr[j]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
} else {
break;
}
printArr(arr, n);
}
}
}
public static void main(String[] args) {
int arr[] = { 7, 4, 1, 8, 5, 2, 9, 6, 3, 10 };
insertSort(arr);
}
}
插入排序最好时间复杂度是 O(n) 、最坏时间复杂度为O(n*n) 、平均时间复杂度为O(n*n) 、稳定的排序算法
3.3归并排序
是利用递归与分治的技术将数据序列划分为越来越小的半子表,再对半子表排序,最后再用递归方法将排好序的半子表合并成越来越大的有序序列。
重点去理解递归的执行顺序和归并排序的原理
public class MergeSortTest {
/**
*
* @param a 数组
* @param p 开始位置下标
* @param r 结束位置下标
*/
private static void mergeSort(int[] a, int p, int r) {
if (p < r) {
int q = (p + r) / 2;
mergeSort(a, p, q);
mergeSort(a, q + 1, r);
merge(a, p, q, r);
}
}
/**
* 将两个小组进行排序+合并
*
* @param a 需要排序的数组
* @param p 左数组的开始位置下标
* @param q 左数组的结束位置下标
* @param r 右数组的结束位置下标
*/
private static void merge(int[] a, int p, int q, int r) {
int n1 = q - p + 1;// 左边序列的长度
int n2 = r - q; // 右边序列的长度
int L[] = new int[n1];
int R[] = new int[n2];
// k用来表示当前遍历的数组a的索引
int i = 0, j = 0, k = 0;
// 1.给L赋值
for (i = 0, k = p; i < n1; i++, k++) {
L[i] = a[k];
}
// 2.给R赋值
for (j = 0, k = q + 1; j < n2; j++, k++) {
R[j] = a[k];
}
// 3.比较大小,从小到大排列 此处注意:结束条件并不是n1+n2,所有会有下一个步骤
for (i = 0, j = 0, k = p; i < n1 && j < n2; k++) {
if (L[i] > R[j]) {
a[k] = R[j];
j++;
} else {
a[k] = L[i];
i++;
}
}
// 4.将两个数组中剩下的数放到a中(以下只有一个满足条件)
if (i < n1) {
for (j = i; j < n1; j++, k++) {
a[k] = L[j];
}
}
if (j < n2) {
for (i = j; i < n2; i++, k++) {
a[k] = R[i];
}
}
}
public static void main(String[] args) {
int arr[] = { 10, 15, 8, 16, 7, 54, 88, 44, 33, 20 };
int len = arr.length;
mergeSort(arr, 0, len - 1);
System.out.println(Arrays.toString(arr));
}
}
归并排序最好、最坏、平均时间复杂度都是 O(nlogn)
归并排序空间复杂度为 O(n)
归并排序是稳定的排序算法
3.4快速排序
快速排序法的原理也是分治法。它的每轮迭代,会选取数组中任意一个数据作为分区点,将小于它的元素放在它的左侧,大于它的放在它的右侧。再利用分治思想,继续分别对左右两侧进行同样的操作,直至每个区间缩小为 1,则完成排序。
public class QuickSortTest {
/**
* 快速排序
*
* @param arr 需要快排的数组
* @param low 数组区间左下标
* @param high 数组区间右下标
*/
private static void quickSort(int[] arr, int low, int high) {
int left;// 左指针
int right;//有指针
int p;// 分区点
int t;// 交换数据的中间变量
// 递归的结束条件
if (low >= high) {
return;
}
left = low;
right = high;
p = arr[low];// 选择最左侧的点为分区点
while (left < right) {
// 如果右侧的数据 大于 分区点 则只需要将右指针左移一位
while (p <= arr[right] && left < right) {
right--;
}
// 如果右侧的数据 小于 分区点 则只需要将左指针右移一位
while (p >= arr[left] && left < right) {
left++;
}
// 以上两个while循环结束就会遇到 p>arr[right] 和p<arr[left]的情况 直接执行交换两个数据
// 或者 left = right 直接结束外层while循环
t = arr[right];
arr[right] = arr[left];
arr[left] = t;
}
// 此时left==right 将分区点 与指针相等点的交换 就会形成左侧的数据都小于 p, 右侧的数据都大于p
arr[low] = arr[left];
arr[left] = p;
// 递归调用左半数组
quickSort(arr, low, right - 1);
// 递归调用右半数组
quickSort(arr, right + 1, high);
}
public static void main(String[] args) {
int[] arr = { 7, 1, 6, 3, 10, 2, 4, 5, 8, 9 };
System.out.println("原始数据: " + Arrays.toString(arr));
quickSort(arr, 0, arr.length - 1);
System.out.println("快速排序: " + Arrays.toString(arr));
}
}
快速排序平均时间复杂度都是 O(nlogn)
归并排序空间复杂度为 O(1)
快速排序是不稳定的排序算法