排序算法特点
排序算法 | 时间复杂度(平均) | 空间复杂度 | 稳定性 |
---|---|---|---|
冒泡排序 | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(1) | 不稳定 |
插入排序 | O(n^2) | O(1) | 稳定 |
希尔特排序 | O(n^2) < T < O(nlogn) | O(1) | 不稳定 |
快速排序 | O(nlogn) | O(nlogn) | 不稳定 |
归并排序 | O(nlogn) | O(n) | 稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
桶排序 | O(n) | ||
计数排序 | O(n) | ||
基数排序 | O(n) |
冒泡排序
排序原理:
将相邻的元素两两比较,当一个元素大于右侧相邻元素时,就交换他们的位置;当一个元素小于或等于右侧相邻元素时,位置保持不变。
时间复杂度: O(n^2),进行两次for循环。
空间复杂度: O(1)
代码实现:
public int[] sort(int[] arr) {
int size = arr.length;
// i代表最右侧已经有i个元素已经有序了。
for (int i = 0; i < size - 1; i++) {
// j代表每次从第1个元素开始比较,已知比较到 size-i-1 个元素位置,后面已经是有序的了,所以不需要比较。
for (int j = 0; j < size - i - 1; j++) {
// 这里都使用j来进行数据的两两交换。
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
选择排序
排序原理:
从数组的 [i, len-1] 区间中选出最小元素的下标k,并与第 i 个位置做置换;接下去从第i+1个元素开始,以此类推。
时间复杂度: O(n^2)。
空间复杂度: O(1)
代码实现:
public int[] sort(int[] arr) {
if (arr == null || arr.length <= 1) {
return arr;
}
int length = arr.length;
// 从左往右依次将第i小的数字插入到arr[i]的位置上。
for (int i = 0; i < length; i++) {
int k = i; //从第i个位置开始遍历,用k记录[i,length-1]区间内的最小数字的下标。
for (int j = i; j <length; j++) {
if (arr[j] < arr[k]) {
k = j; //找到比arr[k]还小的值后,将下标赋值给k。
}
}
// 将arr[i]与arr[k]的值进行互换。
int temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}
return arr;
}
插入排序
排序原理:
将arr[i+1]元素与arr[0]到arr[i]个对比,如果arr[0]<arr[i+1]<arr[i],则将arr[i+1]元素按顺序大小插入。
时间复杂度: O(n^2)。
空间复杂度: O(1)
代码实现:
public int[] sort(int[] arr) {
if (arr == null || arr.length <= 1) {
return arr;
}
int length = arr.length;
for (int i = 1; i < length; i++) {
int temp = arr[i]; //将要比对元素
int j = i;
while (j > 0 && temp < arr[j-1]) {
arr[j] = arr[j-1];
j--;
}
arr[j] = temp;
}
return arr;
}
希尔排序
排序原理:
时间复杂度: O(n^2)。
空间复杂度: O(1)
代码实现:
快速排序
排序原理:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
时间复杂度: O(nlogn)
空间复杂度: O(nlogn)
代码实现:
void quickSort(int[] A, int left, int right) {
if (left < right) {
int pos = partition(A, left, right);
quickSort(A, left, pos - 1);
quickSort(A, pos+1, right);
}
}
int partition(int[] A, int left, int right) {
int temp = A[left];
while (left < right) {
// 右侧元素>temp,则right指针左移
while (left < right && A[right] > temp) {
right--;
}
A[left] = A[right];
// 左侧元素<=temp,则left指针右移
while (left < right && A[left] <= temp) {
left++;
}
A[right] = A[left];
}
A[left] = temp;
return left;
}
归并排序
排序原理:
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
时间复杂度: O(nlogn)
空间复杂度: O(n)
代码实现:
void mergeSort(int[] A, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(A, left, mid);
mergeSort(A, mid + 1, right);
mergeTwoArr(A, left, mid, mid+1, right);
}
}
private void mergeTwoArr(int A[], int L1, int R1, int L2, int R2) {
// 需要借助另外的空间来临时存放已排序的数据。
int[] mergeArr = new int[A.length];
int arr1Index = L1;
int arr2Index = L2;
int newIndex = 0;
while (arr1Index <= R1 && arr2Index <= R2) {
if (A[arr1Index] <= A[arr2Index]) {
mergeArr[newIndex++] = A[arr1Index++];
} else {
mergeArr[newIndex++] = A[arr2Index++];
}
}
while (arr1Index <= R1) {
mergeArr[newIndex++] = A[arr1Index++];
}
while (arr2Index <= R2) {
mergeArr[newIndex++] = A[arr2Index++];
}
// 将L1到R2的已排序的数据回写到原数组A中。
for (int i = 0; i < newIndex; i++) {
A[L1 + i] = mergeArr[i];
}
// 打印数组
printArray(mergeArr, "新数组:");
}
堆排序
排序原理: 二叉堆
代码实现:
// 将数组转化为最小堆
private void buildHeap(int[] arr) {
int size = arr.length;
for (int i = (size/2)-1; i>=0 ; i--) {
siftDown(arr, i, size);
}
}
private void siftUp(int[] arr) {
int childIndex = arr.length-1;
while (childIndex > 0) {
int parentIndex = (childIndex - 1) >> 1;
if (arr[parentIndex] < arr[childIndex]) {
break;
}
swap(arr, parentIndex, childIndex);
childIndex = parentIndex;
}
}
private void siftDown(int[] arr, int parentIndex, int size) {
int childIndex = leftChildIndex(parentIndex);
while (childIndex < size) {
int rightChildIndex = childIndex + 1;
if (rightChildIndex < size && arr[childIndex] > arr[rightChildIndex]) {
childIndex = rightChildIndex;
}
if (arr[childIndex] > arr[parentIndex]) {
break;
}
swap(arr, childIndex, parentIndex);
parentIndex = childIndex;
childIndex = leftChildIndex(parentIndex);
}
}
private void swap(int[] arr, int l, int r) {
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
}
// 根据父节点找到左子节点的位置。
private int leftChildIndex(int parentIndex) {
return 2 * parentIndex + 1;
}
计数排序
排序原理:
代码实现:
桶排序
排序原理:
在我们可以确定需要排列的数组的范围时,可以在该数值范围内生成有限个桶去对应数组中的数,然后我们将扫描的数值放入匹配的桶里的行为,可以看作是分类,在分类完成后,我们需要依次按照桶的顺序输出桶内存放的数值,这样就完成了桶排序。
例如,学生考试分数排序(总分100分)。我们可以分配100个桶,然后遍历学生成绩,并将成绩添加到对应序号的桶中(即50分就添加到50号桶),遍历结束后我们按照桶的顺序进行输出,就会输出有序列表。
缺点:
如果数据分布稀疏,就会浪费很多空间。如有10个数,但是最大的数为1000,则我们需要申请1000个桶去存放数据,此时就会浪费990个桶的空间。
代码实现:
略
基数排序
排序原理:
代码实现: