内部排序算法的性能分析_为什么要排序?排序算法的性能提升之道

全文共4076字,预计学习时长11分钟

0d764e8273ca90d9dbc4b9f57e17e7ae.png

图源:unsplash

排序有什么用?想象一下,如果字典不是按照字母顺序排列,查找一个单词,你得查到什么时候?这就是为什么人们引入了分类的概念,因为其极大地帮助我们快速搜索物品。

那么,如何实现排序呢?这些排序算法,你应该了解。

bce0cfae36eb51254e4c1c67825c1fdd.png

冒泡排序

这是最简单的排序算法。只需比较每对相邻的元素,并检查元素是否有序,否则交换两个元素,直到所有元素都被排序为止。

for(int i =0;i < n; i++){           for(int j=0;j < n -1; j++){               if(arr[j] > arr[j+1]){                   int temp = arr[j];                   arr[j] = arr[j+1];                   arr[j+1] = temp;              }           }       }
42cf0dc0fcc91c017409838e0a2acac7.png

图源:谷歌

性能分析:

· 时间复杂度:

1.最坏情况:O(n²)——由于循环n次元素n次,n为数组的长度,因此冒泡排序排序的时间复杂度变为O(n²)。

2.最佳情况:O(n²)——即使数组已经排序了,算法也会检查每个相邻对,因此最佳情况下的时间复杂度将与最坏情况相同。

· 空间复杂度:O(1)。

由于只输入了数组并未使用任何额外的数据结构,因此空间复杂度将为O(1)。

改进版冒泡排序:

如果看一下代码,就会发现在上述排序算法中即使数组已经排序,时间复杂度也将相同,即O(n²)。

为了克服这个问题,提出了一种改进算法。创建一个标志来确定数组是否已排序,该标志会检查所有相邻对之间是否发生了交换。如果遍历整个数组时没有交换,则该数组已完全排序,因此可以跳出循环。这大大降低了算法的时间复杂度。

for(int i =0;i < n; i++){           boolean isSwapped =false;           for(int j=0;j < n -1; j++){              if(arr[j] > arr[j+1]){                   int temp = arr[j];                   arr[j] = arr[j+1];                   arr[j+1] = temp;                   isSwapped =true;               }           if(!isSwapped){               break;             }          }       }

性能分析:

· 时间复杂度:

1.最坏情况:O(n²)——与上述算法相同。

2.最佳情况:O(n)——由于在此算法中,如果数组已经排序,就会中断循环,因此最佳情况下的时间复杂度将变为O(n)。

· 空间复杂度:O(1)。

c67cecb8bf4e05d004774ee8b934d4a8.png

选择排序

假设排序算法中第一个元素是最小元素,然后检查数组的其余部分中是否存在小于假定最小值的元素。若存在,就交换假定的最小值和实际的最小值,否则转移到下一个元素。

for(int i=0;i

性能分析:

· 时间复杂度:

1.最坏情况:O(n²)——由于对于数组中的每个元素,遍历其余数组以找到最小值,因此时间复杂度将变为O(n²)。

2.最佳情况:O(n²)——即使已对数组进行排序,我们的算法也会在其余数组中寻找最小值,因此最佳情况下的时间复杂度与最坏情况相同。

· 空间复杂度:O(1)。

就像之前的算法一样,除了输入数组之外没有利用任何额外的数据结构,因此空间复杂度将为O(1)。

c17544c6e8ae1064bd76cdd0bbeb01d3.png

插入排序:

在这种排序算法中,对于每个元素,都要检查其顺序是否正确,直到当前元素为止。由于第一个元素是有序的,所以我们从第二个元素开始检查顺序是否正确否则交换元素。因此,在任何给定元素上,检查当前元素是否大于上一个元素。如果不是,继续交换元素,直到当前元素大于上一个元素为止。

for(int i =1;i < n; i++) {           int j = i;           while(j >0&& arr[j] < arr[j-1]) {               int temp = arr[j];               arr[j] = arr[j-1];               arr[j-1] = temp;               j--;           }       }

性能分析:

· 时间复杂度:

1.最坏情况:O(n²)——在最坏情况下,数组按降序排序。因此,必须遍历每个元素并向左交换。

2.最佳情况:O(n)——在最佳情况下,数组已经排序。因此,对于每个元素,仅将当前元素与左侧的元素进行比较。由于顺序正确,不会交换并继续进行下一个元素。因此,时间复杂度将为O(n)。

· 空间复杂度:O(1)。

由于除了输入数组之外,没有使用任何额外的数据结构,因此空间复杂度将为O(1)。

c4953cc3eb326296062d36cd093ec442.png

快速排序

快速排序也被称为分区排序。该排序算法因其分而治之的概念相较于之前的算法效率更高

首先确定一个主元,然后找到该主元位置的正确索引,将该数组分为两个子数组。一个子数组包含小于主元的元素,另一个子数组包含大于主元的元素。然后,递归调用这两个子数组,直到无法进一步划分数组为止。

publicstaticvoid quicksort(int[] arr, int low, int high) {                    if(low >= high) return;                    int pivotPosition = partition(arr, low, high);                    quicksort(arr,low, pivotPosition-1);                    quicksort(arr, pivotPosition+1, high);                }

但是如何划分子数组呢?

假设数组的最后一个元素是主元,则用两个指针遍历整个数组。左指针指向的元素应小于主元,右指针指向的元素应大于主元。如果不是,则在左右指针处交换元素以对应数组中的特定位置,左边的元素较小,而右边的元素较大。然后,将主元插入此位置。

publicstaticint partition(int[] arr, int low, int high) {                    int pivot = arr[high];                    int left = low, right = high-1;                    while(left < right) {                       while(arr[left]pivot) {                            right--;                       }                       if(left >= right) {                            break;                       }                       int temp = arr[left];                       arr[left] = arr[right];                       arr[right] = temp;                    }                    int temp = arr[left];                    arr[left] = arr[high];                    arr[high] = temp;                    return left;                }

性能分析:

· 时间复杂度:

1.最佳情况:O(nlogn)——首先将数组递归分为两个子数组,时间复杂度为O(logn)。每次函数调用都将调用时间复杂度为O(n)的分区函数,因此,总时间复杂度为O(nlogn)。

2.最坏情况:O(n²)——当数组以降序排序或数组中的所有元素都相同时,由于子数组高度不平衡,因此时间复杂度跃升至O(n²)。

· 空间复杂度:O(n)。

由于递归调用quicksort函数,因此使用内部堆栈来存储这些函数调用。堆栈中最多有n个调用,因此空间复杂度为O(n)。

0910ab77d58d0b8bb5ae233ca0fe5e10.png

合并排序

合并排序和快速排序一样,都使用分而治之概念。在合并排序主要工作是合并子数组,而在快速排序中,主要工作是对数组进行分区/划分,因此快速排序也称为分区排序。

下面的函数会一直将数组递归地分成两个子数组直到每个子数组只有一个元素。

publicvoid merge(int arr[], int l, int m, int r) {             int n1 = m-l+1;             int n2 = r-m;             int[] L =new int[n1];             int[] R =new int[n2];             for(int i =0;i < n1; i++) {                 L[i] = arr[l+i];             }             for(int i =0;i < n2; i++) {                 R[i] = arr[m+1+i];             }             int i =0, j =0, k =l;             while(i < n1 && j < n2) {                 if(L[i] <=R[j]) {                     arr[k++] =L[i++];                 }                 else {                     arr[k++] =R[j++];                 }             }             while(i < n1) {                 arr[k++] =L[i++];             }             while(j < n2) {                 arr[k++] =R[j++];             }         

将这些子数组存储在两个新数组中后,就根据它们的顺序进行合并,并将它们存储到输入数组中。所有这些子数组合并后,输入数组就排序完成了。

publicvoid merge(int arr[], int l, int m, int r) {             int n1 = m-l+1;             int n2 = r-m;             int[] L =new int[n1];             int[] R =new int[n2];             for(int i =0;i < n1; i++) {                 L[i] = arr[l+i];             }             for(int i =0;i < n2; i++) {                 R[i] = arr[m+1+i];             }             int i =0, j =0, k =l;             while(i < n1 && j < n2) {                 if(L[i] <=R[j]) {                     arr[k++] =L[i++];                 }                 else {                     arr[k++] =R[j++];                 }             }             while(i < n1) {                 arr[k++] =L[i++];             }             while(j < n2) {                 arr[k++] =R[j++];             }         }

性能分析:

· 时间复杂度

1.最佳情况:O(nlogn)——首先将数组递归分为两个子数组,时间复杂度为O(logn)。每次函数调用都将调用时间复杂度为O(n)的分区函数,因此,总时间复杂度为O(nlogn)。

2.最坏情况:O(nlogn)——最坏情况下的时间复杂度与最佳情况相同。

· 空间复杂度:O(n)

由于递归调用MergeSort函数,因此使用内部堆栈来存储这些函数调用。堆栈中最多有n个调用,因此空间复杂度为O(n)。

af7543ff4c8bf1c4b0c6b12b80dca9d2.png

图源:unsplash

上面提到的算法是基于比较的排序算法,因为在对元素进行相互比较之后再对其进行排序。但是,还有其他基于非比较的排序算法,例如计数排序、基数排序、桶排序等,由于时间复杂度为O(n),因此也称为线性排序算法。

每种算法各自都有优缺点,采用哪种算法取决于优先级。如果效率上没有问题,可以使用易实现的冒泡排序。或者在数组几乎排好序时使用插入排序,因为此时插入排序的时间复杂度是线性的。

7cad1af68e2e0a4ae6497cb87174be38.png

留言点赞关注

我们一起分享AI学习与发展的干货

如转载,请后台留言,遵守转载规范

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值