常用排序算法整合(一)(插入、希尔、冒泡、快排)

插入排序

算法步骤

  • 假设待排序列存储在 A r r [ 0 : n ] Arr[0:n] Arr[0:n]当中
  • 循环 n − 1 n-1 n1次,每次使用顺序查找,得到 A r r [ i ] Arr[i] Arr[i]在有序序列 A r r [ 0 : i ] Arr[0:i] Arr[0:i]当中的插入位置,然后将其插入到有序序列当中,重复操作直到所有元素都插入到有序序列( A r r [ 0 : n ] Arr[0:n] Arr[0:n]有序)

算法实现

时间复杂度: O ( n 2 ) O(n^2) O(n2);空间复杂度: O ( 1 ) O(1) O(1),稳定排序

void insertSort(vector<int>& arr)
{
    for(int i = 1;i<arr.size();i++)
    {
        int tmp = arr[i];
        int j = i-1;
        while((j>=0) && (tmp<arr[j]))
        {
            arr[j+1] = arr[j];
            j--;   
        }
        arr[j+1] = tmp;
    }
}

在这里插入图片描述

折半插入排序

与普通的插入排序思想是相同的,但是因为查找要插入位置是在有序序列中查找,所以使用二分查找来减少比较次数,但移动次数不变,所以时间复杂度、空间复杂度相同

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本,从“减少记录个数”和“序列基本有序”两个方面对其进行了改进。

算法步骤

希尔排序通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。
在这里插入图片描述
如图,第一趟间隔 d 1 = 5 d_1=5 d1=5,原始序列被分成 { 49 , 13 } 、 { 38 , 27 } \{49,13\}、\{38,27\} { 49,13}{ 38,27}等五组,在组内插入排序,得到了第一趟排序的结果,

然后第二趟间隔为 d 2 = 3 d_2=3 d2=3,全部记录分成3组,组内插入排序得到第二次的结果

第三趟增量为 d 3 = 1 d_3=1 d3=1,直接对整个序列插入排序,得到最终结果

算法实现

void shellInsertSort(vector<int>& arr,int dk)
{
    for(int i = dk;i<arr.size();i+=dk)//从0号开始,下一个是dk号
    {
        int tmp = arr[i];
        int j = i-dk;//从上一个
        while((j>=0) && (tmp<arr[j]))
        {
            arr[j+dk] = arr[j];
            j-=dk;   
        }
        arr[j+dk] = tmp;
    }
}
void shellSort(vector<int>& arr,vector<int> dks)
{
    for(int i = 0;i<dks.size();i++)
    {
        shellInsertSort(arr,dk[i]);
    }
}

算法分析

当增量大于1时,记录是跳跃式的移动的,从而最后一趟增量为1的排序时,序列已经基本有序,只需要少量比较和移动就可完成排序,所以希尔排序的时间复杂度要比直接插入排序低,但具体分析还是一个尚未解决的难题。

希尔排序空间复杂度和直接插入排序相同

希尔排序是不稳定的排序方法

冒泡排序

冒泡排序的思想是两两比较相邻元素,如果发生逆序则交换,从而让关键字小的元素像“气泡”上浮

算法步骤

  • 假设待排序列存储在 A r r [ 0 : n ] Arr[0:n] Arr[0:n]中,首先比较第一个元素和第二个元素,若为逆序则交换,然后比较第二个和第三个,以此类推直到第n-1个和第n个比较完,完成一次冒泡过程
  • 然后进行第二次冒泡过程,第一趟已经使最大的元素“沉底”,所以只对前n-1个元素进行冒泡
  • 直到某一趟冒泡过程没有进行过交换操作,说明已经达到排序要求,完成排序

图片来源:https://www.runoob.com/w3cnote/bubble-sort.html

算法实现

void bubbleSort(vector<int>& arr)
{
    int n = arr.size();
    bool swapFlag = 1;
    while((n>0) && (swapFlag==1))
    {
        swapFlag = 0;
        for(int i = 0;i<n;i++)
        {
            if(arr[i]>arr[i+1]) //降序改为小于
            {
                int t = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = t;
                swapFlag = 1;
            }
        }
        n--;
    }
}

算法分析

最好情况下只需比较 n − 1 n-1 n1次,无需移动元素

最坏情况下,需要 n − 1 n-1 n1次冒泡,总的比较次数为 n ( n − 1 ) / 2 ≈ n 2 / 2 n(n-1)/2 \approx n^2/2 n(n1)/2n2/2,总的交换次数: 3 n ( n − 1 ) / 2 ≈ 3 n 2 / 2 3n(n-1)/2 \approx 3n^2/2 3n(n1)/23n2/2

所以平均情况下,比较次数和移动次数分别约为 n 2 / 4 , 3 n 2 / 4 n^2/4,3n^2/4 n2/43n2/4,故时间复杂度为 O ( n 2 ) O(n^2) O(n2)

空间复杂度为 O ( 1 ) O(1) O(1)

冒泡排序是稳定排序

算法平均时间性能由于移动元素次数多,所以比直接插入排序要差

快速排序

快速排序是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

算法步骤

  1. 从序列中挑出一个元素作为枢轴(通常取第一个)
  2. 重新排序,所有比枢轴小的元素放在枢轴前面,所有元素比枢轴大的放在枢轴的后面(相同的数可以到任一边)
  3. 递归地把小于枢轴元素的子序列和大于枢轴元素的子序列排序;

其中一次快排的操作如下:

  1. 选择枢轴,设置两个指针low和high分别指向序列的下界和上界
  2. 从序列最右侧向左搜索,找到第一个小于枢轴的元素,将其移动到low处
  3. 然后从最左侧向右搜索第一个大于枢轴的元素,将其移动到high处
  4. 重复2、3,直到low与high相等为止,此时low(或high)所在位置即为这次排序枢轴的最终位置,左右为两个子序列

算法实现

int Paritition(vector<int>& arr, int low, int high) 
{
   int pivot = arr[low];
   while (low < high) 
   {
     while (low < high && arr[high] >= pivot) 
     {
       --high;
     }
     arr[low] = arr[high];
     while (low < high && arr[low] <= pivot) 
     {
       ++low;
     }
     arr[high] = arr[low];
   }
   arr[low] = pivot;
   return low;
 }

void QuickSort(vector<int>& arr, int low, int high)
{
    if (low < high)
    {
        int pivot = Paritition(arr, low, high);
        QuickSort(arr, low, pivot - 1);
        QuickSort(arr, pivot + 1, high);
    }
}

算法分析

理论上可以证明,平均情况下,快速排序的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

快速排序是递归的,执行行时需要有一个栈来存放相应的数据。最大递归调用次数与递归树的 深度一致,所以最好情况下的空间复杂度为 o ( l o g 2 n ) o(log_2n) o(log2n),最坏情况下为 O ( n ) O(n) O(n)

快速排序是不稳定排序

参考

严蔚敏 李冬梅 吴伟民 《数据结构(C语言版)(第二版)》

猜你喜欢

转载自blog.csdn.net/i0o0iW/article/details/108437625