时间复杂度汇总:
稳定性:在排序过程中相同元素的相对位置保持不变
稳定的排序算法:
冒泡排序(Bubble Sort):冒泡排序是一种简单的比较排序算法,它比较相邻元素并交换它们,如果相邻元素相等时不进行交换,因此它是稳定的。
插入排序(Insertion Sort):插入排序也是一种简单的比较排序算法,它将一个元素逐个插入到已排序的部分中,当遇到相等的元素时,不改变它们的相对顺序,因此它是稳定的。
归并排序(Merge Sort):归并排序是一种分治排序算法,它将数组分成两个子数组,分别排序,然后将它们合并成一个有序数组。在合并过程中,相等元素的相对顺序保持不变,所以归并排序是稳定的。
不稳定的排序算法:
快速排序(Quick Sort):快速排序是一种分治排序算法,它不保证相等元素的相对顺序在排序后保持不变,因此是不稳定的。
选择排序(Selection Sort):选择排序是一种简单的比较排序算法,它每次选择最小的元素并将其放在已排序部分的末尾,相等元素可能会发生交换,所以是不稳定的。
希尔排序(Shell Sort):希尔排序是插入排序的改进版,它不保证相等元素的相对顺序在排序后保持不变,因此是不稳定的
堆排序也是不稳定的排序。
稳定排序
冒泡排序
冒泡排序(Bubble Sort)算法是所有排序算法中最简单、最基础的一个,它的实现思路是通过相邻数据的交换达到排序的目的。
冒泡排序的执行流程是:
- 对数组中相邻的数据,依次进行比较;
- 如果前面的数据大于后面的数据,则把前面的数据交换到后面。经过一轮比较之后,就能把数组中最大的数据排到数组的最后面了;再用同样的方法,把剩下的数据逐个进行比较排序,最后得到就是从小到大排序好的数据
- 冒泡排序比较一次就比较好了那么就是O(n) ,具体来说,当冒泡排序开始遍历数组时,如果在一轮遍历中没有发生任何元素的交换操作,那么说明数组已经按照升序或降序排列,这时排序过程可以提前终止。因此,当输入数组本身已经有序时,冒泡排序的时间复杂度是O(n)。(你可以在内层循环中添加一个标志(flag)来判断是否有元素发生了交换。如果没有发生交换,就可以提前终止排序。)
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
for (int j = 0; j < n - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
直接插入排序
将无序队列中的数依次插入到有序队列中去
时间复杂度:正序时O(n) 逆序时O(n[^2])
当待排元素已经为正序时,或者接近正序时所比较的次数和移动次数比较少。对于小规模来说,插入排序是一个快速的排序法。
空间复杂度:O(1) 只需要一个监视哨arr[0]
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args){
int[] a={12,15,9,20,6,31,24};
Sort(a);//调用方法
}
public static void insertionSort(int[] a) {
int n = a.length;
for (int i = 1; i < n; i++) {
int key = a[i];
int j = i-1;
while(j>=0&&a[j]>key)
{
a[j+1] = a[j];
j--;
}
a[j+1] = key;
}
}
}
归并排序
归并排序是一种分治排序算法,它将数组分成两个子数组,分别排序,然后将它们合并成一个有序数组。在合并过程中,相等元素的相对顺序保持不变,所以归并排序是稳定的。
归并排序的最好最坏的时间复杂度都是0(nlogn), 空间复杂度是O(n)
public class MergeSort {
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
mergeSort(arr);
for (int num : arr) {
System.out.print(num + " ");
}
}
public static void mergeSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return; // 数组为空或只有一个元素时无需排序
}
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
// 递归分割左右子数组
mergeSort(arr,left,mid);
mergeSort(arr,mid+1,right);
merge(arr,left,mid,right);
}
}
private static void merge(int[] arr, int left, int mid, int right) {
int n1 = mid-left+1;
int n2 = right-mid;
int[] leftArr = new int[n1];
int[] rightArr = new int[n2];
for(int i=0;i<n1;i++)
{
int leftArr[i] = arr[left+i];
}
for(int j=0;j<n2;j++)
{
int rightArr[j] = arr[mid+j+1];
}
int i=0,j=0,k=left;
while(i<n1&&j<n2)
{
if(leftArr[i]<rightArr[j])
{
arr[i] = leftArr[i];
i++;
}
else
{
arr[j] = rightArr[j];
j++;
}
k++;
}
while(i<n1)
{
arr[k] = leftArr[i];
i++;
k++;
}
while(j<n2)
{
arr[k] = rightArr[j];
j++;
k++;
}
}
}
不稳定的排序
选择排序
它的基本思想是在未排序的部分中选择最小(或最大)的元素,然后将其放在已排序部分的末尾。它的最好最坏时间复杂度是O(n^2)
public class SelectionSort {
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
selectionSort(arr);
for (int num : arr) {
System.out.print(num + " ");
}
}
public static void selectionSort(int[] arr) {
int n = arr.length;
for(int i=0;i<n-1;i++)
{
int minIndex = i;
for(int j=i+1;j<n;j++)
{
if(arr[minIndex]>arr[j])
{
minIndex = j;
}
}
// 交换最小元素和当前位置的元素
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
快速排序
- 首先设定一个分界值,通过该分界值把数组分为左右两个部分;
- 将大于等于分界值的元素放到分界值的右边,将小于分界值的元素放到分界值的左边;
- 然后对左右两边的数据进行独立的排序,在左边数据中取一个分界值,把小于分界值的元素放到分界值的左边,大于等于分界值的元素,放到数组的右边;右边的数据也执行同样的操作;
- 重复上述操作,当左右各数据排序完成后,整个数组也就完成了排序。
快速排序的平均时间复杂度为O(n log n),其中n是数组的长度。这是快速排序在大多数情况下的性能表现,因此它通常被认为是一种高效的排序算法。
快速排序的最好情况时间复杂度是O(n log n),当输入数据分布均匀随机时,快速排序的表现最佳,每次划分都能将数组均匀地分成两半。
然而,快速排序的最坏情况时间复杂度是O(n^2),当输入数据已经完全有序或者接近有序时,每次划分都可能将数组分成一个较大的部分和一个较小的部分,导致递归深度增加,性能下降
quickRow(quickNums, 0, quickNums.length - 1);
public static void quickRow(int[] array, int low, int high){
int i,j,pivot;
//结束条件
if (low >= high) {
return;
}
i = low;
j = high;
//选择的节点,这里选择的数组的第一数作为节点
pivot = array[low];
while (i < j){
//从右往左找比节点小的数,循环结束要么找到了,要么i=j
while (array[j] >= pivot && i < j){
j--;
}
//从左往右找比节点大的数,循环结束要么找到了,要么i=j
while (array[i] <= pivot && i < j){
i++;
}
//如果i!=j说明都找到了,就交换这两个数
if (i < j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//i==j一轮循环结束,交换节点的数和相遇点的数
array[low] = array[i];
array[i] = pivot;
//数组“分两半”,再重复上面的操作
quickRow(array,low,i - 1);
quickRow(array,i + 1,high);
}
堆排序
堆排序(Heap Sort)算法是利用堆结构和二叉树的一些特性来完成排序的。 其实每次堆排序就是将无序的堆排成大根堆或者小根堆。
堆排序的时间复杂度在最好情况和最坏情况下都是O(n log n),其中n是数组的长度。