排序算法比较
算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
插入排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
希尔排序 | O(nlogn) | O(n^2) | O(n) | O(1) | 不稳定 |
快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(logn) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
桶(基数)排序 | O(n * r) | O(n * r) | O(n * r) | O(n * r) | 稳定 |
冒泡排序
每遍历一次,将当前遍历的所有元素的最大值置于最后。即第一次遍历将数组中所有元素的最大值置于倒数第一位,第二次遍历(除开最后一位)剩余所有的数,将其中的最大值置于倒数第二位。以此类推。。。
public static void bubble(int[] arr) {
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
boolean flag = false;// 作为标记,如果一次遍历中没有任何交换,则数组已经有序,不需要再进行遍历
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (!flag) {
break;
}
}
}
选择排序
每遍历一次,将当前遍历的所有元素的最小值和首位进行交换。即第一次遍历将数组中所有元素的最小值和数组第一位进行交换,第二次遍历(除开第一位)剩余所有的数,将其中的最小值和第二位进行交换。以此类推。。。
public static void select(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
//遍历找到最小值
for (int j = i + 1; j < arr.length - 1; j++) {
if (min > arr[j]) {
min = arr[j];
minIndex = j;
}
}
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
插入排序
将数组分为两个部分,前半部分有序,依次遍历后半部分,在有序部分中找到合适的位置插入
public static void insert(int[] arr) {
int insertIndex = 0;
int insertVal = 0;
for (int i = 1; i < arr.length; i++) {
insertVal = arr[i];
insertIndex = i;
// for (insertIndex = i - 1; insertIndex >= 0; insertIndex--) {
// if (arr[insertIndex] > insertVal) {
// arr[insertIndex + 1] = arr[insertIndex];//将比插入值大的数向后移
// } else {
// break;
// }
// }
//
// insertIndex++;
while (insertIndex > 0 && arr[insertIndex - 1] > insertVal) {
arr[insertIndex] = arr[insertIndex - 1];
insertIndex--;
}
if (insertIndex != i) {
arr[insertIndex] = insertVal;
}
}
}
希尔排序
希尔排序是插入排序的一种优化,在简单插入排序中,如果出现很小的数在数组后端,需要后移的操作数量很多。为了减少这种移动,所以有了希尔算法。
希尔排序的核心思想是:
将数组按照一定的增量分组,对每组进行简单插入排序,逐渐减少增量至1,即对整个数组进行一次简单插入,得到最后的有序数组。
public static void shell(int[] arr) {
for (int step = arr.length / 2; step > 0; step = step / 2) {
for (int i = step; i < arr.length; i++) {
int insertVal = arr[i];
int insertIndex = i;
while (insertIndex > step - 1 && arr[insertIndex - step] > insertVal) {
arr[insertIndex] = arr[insertIndex - step];
insertIndex -= step;
}
if (insertIndex != i) {
arr[insertIndex] = insertVal;
}
}
}
}
快速排序
快速排序是找到一个基准数,将比基准数小的数全部放到左边,将比基准数大的数全部放到右边。然后依次递归排序左右子数组。
public static void quick(int[] arr, int left, int right) {
if (left >= right) {
return;
}
//取中间的值为基准值
int pivotVal = arr[(left + right) / 2];
//采用双指针遍历数组
int i = left;
int j = right;
int temp = 0;
while (j > i) {//当左右指针相遇, 即遍历完整个数组, 退出
while (arr[i] < pivotVal) {//当前数小于基准值且在左边, 不需要移位
i++;
}
while (arr[j] > pivotVal) {//当前值大于基准值且在右边,不需要移位
j--;
}
if (i >= j) {//当左右指针相遇, 即遍历完整个数组, 退出
break;
}
if (i != j) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
if (arr[i] == pivotVal) {//防止死循环
j--;
}
if (arr[j] == pivotVal) {//防止死循环
i++;
}
}
if (i == j) {//防止死循环
i++;
j--;
}
//遍历左边数组
quick(arr, left, j);
//遍历右边数组
quick(arr, i, right);
}
归并排序
归并排序是将数组划分为两个部分,一直分解到每个数组只有一个数字。接着开始合并,每次将两个子数组合并为一个有序的数组.
比如将【2,1,3,4】会被分解为【2】、【1】、【3】、【4】,接着将【2】、【1】合并为【1,2】,【3】、【4】合并为【3,4】,然后将【1,2】,【3,4】合并为【1,2,3,4】。
public static void merge(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
merge(arr, left, mid);
merge(arr, mid + 1, right);
//开始合并
//1. 遍历左右数组,将数字一从小到大的顺序填充到临时数组中, 直到某一边数组填充完毕
int i = left;//左边数组的初始位置
int j = mid + 1;//右边数组的初始位置
int[] temp = new int[right - left + 1];
int tempIndex = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[tempIndex] = arr[i];
i++;
tempIndex++;
} else {
temp[tempIndex] = arr[j];
j++;
tempIndex++;
}
}
//2.将另一边剩余的数字依次填充到临时数组中
while (i <= mid) {
temp[tempIndex] = arr[i];
i++;
tempIndex++;
}
while (j <= right) {
temp[tempIndex] = arr[j];
j++;
tempIndex++;
}
//3.将临时数组copy到原来的数组中
tempIndex = 0;
i = left;
while (tempIndex < temp.length) {
arr[i] = temp[tempIndex];
i++;
tempIndex++;
}
}
}
桶排序
桶排序(基数排序),将所有数字变成同样的数位长度(即数位较短的数前面补零)例如【1,100】变为【001,100】
从低位开始,依次进行一次排列,当最高位排完就得到了有序序列
说明:桶排序非常占用内存,如果排列的数组过大,容易造成内存溢出,另外不支持负数的排序
public static void bucket(int[] arr) {
//定义十个桶,占用内存。。。。
int[][] bucket = new int[10][arr.length];
//记录每个桶的下标,即存储的数字个数
int[] bucketElementsCount = new int[10];
//找到数组的最大值
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
int maxLength = (max + "").length();
//第一次遍历数字的个位数,第二次是十位数,以此类推。。。
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//将数字放入对应的同种
for (int j = 0; j < arr.length; j++) {
int digit = arr[j] / n % 10;
bucket[digit][bucketElementsCount[digit]] = arr[j];
bucketElementsCount[digit]++;
}
//遍历桶,将桶中的元素依次放回原数组
int index = 0;
for (int j = 0; j < bucketElementsCount.length; j++) {
for (int k = 0; k < bucketElementsCount[j]; k++) {
arr[index] = bucket[j][k];
index++;
}
bucketElementsCount[j] = 0;
}
}
}
堆排序
将数组设计成大顶堆或者小顶堆,将堆顶位置的元素与数组末端的元素交换得到有序数组
大顶堆原理:大顶堆要求父节点的值大于等于左子树的值,又大于等于右子树的值。
小顶堆原理:小顶堆要求父节点的值小于等于左子树的值,又小于等于右子树的值。
public static void heap(int[] arr) {
//找到当前的第一个非叶子节点
int i = (arr.length - 1) / 2;
//从该节点从下往上,右至左创建调整堆
for (; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
int temp = 0;
for (int j = arr.length - 1; j > 0; j--) {
//交换堆顶元素和末尾元素
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
//重新构建大顶堆
adjustHeap(arr, 0, j);
}
}
private static void adjustHeap(int[] arr, int i, int length) {
int temp = arr[i]; //保留当前节点
int maxIndex = 0;
int lchildIndex = 2 * i + 1;//当前节点的左子节点的下标
int rchildIndex = 2 * i + 2;//当前节点的右子节点的下标
while (lchildIndex < length) {
maxIndex = lchildIndex;
if (rchildIndex < length && arr[lchildIndex] < arr[rchildIndex]) {
//左子节点小于右子节点
maxIndex = rchildIndex;
}
if (temp >= arr[maxIndex]) {
//当前节点比子节点都大, 退出
break;
}
arr[i] = arr[maxIndex];
//继续调整当前节点的左子节点
i = maxIndex;
lchildIndex = 2 * i + 1;
rchildIndex = 2 * i + 2;
}
arr[i] = temp;
}