一.八大排序简介
1.冒泡排序
(1)算法思想
从前向后遍历,依次比较相邻元素的值,若为逆序则交换,使较大的元素逐渐从前部移向后部,每次遍历结束后最大的元素移至末尾,下一次无需遍历该元素。
优化:若某次遍历中未交换元素,则表明数组有序,排序结束。
(2)特点
1)算法简单,程序简便,但交换次数多,较费时 2)对n个元素的数组需遍历n-1次,每次遍历将最大的元素放在最后,下次无需遍历该元素。
(3)图解
(4)代码
public static void BubbleSort(int[] arr) {
boolean flag = false;
int temp;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (!flag) break;
flag = false;
}
}
2.选择排序
(1)算法思想
依次从数组中选出最小(最大)的元素与数组第一个元素交换,每次遍历只交换一次。 第一次从arr[0] ~ arr[n-1]中选出最小元素,与arr[0]交换; 第二次从arr[1] ~ arr[n-1]中选出最小元素,与arr[1]交换; 第三次从arr[2] ~ arr[n-1]中选出最小元素,与arr[2]交换; … 第n-1次arr[n-2] ~ arr[n-1]中选出最小元素,与arr[n-2]交换。 共交换n-1次。
(2)特点
交换次数比冒泡排序少。
(3)图解
(4)代码
public static void SelectSort(int[] arr) {
int arr_min;
int index;
for (int i = 0; i < arr.length - 1; i++) {
arr_min = arr[i];
index = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr_min) {
index = j;
arr_min = arr[j];
}
}
if (index != i) {
arr[index] = arr[i];
arr[i] = arr_min;
}
}
}
3.插入排序
(1) 算法思想
将数组看为以第一个元素为基准的有序数组和包含其他元素的无序数组,将无序数组中的元素依次插入有序数组(与数组中的每个值比较,找到位置)。 在代码中未额外开辟空间,而是采用从右向左比较,若有序数组中比较的元素比该元素大则右移,否则插入的方法。
(2)特点
适合数组本身比较有序的情况,若数组本身逆序程度较大(有序表很长,需插入的数较小)则在插入数据时需多次右移,增加算法时间。
(3)图解
(4)代码
public static void InsertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int insertVal = arr[i];
int insertIndex = i - 1;
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
if (insertIndex + 1 != i) arr[insertIndex + 1] = insertVal;
}
}
4.希尔排序
(1)算法思想
把数组按一定增量分组,对每组使用直接插入排序,之后将增量/2,再次排序,直到增量为1,进行最后一次排序。
设数组长度为n。 第1次增量为n/2,共分n/2组(即索引为j的元素与索引为j-n/2,j+n/2,j+n…的元素在同一组),进行n/2次直接插入排序。 第2次增量为n/4,共分n/4组,进行n/4次直接插入排序。 … 第log2n次增量为1,共分1组,进行1次插入排序,排序后的结果即为有序数组。
(2)特点
经调整后,对索引值较大、数值较小的元素在前几次排序中已经插入到前面,最后一次元素较有序,算法时间减少。
(3)图解
(4)代码
public static void ShellSort(int[] arr) {
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
while (j - gap >= 0 && temp < arr[j - gap]) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
5.快速排序
(1)算法思想
基于数组中的一个基准元素(代码中为索引在中间的数)将数组分成两部分,左部分的所有元素都小于等于基准元素,右部分的所有元素都大于等于基准元素,再将左右两部分分别递归进行相同操作,直到只剩一个或两个元素。
(2)特点
速度快,最常使用的排序算法,使用递归以空间换时间。
(3)图解
以最左端元素作为基准元素 以最右端元素作为基准元素
(4)代码
public static void QuickSort(int[] arr, int left, int right) {
int l = left;
int r = right;
int pivot = arr[(l + r) / 2];
int temp;
while (l < r) {
while (arr[l] < pivot) l++;
while (arr[r] > pivot) r--;
if (l >= r) break;
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
if (arr[l] == pivot) r--;
if (arr[r] == pivot) l++;
}
if (l == r) {
l++;
r--;
}
if (left < r) QuickSort(arr, left, r);
if (right > l) QuickSort(arr, l, right);
}
6.归并排序
(1)算法思想.
采用分治算法,先将数组分为左右两个子数组,递归将子数组二分化,直到每个子数组只有一个元素;之后使用合并有序数组的方法(双指针)逐个将子数组合并,直到最终生成有序数组。
(2)特点
与快速排序的区别:快速排序在分的时候按照基准元素将大的分在一边,小的分在一边,分完即有序,只需依次合并;归并排序在分的时候直接按照索引值分,在合并的时候才考虑顺序,采取从下到上的方式,用两个有序子数组并成一个有序数组。
(3)图解
(4)代码
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
int t = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
i++;
t++;
} else temp[t++] = arr[j++];
}
while (i <= mid) temp[t++] = arr[i++];
while (j <= right) temp[t++] = arr[j++];
t = 0;
int tempLeft = left;
while (tempLeft <= right) arr[tempLeft++] = temp[t++];
}
public static void MergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2;
MergeSort(arr, left, mid, temp);
MergeSort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
}
7.基数排序
(1)算法思想
与桶排序相似,按照数组元素各位的大小将其装入不同的桶从而进行排序。 并不考虑正负的情况下,共有10个桶,分别代表某元素的某位为0-9,当比较某位时,将数组中的每个元素按照该位的数字(位数不足的补0)放入对应的桶中,全放入后按桶中的顺序拷贝到数组中,更新数组,之后接着比较上一位的数字,直到最高位,之后更新的数组就是有序数组。
设数组中最大数有i位。 第一次比较个位,将数组中的每个元素按照个位放到对应的桶中,之后将个位为0的第一个数到个位为9的最后一个数依次放入数组,个位越小越靠前。 第二次比较十位,更新数组后十位越小越靠前,十位相同的元素中个位越小越靠前(在前一次排序中将其排到个位较大元素的前面)。 … 第i次比较第i位,其他第i位为0的元素均在最前,第i位不为0的元素在其之后按照该位大小排序,更新数组之后即为有序数组。
(2)特点
目前的代码不适用于存在负数的情况,对于只有一个元素特别大的情况会耗费大量时间。
(3)图解
(4)代码
public static void RadixSort(int[] arr) {
int[][] bucket = new int[10][arr.length];
int[] counts = new int[10];
int max_num = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max_num) max_num = arr[i];
}
int max_len = (max_num + "").length();
for (int l = 0; l < max_len; l++) {
for (int num : arr) {
int digit = num / (int)(Math.pow(10, l)) % 10;
bucket[digit][counts[digit]] = num;
counts[digit]++;
}
int index = 0;
for (int j = 0; j < counts.length; j++) {
if (counts[j] != 0) {
for (int k = 0; k < counts[j]; k++) {
arr[index] = bucket[j][k];
index++;
}
}
counts[j] = 0;
}
}
}
8.堆排序
与树相关,在之后进行总结。
二.排序算法比较
1.时间复杂度比较
名词解释: n:数据规模 k:桶的个数 In-place:不占用额外内存 Out-place:占用额外内存 稳定:若a原本排在b前面,且a=b,则排序后a仍然排在b前面 不稳定,若a原本排在b前面,且a=b,但排序后a可能排在b后面。
2.时间比较
随机生成长度为80,000的数组,比较排序的时间。
随机生成长度为8,000,000的数组,比较排序的时间,其中冒泡排序、选择排序与插入排序耗时过长,不参与比较 可以看出,在数据量较大的情况下,快速排序和归并排序的运行时间较少,更适用,快速排序占用的空间更少,综合能力最优。
|