排序算法:实现,平均复杂度,最好…

本文详细介绍了包括冒泡排序、插入排序、Shell排序等在内的七种经典排序算法的实现原理及性能分析,帮助读者深入理解每种算法的特点及其适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1:冒泡算法
    1.1 实现

void bubbleSort(int *a, int n)
{
        int i;
        int j;
        int flag;
        for(i = 0; i < n; i++)
        {
                flag = 0;
                for(j = 0; j < n - i - 1; j++)
                        if(a[j] > a[j + 1])
                        {
                                swap(a[j], a[j + 1]);
                                flag = 1;
                        }
                if(flag == 0)
                        break;
        }
}

    1.2 平均复杂度
            n轮循环,每一轮循平均的复杂度为n/2,故总的复杂度为:O(n * n)
    1.3 最好情况
          当输入序列已经排序好的时候,一轮冒泡下来没有交换过位置,直接退出程序,复杂度为:O(n)
    1.4 最坏情况
          当输入序列是你逆序的时候,每一次比较都要进行交换,复杂度为:O(n*n)
    1.5 稳定性
          a = b的时候,由于只有大于才做交换,故a,b的位置没有机会交换,所以,冒泡排序是稳定的

2:插入排序
    2.1 实现

void insertSort(int *a, int n)
// 该算法是最初始的实现,顺序比较前面元素,效率较低
{
        int tmp;
        int i, j, k;
        for(i = 0; i < n; ++i)
        {
                tmp = a[i];
                for(j = 0; j < i; ++j)
                {
                        if(tmp < a[j])
                        {
                                for(k = i; k > j; --k)
                                {
                                        a[k] = a[k - 1];
                                }
                                a[j] = tmp;
                                break;
                        }
                }
        }
}

void simpleInsertSort(int *a, int n)
PS: 以下代码引自《数据结构与算法分析:C++描述》(第3版)
// 该算法插入时,逆序比较前面一排序元素,效率较高
{
        int tmp;
        int i, j;
        for(i = 1; i < n; ++i)
        {
                for(j = i, tmp = a[i]; a[j - 1] > tmp && j > 0; --j)
                {
                        a[j] = a[j - 1];
                }
                a[j] = tmp;
        }
}

    2.2 平均复杂度
          插入的次数为n,而每次插入操作需要移动的数据平均复杂度为O(n),故总的平均复杂度为:O(n*n)
    2.3 最好情况
          当输入的序列已经有序的时候,只需要进行比较,不用移动数据,故其复杂度仅仅为O(n)
    2.4 最坏情况
          当输入的序列为逆序的时候,每次插入都要移动之前所有元素,故其复杂度为O(n*n)
    2.5 稳定性
          如果a=b,则当a在前面时候,其必然比b先确定,而后面b再插入时,必然在a后面,故该算法稳定

3:shell排序
    3.1 实现
PS: 以下代码引自《数据结构与算法分析:C++描述》(第3版)

void shellSort(int *a, int n)
// 该程序使用的增量为shell增量,最坏情况为O(n*n)
// 更好的效果可以选择Hibbard增量:2^k - 1
{
        int tmp;
        int gap, i, j;
        for(gap = n / 2; gap > 0; gap /= 2)
        {
                for(i = gap; i < n; ++i)
                {
                        tmp = a[i];
                        j = i;
                        for(; j >= gap && tmp < a[j - gap]; j -= gap)
                                a[j] = a[j - gap];
                        a[j] = tmp;
                }
        }
}
    3.2 平均复杂度
          由于使用的增量(程序中的gap)不同,导致的复杂度也不同,常用的有Hibbard增量,基于模拟的结果被认为是O(n^(5/4))
    3.3 最好情况
          当输入序列已经有序的时候,每一次增量向前比较都立即不满足条件而退出,故只有前面两层循环起到了作用,复杂度为O(nlogn)
    3.4 最坏情况
          使用Hibbard增量的时候,该排序算法的最坏情况复杂度为O(n^(3/2))(已经被证明)
    3.5 稳定性
          由于不同的gap间隔对应的数据是独自比较的,所以,如果a=b但是不在同一个gap间隔上,显然就会出现前后颠倒的情况,即是说,该算法不稳定

4:堆排序
    4.1 实现
PS: 以下代码引自《数据结构与算法分析:C++描述》(第3版)

void percDown(int *, int , int );
void heapSort(int *a, int n)
{
        int i, j;
        // build heap
        for(i = n / 2; i >= 0; i--)
                percDown(a, i, n);

        // delete Max
        for(j = n - 1; j > 0; j--)
        {
                swap(a[0], a[j]);
                percDown(a, 0, j);
        }
}


inline int leftChild(int i)
{
        return 2 * i;
}

void percDown(int *a, int i, int n)
{
        int child;
        int tmp;
        for(tmp = a[i]; leftChild(i) < n; i = child)
        {
                child = leftChild(i);
                if(child != n - 1 && a[child] < a[child + 1])
                        child++;
                if(tmp < a[child])
                        a[i] = a[child];
                else
                        break;
        }
        a[i] = tmp;
}
    4.2 平均复杂度
          排序的过程主要取决于后期的调整(前期的建立堆的过程只需要O(logn * logn)),其复杂度为O(nlongn)
    4.3 最好情况
          对于堆排序,无论输入的序列是否有序或者其他的条件,建立和调整堆的复杂度都不会受到影响,故最好情况的复杂度为O(nlogn)
    4.4 最坏情况
          对于堆排序,无论输入的序列是否有序或者其他的条件,建立和调整堆的复杂度都不会受到影响,故最坏情况的复杂度为O(nlogn)
    4.5 稳定性
          不稳定,暂时没有想到好的说明例子,但是如果画出其二叉树的表示图,就可以直观地看出,a=b时,如果b后于a的插入,其将在a的后面,但是经过percDown调整后,b有可能被调到比a更高的层次(也就是a的前面了),所以,顺序可能存在调换情况。

5:归并排序
    5.1 实现
PS: 以下代码引自《数据结构与算法分析:C++描述》(第3版)

void merge(int *, int *, int , int , int );
void mergeSort(int *, int *, int , int );
void simpleMergeSort(int *a, int n)
{
        int *b = new int[n];
        mergeSort(a, b, 0, n - 1);
        delete b;
}

void mergeSort(int *a, int *tmp, int left, int right)
{
        if(left < right)
        {
                int center = (left + right) / 2;
                mergeSort(a, tmp, left, center);
                mergeSort(a, tmp, center + 1, right);
                merge(a, tmp, left, center + 1, right);
        }
}
void merge(int *a, int *tmp, int leftPos, int rightPos, int rightEnd)
{
        int leftEnd = rightPos - 1;
        int tmpPos = leftPos;
        int numElements = rightEnd - leftPos + 1;

        while(leftPos <= leftEnd && rightPos <= rightEnd)
        {
                if(a[leftPos] < a[rightPos])
                        tmp[tmpPos++] = a[leftPos++];
                else
                        tmp[tmpPos++] = a[rightPos++];
        }

        while(leftPos <= leftEnd)
                tmp[tmpPos++] = a[leftPos++];

        while(rightPos <= rightEnd)
                tmp[tmpPos++] = a[rightPos++];

        for(int i = 0; i < numElements; i++, rightEnd--)
                a[rightEnd] = tmp[rightEnd];
}

    5.2 平均复杂度
          对于各种输入序列,归并排序的处理过程都是一样的,T(1) = 1, T(N) = T(N/2) + N,不难求得最后的复杂度T(N)为O(NlogN)
    5.3 最好情况
          与平均情况一致
    5.4 最坏情况
          与平均情况一致
    5.5 稳定性
          由于没有发生数据交换,所有当a=b的时候,a一开始如果在b前面,则其每一次合并后仍然在b前面,故该排序算法是稳定的

6:快速排序
    6.1 实现
PS: 以下代码引自《数据结构与算法分析:C++描述》(第3版)

int median3(int *, int , int);
void quickSort(int *, int , int);
void simpleQuickSort(int *a, int n)
{
        quickSort(a, 0, n - 1);
}

void quickSort(int *a, int left, int right)
{
// if number of elements less than 10, used insertSort
        if(left + 10 > right)
                insertSort(a, right - left + 1);
        else
        {
                int pivot = median3(a, left, right);
                int i = left, j = right;
                for(;;)
                {
                        while(a[++i] < pivot);
                        while(a[--j] > pivot);
                        if(i < j)
                                swap(a[i], a[j]);
                        else
                                break;
                }
                // reset pivot
                swap(a[i], a[right - 1]);
                quickSort(a, left, i - 1);
                quickSort(a, i + 1, right);
        }
}

// find out median of three elements and set pivot
int median3(int *a, int left, int right)
{
        int center = (left + right) / 2;
        if(a[center] < a[left])
                swap(a[left], a[center]);
        if(a[right] > a[left])
                swap(a[left], a[right]);
        if(a[right] < a[center])
                swap(a[center], a[right]);

        // place pivot at position right - 1
        swap(a[center], a[right - 1]);
        return a[right - 1];
}

    6.2 平均复杂度
            平均复杂度为O(NlogN)
    6.3 最好情况
            每次都刚好选到了一个区间段的中位数,相当于归并排序的过程,复杂度为O(NlongN)
    6.4 最坏情况
            每次都刚好选到了最小的元素作为主元,导致极度的不平衡,退化为插入排序的最坏情况了,复杂度为O(N^2)
    6.5 稳定性
            当a=b>pivot且a在b前面的时候,由于从后面开始遍历,故b会先于a被替换到pivot的前面,这样,b就变成了在a的前面,也就是说,ab位置对调,故该排序算法不稳定
7:直接选择排序
    7.1 实现

void selectSort(int *a, int n)
{
        int i, j;
        int tmp;
        for(i = 0; i < n; ++i)
        {
                tmp = i;
                for(j = i + 1; j < n; j++)
                        if(a[j] < a[tmp])
                                tmp = j;
                swap(a[i], a[tmp]);
        }
}

    7.2 平均复杂度
          每次循环需要访问n-i个元素得到最小值,故其总的比较次数为(N+1)*N/2,故复杂度为O(N^2)
    7.3 最好情况
          与平均情况一致
    7.4 最坏情况
          与平均情况一致
    7.5 稳定性
          假设a=b,并且a在b的前面,而在某轮循环中最小值在b的后面,而次最小值需要跟a交换,那么该轮过后,b就在a前面了,所以,该排序算法不稳定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值