十大排序算法
- 冒泡排序
- 简单比较排序(最大、小值排序)
- 直接插入排序
- 希尔排序(缩小增量排序)
- 快速排序(快排)
- 堆排序
- 归并排序
- 计数排序
- 桶排序
- 基数排序
- jdk8中关于排序的优化
冒泡排序
思路
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
实现
public static void bubbleSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
boolean isSort = true;
for (int j = 0; j < array.length - i - 1; j++) {
if (array[j] > array[j + 1]) {
exchange(array, j, j + 1); // 交换函数
isSort = false;
}
}
if(isSort){
break; //优化实现
}
}
}
优化
一趟比较中没有交换元素说明该数组已经有序
简单比较排序(最大、小值排序)
思路
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
- 以此类推,直到所有元素均排序完毕
实现
public static void selectionSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
int index = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[index]) {
index = j;
}
}
if (index != i) {
exchange(array, index, i);
}
}
}
直接插入排序
思路
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
实现
public static void insertSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
int index = i;
int temp = array[i + 1];
while (index >= 0 && array[index] > temp) {
array[index + 1] = array[index];
index--;
}
if (index != i) {
array[index + 1] = temp;
}
}
}
优化
可见希尔排序、快速排序
希尔排序(缩小增量排序)
思路
插入排序的一种更高效的改进版本, 插入排序中最坏的情况就是待排序元素需要插入到最远距离位置
希尔排序就是基于这个点优化 增量设置取二分之一到三分之一中间
- 设置增量
- 按区间分组并排序(直接插入排序)
- 按比例缩小增量
- 重复2步骤
- 直到增量为1重复2步骤(排序完成)
实现
public static void shellSort(int[] array) {
int multiple = 2;
for (int gap = array.length / multiple; gap > 0; gap /= multiple) {
for (int i = gap; i < array.length; i++) {
int index = i - gap;
int temp = array[i];
while (index >= 0 && array[index] > temp) {
array[index + gap] = array[index];
index -= gap;
}
if (index != i - gap) {
array[index + gap] = temp;
}
}
}
}
快速排序(快排)
思路
- 在区间中随机挑选一个元素作基准
- 将小于基准的元素放在基准之前,大于基准的元素放在基准之后
- 再分别对小数区与大数区进行排序
实现
public static void quickSort(int[] array) {
quickSort(array, 0, array.length - 1);
}
private static void quickSort(int[] array, int left, int right) {
int pivot = array[(left + right) / 2];
int l = left, r = right;
while (l < r) {
while (array[l] < pivot) {
l++;
}
while (array[r] > pivot) {
r--;
}
if (l >= r) {
break;
}
if (array[l] != array[r]) {
exchange(array, l, r);
}
if (array[l] == pivot) {
r--;
}
if (array[r] == pivot) {
l++;
}
}
if (l == r) {
l += 1;
r -= 1;
}
if (left < r) {
quickSort(array, left, r);
}
if (right > l) {
quickSort(array, l, right);
}
}
优化
- 当数量较少时使用直接插入排序
- 对于数组基本有序时,固定基准的快速排序方法性能急速下降(升序数组选取开头基准,每次右边全部比基准大,分不出两个区间),使用三数取中(开头,中间,结尾取中数为基准),这样可以分出区间,降低时间复杂度
- 对于数组重复数据较多情况,使用聚集元素思想,在一次分割结束后,将与本次基准相等的元素聚集在一起,再分割时,不再对聚集过的元素进行分割
- 使用尾递归方式:当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,大多数现代的编译器会利用这种特点自动生成优化的代码。
- 多线程
- 优化快速排序设计:使用双轴快速排序方式,选取两个基准(左边比右边大)放在数组最前方,这样数组划分为四个区域:小于等于第一基准区域,大于第一基准且小于等于第二基准区域,大于第二基准区域,待排序区域,当待排序区域没有元素则一趟排序完成
堆排序
思路
堆是一种优先队列,两种实现,大顶堆和小顶堆,大顶堆:完全二叉树中每个父节点都大于子节点
- 将数组构建为大顶堆
- 构建时:从最后一个非叶子节点调整,保证每个父节点大于子节点
- 交换之后修复时,从第一个父开始,保证每个父节点大于子节点
- 将堆顶元素([0])与待排序区域最后一个元素交换
- 重复一二步骤,直至只剩最后一个元素(完成排序)
实现
public static int[] heapSort(int[] array) {
for (int i = array.length / 2 - 1; i >= 0; i--) {
heapAdjust(array, i, array.length);
}
for (int j = array.length - 1; j > 0; j--) {
exchange(array, j, 0);
heapAdjust(array, 0, j);
}
return array;
}
/**
* 将数组调整为大顶堆
*
* @param array 待调整数组
* @param index 非叶子节点在数组中索引
* @param length 表示对多少个元素继续调整
*/
public static void heapAdjust(int[] array, int index, int length) {
int temp = array[index];
for (int k = index * 2 + 1; k < length; k = k * 2 + 1) {
if (k + 1 < length && array[k] < array[k + 1]) {
k++;
}
if (array[k] > temp) {
array[index] = array[k];
index = k;
} else {
break;
}
}
array[index] = temp;
}
归并排序
思路
分治思想+空间换取时间思想
- 将数组分成两个数组
- 对两个数组递归调用归并排序
- 对两个有序数组使用临时空间进行重组有序
实现
public static void mergeSort(int[] array) {
int[] temp = new int[array.length];
mergeSort(array, 0, array.length - 1, temp);
}
public static void mergeSort(int[] array, int left, int right, int[] temp) {
if (right > left) {
// 分组中元素大于两个继续拆分
int mid = (left + right) / 2;
mergeSort(array, left, mid, temp);
mergeSort(array, mid + 1, right, temp);
merge(array, left, right, mid, temp);
}
}
/**
* 有序数组合并
*/
public static int[] merge(int[] array, int left, int right, int mid, int[] temp) {
int index = 0;
int i = left;
int j = mid + 1;
while (i <= mid && j <= right) {
if (array[i] > array[j]) {
// 左边比右边大 右边进
temp[index] = array[j];
j++;
} else {
// 左边比右边小 左边进
temp[index] = array[i];
i++;
}
index++;
}
// 左边到头 右边全部填充
while (j <= right) {
temp[index] = array[j];
j++;
index++;
}
// 右边到头 左边全部填充
while (i <= mid) {
temp[index] = array[i];
i++;
index++;
}
index = 0;
int tempLeft = left;
while (tempLeft <= right) {
array[tempLeft] = temp[index];
index++;
tempLeft++;
}
return temp;
}
计数排序
思路
空间换时间
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项
- 对所有的计数累加(从 C中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素 i 放在新数组的第 C[i] 项,每放一个元素就将 C[i] 减去1
实现
public static int[] countSort(int[] array) {
// 获取最大值和最小值
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int i : array) {
if (i > max) {
max = i;
}
if (i < min) {
min = i;
}
}
if (max == min) {
return array;
}
int[] countArray = new int[max - min + 1];
for (int i : array) {
countArray[i - min]++;
}
int index = 0;
for (int i = 0; i < countArray.length; i++) {
if (countArray[i] != 0) {
for (int j = 0; j < countArray[i]; j++) {
array[index++] = min + i;
}
}
}
return array;
}
桶排序
思路
- 设置一个定量的数组当作空桶子。
- 寻访序列,并且把项目一个一个放到对应的桶子去。
- 对每个不是空的桶子进行排序。
- 从不是空的桶子里把项目再放回原来的序列中。
实现
public static void bucketSort(int[] array, int bucketSize) {
// 获取最大值和最小值
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int i : array) {
if (i > max) {
max = i;
}
if (i < min) {
min = i;
}
}
if (max == min) {
return array;
}
// 分桶
int bucketCount = (max - min) / bucketSize + 1;
int[][] bucketArr = new int[bucketCount][array.length];
int[] bucketNum = new int[bucketCount];
for (Integer i : array) {
int index = (i - min) / bucketSize;
bucketArr[index][bucketNum[index]++] = i;
}
// 将不需要的元素清除
for (int i = 0; i < bucketCount; i++) {
int[] copy = new int[bucketNum[i]];
System.arraycopy(bucketArr[i], 0, copy, 0, copy.length);
bucketArr[i] = copy;
}
// 给桶排序并合并桶
int index = 0;
for (int i = 0; i < bucketCount; i++) {
if (bucketSize == 1 && bucketNum[i] != 0) {
for (int j = 0; j < bucketNum[i]; j++) {
array[index++] = bucketArr[i][j];
}
} else if (bucketSize != 1) {
if (bucketCount == 1) {
bucketSize--;
}
// 给这个桶排序, 再添加
int[] bucket = bucketSort(bucketArr[i], bucketSize);
for (int element : bucket) {
array[index++] = element;
}
}
}
}
基数排序
思路
- 按个位为关键字计数排序
- 按十位为关键字计数排序
- 以此类推
- 至到最大数的最高位排序结束
实现
public static void radixSort(int[] array) {
// 获取最大值
int max = Integer.MIN_VALUE;
for (int i : array) {
if (i > max) {
max = i;
}
}
// 创建排序桶
int[][] bucketArr = new int[10][array.length];
int[] bucketNum = new int[10];
int mod = 10;
int div = 1;
int remainder = max;
while (remainder > 0) {
for (int i = 0; i < array.length; i++) {
int index = array[i] / div % mod;
bucketArr[index][bucketNum[index]++] = array[i];
}
// 再将排序桶中的数据复制回原数组
int count = 0;
for (int i = 0; i < bucketNum.length; i++) {
if (bucketNum[i] != 0) {
for (int j = 0; j < bucketNum[i]; j++) {
array[count++] = bucketArr[i][j];
}
bucketNum[i] = 0;
}
}
div *= 10;
remainder = max / div;
}
}
优化
10个桶完成基数排序无法兼容负数
修改方案:1.将负数和正数(加上0)分成两个数组,分别排序,负数组倒序输出,正数(加上0)正序输出
2.设计19个桶,-9到9,直接排序
jdk8中关于排序的优化
- byte数组 插入|29|计数
- short或char数组 插入|47|双轴快速排序|3200|计数
- 其他数组 插入| 47| 双轴快速排序| 286| 归并