相信很多人都知道十大排序算法是哪些,但真正能手写出来的,可能只能完整的写出六七个。
怎么才能将排序算法都牢牢记住,在面试时丝毫不慌?
千万不要死记硬背,应该理解每个算法的特点,根据特点推演算法,才能稳如老狗。
目录
分类
十大排序算法并非是毫无关联的十种算法,如果从逻辑层面上划分,可以归纳为以下五类:
- 交换排序:通过两两交换元素实现排序
- 选择排序:通过组内筛选元素实现排序
- 插入排序:通过顺序插入元素实现排序
- 归并排序:通过拆分合并元素实现排序
- 桶类排序:通过分组匹配元素实现排序
交换排序
交换排序有:冒泡排序、快速排序
快速排序是冒泡排序的改进,虽然没有冒泡排序稳定,但在一般情况下效率更高
冒泡排序 - O(n2)
特点:相邻元素比较并交换
public void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
快速排序 - O(nlogn)
特点:分治递归,线性划分(左右指针一轮遍历,将比目标数小的放左边,大的放右边)
public void quickSort(int[] arr, int row, int high) {
if (row < high) {
int target = partition(arr, row, high);
quickSort(arr, row, target - 1);
quickSort(arr, target + 1, high);
}
}
public int partition(int[] arr, int low, int high) {
int temp = arr[low];
while (low < high) {
while (low < high && arr[high] >= temp) high--;
arr[low] = arr[high];
while (low < high && arr[low] <= temp) low++;
arr[high] = arr[low];
}
arr[low] = temp;
return low;
}
缺点:最坏情况下回归O(n2),解决办法是使用“线性时间选择算法”得到中位数作为patation的目标数,但实现复杂度较大,不容易记忆。
选择排序
选择排序有:选择排序、堆排序
堆排序和选择排序都不稳定,但堆排序的效率更高
选择排序 - O(n2)
特点:每次遍历求最大or最小
public void selectSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
if (i != minIndex) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
堆排序 - O(nlogn)
特点:将数组视为完全二叉树,父子大小关系决定升序or降序;第一轮生成堆,第二轮根与叶子做交换并调整
public void heapSort(int[] arr) {
int len = arr.length;
for (int i = len / 2 - 1; i >= 0; i--) {
adjustment(arr, i, len);
}
for (int i = len - 1; i >= 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
adjustment(arr, 0, i);
}
}
public void adjustment(int[] arr, int pos, int len) {
int child = pos * 2 + 1;
if (child + 1 < len && arr[child] < arr[child + 1]) {
child++;
}
if (child < len && arr[pos] < arr[child]) {
int temp = arr[pos];
arr[pos] = arr[child];
arr[child] = temp;
adjustment(arr, child, len);
}
}
插入排序
插入排序有:插入排序、希尔排序
希尔排序是插入排序的改进,虽然没有插入排序稳定,但在一般情况下效率更高
插入排序 - O(n2)
特点:原地维护一个有序子数组,将新元素插入符合顺序的位置
public void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}
}
二分版本
public void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int target = binarySearch(arr, i);
int temp = arr[i];
for (int j = i; j > target; j--) {
arr[j] = arr[j - 1];
}
arr[target] = temp;
}
}
public int binarySearch(int[] arr, int end) {
int left = 0;
int right = end - 1;
while (left <= right) {
int mid = (right + left) >> 1;
if (arr[mid] < arr[end]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
希尔排序 - O(nlogn)
特点:按间隔分组,每组内进行插入排序(个人认为最难以意会的排序思想)
public void shellSort(int[] arr) {
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
int temp = arr[i];
int j = i - gap;
while (j >= 0 && arr[j] > temp) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = temp;
}
}
}
归并排序
归并排序有:归并排序
归并排序不仅稳定,而且时间复杂度在任何情况下都一样是O(nlogn)
归并排序 - O(nlogn)
特点:向下分治递归,向上合并
public void mergeSort(int[] arr, int low, int high) {
if (low < high) {
int mid = (low + high) >> 1;
mergeSort(arr, low, mid);
mergeSort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
}
public void merge(int[] arr, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= high) {
if (arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= high) {
temp[k++] = arr[j++];
}
for (k = 0; k < temp.length; k++) {
arr[low + k] = temp[k];
}
}
桶类排序
桶类排序有:桶排序、基数排序、计数排序
桶类排序的优点是近乎于O(n)的效率且都很稳定,但都需要额外空间。其中计数排序最简单,是特殊的桶排序
桶排序 - O(n+m)
特点:按固定长度区间划分为桶,按桶匹配,桶内再排序
public void bucketSort(int[] arr) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int num : arr) {
max = Math.max(max, num);
min = Math.min(min, num);
}
int gap = (max - min) / 10 + 1;
List[] bucket = new ArrayList[gap];
for (int i = 0; i < gap; i++) {
bucket[i] = new ArrayList<Integer>();
}
for (int num : arr) {
bucket[(num - min) / gap].add(num);
}
int index = 0;
for (int i = 0; i < gap; i++) {
bucket[i].sort(null);
for (int j = 0; j < bucket[i].size(); j++) {
arr[index++] = (int)bucket[i].get(j);
}
}
}
缺点:不适用于数据范围大且数据值集中的数据
基数排序 - O(n*m)
特点:按0~9固定10个桶,按个位、十位、百位…确定需要m轮桶匹配
public void radixSort(int[] arr) {
int max = Integer.MIN_VALUE;
for (int num : arr) {
max = Math.max(num, max);
}
int radix = 0;
while (max != 0) {
max /= 10;
radix++;
}
int[][] bucket = new int[10][arr.length];
int[] count = new int[10];
for (int i = 0, pos = 1; i < radix; i++, pos *= 10) {
for (int num : arr) {
int mod = (num / pos) % 10;
bucket[mod][count[mod]] = num;
count[mod]++;
}
int index = 0;
for (int k = 0; k < 10; k++) {
if (count[k] > 0) {
for (int l = 0; l < count[k]; l++) {
arr[index++] = bucket[k][l];
}
count[k] = 0;
}
}
}
}
缺点:不适用于位数差距很小的数据
计数排序 - O(n+m)
特点:数据范围多大就有多少个桶
public void countSort(int[] arr) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int num : arr) {
max = Math.max(max, num);
min = Math.min(min, num);
}
int[] count = new int[max - min + 1];
for (int num : arr) {
count[num - min]++;
}
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index++] = i + min;
count[i]--;
}
}
}
缺点:不适用于数据范围大、稀疏的数据