文章目录
前言
排序算法在编程生涯中虽然用的不多,但是作为基本功,还是要掌握一下。冒泡排序(Bubble Sort)
冒泡排序(Bubble Sort)
是一种交换排序基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
在最好的情况下,也就是数列本身是排好序的,需要进行 n - 1 次比较;
在最坏的情况下,也就是数列本身是逆序的,需要进行 n(n-1)/2 次比较。因此冒泡排序总的时间复杂度是 O(n^2)。
public static void bubbleSort(List<Integer> list) {
//外面循环每次找出最小的数(数列本身是排好序的,需要进行 n - 1 次比较)
for (int i = 0; i < list.size(); i++) {
//内循环找出第i小的元素
for (int j = i + 1; j < list.size(); j++) {
if (list.get(j).compareTo(list.get(i)) < 0) {
int tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
}
}
}
选择排序(Selection Sort)
基本思想是每一趟在n - i + 1 (i = 1,2,***,n - 1)个记录中选取关键字最小(或最大)的记录作为有序序列的 第 i 个记录,
直到所有元素排序完成。选择排序是不稳定的排序算法。
选择排序的时间复杂度为 O(n^2)。
public static void selectionSort(List<Integer> list) {
for (int i = 0; i < list.size(); i++) {
int min = i;
for (int j = i + 1; j < list.size(); j++) {
if (Integer.compare(list.get(min), list.get(j)) == 1) {
min = j;
}
}
if (i != min) {
int tmp = list.get(min);
list.set(min, list.get(i));
list.set(i, tmp);
}
}
}
插入排序(Insertion Sort)
将一个记录插入到已经排好序的有序数列中,从而得到一个有序但记录数加一的有序数列。
插入排序的时间复杂度为 O(n^2),是稳定的排序方法,适用于数量较少的排序。
public static void insertionSort(List<Integer> list) {
for (int i = 1; i < list.size(); i++) {
for (int j = i; j > 0; j--) {
int tmp;
if (list.get(j).compareTo(list.get(j - 1)) < 0) {
tmp = list.get(j);
list.set(j, list.get(j - 1));
list.set(j - 1, tmp);
} else {
break;
}
}
}
}
鸡尾酒排序(Cocktail Sort)
鸡尾酒排序是冒泡排序的一种变形。
先找到最小的数字,放在第一位,再找到最大的数字放在最后一位。
然后再找到第二小的数字放到第二位,
再找到第二大的数字放到倒数第二位。以此类推,直到完成排序。
鸡尾酒排序的时间复杂度为 O(n^2)。
public static void cocktailSort(List<Integer> list) {
for (int i = 0; i < (list.size() >> 1); i++) {
//先找到最小的数字,放在第一位
for (int j = i + 1; j < list.size(); j++) {
if (list.get(i).compareTo(list.get(j)) > 0) {
int tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
}
//再找到最大的数字放在最后一位
for (int l = i + 1; l < list.size() - i; l++) {
int maxIndex = list.size() - i - 1;
if (list.get(l).compareTo(list.get(maxIndex)) > 0) {
int tmp = list.get(l);
list.set(l, list.get(maxIndex));
list.set(maxIndex, tmp);
}
}
}
}
希尔排序(Shell Sort)
希尔排序(Shell Sort)是插入排序的一种,是针对直接插入排序算法的改进。
基本思想是将相距某个增量 d 的记录组成一个子序列,通过插入排序使得这个子序列基本有序,然后 减少增量继续排序。
操作上先取一个小于 n 的整数 d1 作为第一个增量,把全部记录分成 d1 个组,所有距离为 dl 的倍数的记录放在同一个组中。
先在各组内进行直接插人排序,然后取第二个增量d2 < d1 重复上述的分组和排序,
直至所取的增量 dt = 1 (dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
希尔排序的时间复杂度可以达到 O(n^(3/2)),要好于前面几种算法。
public static void shellSort(List<Integer> list) {
int j;
//分层 list.size() >> 1 组,每组单独排序 第二次循环向量再次缩小2倍
for (int gap = list.size() >> 1; gap > 0; gap = gap >> 1) {
for (int i = gap; i < list.size(); i++) {
Integer tmp = list.get(i);
for (j = i; j >= gap && tmp.compareTo(list.get(j - gap)) < 0; j -= gap) {
list.set(j, list.get(j - gap));
}
list.set(j, tmp);
}
}
}
梳排序(Comb Sort)
梳排序和希尔排序很类似。
希尔排序是在直接插入排序的基础上做的优化,而梳排序是在冒泡排序的基础上做的优化,
也就是将相距某个增量 d 的记录组成一个子序列,通过冒泡排序使得这个子序列基本有序,然后减少 增量继续排序。
梳排序的时间复杂度是 O(nlogn)。
public static void combSort(List<Integer> list) {
int n = list.size();
int step = n;
int k;
//将数组长度/1.3得到本次的gap; 当step=1时,相当于最后进行了一次冒泡排序
while ((step /= 1.3) >= 1) {
for (int i = n - 1; i >= step; i--) {
k = i - step;
//如果前者大于后者,则进行交换
if (list.get(k) > list.get(i)) {
int tmp = list.get(k);
list.set(k, list.get(i));
list.set(i, tmp);
}
}
}
}
归并排序(Merge Sort)
归并排序(MERGE-SORT) 是一种分治算法,是建立在归并操作上的一种有效的排序算法。
常用的 2 路归并排序假设初始序列有 n 个记录,可以看成是 n 个长度为 1 的子序列,进行两两归并,可以得到 n / 2 个长度为 2 或 1 的子序列;再两两归并,直到得到一个长度为 n 的有序序列为止。
归并排序的时间复杂度是 O(nlogn),是一种效率高且稳定的算法。
private static void mergeSort(List<Integer> list, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) >> 1;
//左边归并排序,使得左子序列有序
mergeSort(list, left, mid, temp);
//右边归并排序,使得右子序列有序 右边第一个是左边的最后一个+1
mergeSort(list, mid + 1, right, temp);
//左序列指针
int i = left;
//右序列指针
int j = mid + 1;
//临时数组指针
int t = 0;
while (i <= mid && j <= right) {
if (list.get(i).compareTo(list.get(j)) <= -1) {
temp[t++] = list.get(i++);
} else {
temp[t++] = list.get(j++);
}
}
//将左边剩余元素填充进temp中
while (i <= mid) {
temp[t++] = list.get(i++);
}
//将右序列剩余元素填充进temp中
while (j <= right) {
temp[t++] = list.get(j++);
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while (left <= right) {
list.set(left++, temp[t++]);
}
}
}
快速排序(Quick Sort)
快速排序(Quicksort)是对冒泡排序的一种改进。
基本思想是通过一趟排序将待排记录分割成独立的两部分,
其中一部分的记录都比另一部分小,然后再分别对这两个部分进行快速排序,最终实现整个序列的排序。
快速排序的时间复杂度为 O(nlogn),是一种不稳定的排序算法
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
// 设定基准值(pivot)
int index = left + 1;
for (int i = index; i <= right; i++) {
if (arr[i] < arr[left]) {
swap(arr, i, index);
index++;
}
}
swap(arr, left, index - 1);
int partitionIndex = index - 1;
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
}
/**
* 交换元素
*
* @param arr
* @param a
* @param b
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
堆排序(Heap Sort)
堆排序(Heap sort)是指利用堆这种数据结构所设计的一种排序算法。
堆是具有下列性质的完全二叉树:
1. 每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;
2. 每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
基本思想是把待排序的序列构造成一个大顶堆,此时序列的最大值就是队顶元素,把该元素放在最后,然后对剩下的 n - 1 个元素继续构造大顶堆,直到排序完成。
堆排序的时间复杂度为 O(nlogn),由于要构造堆,因此不适用于序列个数较少的情况.
public static void heapSort(int[] arr) {
//1.构建大顶堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for (int j = arr.length - 1; j > 0; j--) {
//将堆顶元素与末尾元素进行交换
swap(arr, 0, j);
//重新对堆进行调整
adjustHeap(arr, 0, j);
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
*
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int[] arr, int i, int length) {
//先取出当前元素i
int temp = arr[i];
//从i结点的左子结点开始,也就是2i+1处开始
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
//如果左子结点小于右子结点,k指向右子结点
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;
}
//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
if (arr[k] > temp) {
arr[i] = arr[k];
i = k;
} else {
break;
}
}
//将temp值放到最终的位置
arr[i] = temp;
}
/**
* 交换元素
*
* @param arr
* @param a
* @param b
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
基数排序(Radix Sort)
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
public static int[] sort(int[] sourceArray) {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxDigit = getMaxDigit(arr);
return radixSort(arr, maxDigit);
}
/**
* 获取最高位数
*/
private static int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLenght(maxValue);
}
private static int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
protected static int getNumLenght(long num) {
if (num == 0) {
return 1;
}
int lenght = 0;
for (long temp = num; temp != 0; temp /= 10) {
lenght++;
}
return lenght;
}
private static int[] radixSort(int[] arr, int maxDigit) {
int mod = 10;
int dev = 1;
for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
int[][] counter = new int[mod * 2][0];
for (int j = 0; j < arr.length; j++) {
int bucket = ((arr[j] % mod) / dev) + mod;
counter[bucket] = arrayAppend(counter[bucket], arr[j]);
}
int pos = 0;
for (int[] bucket : counter) {
for (int value : bucket) {
arr[pos++] = value;
}
}
}
return arr;
}
/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private static int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
总结
完
感谢您的阅读
如果你发现了错误的地方,可以在留言区提出来,我对其加以修改