11、【算法】排序算法总结

常见排序算法总结

一、冒泡排序
1、定义

    冒泡排序是一种比较简单的排序算法,它会遍历若干次要排序的数列,每次便利时,它都会从前往后依次的比较两个相邻的数的大小;如果前者比后者大,则交换它们的位置。
    这样一次遍历之后,最大的元素就在数列的末尾了。采用相同的方法在此遍历时,第二大的元素就被排列在最大元素之前,重复此操作,直到整个数列都有序为止。

冒泡排序的时间复杂度为 :O(N2)-稳定

2、实现(C++)
void bubbleSort(int *arr, int len)
{
    int temp;	  
    int flag;    //标志位
    //注意i和j的取值范围是重点
    for(int i = len-1;i > 0; i++)
    {
        flag = 0;//初始化标志为为0

        //将arr[0……i]中最大的数据放在末尾
        for(int j = 0; j < i; j++)
        {
            if(arr[j] > arr[j+1])
            {
                //交换两个数
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;

                //若发生交换,则将标志为置为1
                flag = 1;
            }
        }
        //若没有发生交换,则说明数列已有序
        if(flag == 0)
            break;
    }
}
二、快速排序
1、定义

    快速排序(quick sort)使用分治法策略
    它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都小,然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数列变成有序数列。

快速排序的流程:
    (1)从数列中选择一个基准值
    (2)将所有比基准值小的数据放在基准值前面,所有比基准值大的数据放在基准值后面(相同的数据可以放在一边),在这个分区结束后,该基准值就处于数列的中间位置了。
    (3)递归的把基准值前面的子数列和基准值后面的子数列进行快速排序。

快速排序的时间复杂度:最坏的情况下是O(n2),平均情况是O(nlog2n)-不稳定

2、实现(C++)
//2、快速排序
void quickSort(int *arr, int left, int right)
{
    if(left < right)
    {
        int midTemp;//基准值

        int i = left;
        int j = right;
        midTemp = arr[0];    //将arr[0]作为基准值
        while(i < j)
        {
            while(i < j && arr[j] > midTemp)
                j--;//从后往前找第一个小于midTemp的值
            if(i < j)
                a[i++] = a[j];
            while(i < j && arr[i] < midTemp)
                i++;//从前往后找第一个大于midTemp的值
            if(i < j)
                arr[j--] = arr[i];
        }
        arr[i] = midTemp;
        //递归调用
        quickSort(arr, left, i-1);
        quickSort(arr, i+1, right);
    }
}
三、直接插入排序
1、定义

    直接插入排序(Straight insertion Sort)的基本思想是:把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含n-1个元素,排序过程中,每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为一个新的有序表,重复n-1次可完成整个排序过程。
    直接插入排序的时间复杂度:O(n2)-稳定

2、实现(C++)
//3、直接插入排序
void insertSort(int *arr, int len)
{
    for(int i = 1; i < len; i++)
    {
        //为arr[i]在前面arr[0 ... i-1]有序区间中找到一个合适的位置
        for(int j = i - 1; j >= 0; j--)
        {
            if(arr[j] < arr[i])
                break;
        }

        //若找到一个合适的位置
        if(j != i -1)
        {
            //将比arr[i]大的数据向后移
            int temp = arr[i];
            for(int k = i - 1; k > j; k--)
                arr[k+1] = arr[k];
            //将arr[i]放到正确的位置上
            arr[k+1] = temp;
        }
    }
}
四、希尔排序
1、定义

    希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。该方法又称缩小增量排序

    希尔排序实质上是一种分组插入方法。它的基本思想是:对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。

    希尔排序时间复杂度:希尔排序的时间复杂度与增量(即,步长gap)的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N2),而Hibbard增量的希尔排序的时间复杂度为O(N3/2)。-不稳定

Hbbard增量序列: h = 1,3,7,,2^k-1^
hbbard增量序列的特点是:相邻增量没有公因子,最坏的运行时间为O(N^3/2^)
2、实现(C++)
/*
 * 希尔排序
 * 参数说明:
 *     arr -- 待排序的数组
 *     n -- 数组的长度
 */
void shellSort(int* arr, int n)
{
    // gap为步长,每次减为原来的一半。
    for (int gap = n / 2; gap > 0; gap /= 2)
    {
        // 共gap个组,对每一组都执行直接插入排序
        for (int i = 0 ;i < gap; i++)
        {
            for (int j = i + gap; j < n; j += gap)
            {
                // 如果a[j] < a[j-gap],则寻找a[j]位置,并将后面数据的位置都后移。
                if (arr[j] < arr[j - gap])
                {
                    int tmp = arr[j];
                    int k = j - gap;
                    while (k >= 0 && a[k] > tmp)
                    {
                        arr[k + gap] = arr[k];
                        k -= gap;
                    }
                    arr[k + gap] = tmp;
                }
            }
        }
    }
}
五、选择排序
1、定义

    选择排序(Selection sort)是一种简单直观的排序算法。它的基本思想是:首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置;接着,再从剩余未排序的元素中继续寻找最小(or最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

    选择排序时间复杂度:选择排序的时间复杂度是O(N2)。
    假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?N-1!因此,选择排序的时间复杂度是O(N2)。-不稳定

2、实现(C++)
/*
 * 选择排序
 * 参数说明:
 *     a -- 待排序的数组
 *     n -- 数组的长度
 */
void selectSort(int* a, int n)
{
    int min;    // 无序区中最小元素位置

    for(int i=0; i<n; i++)//有序区的末尾位置
    {
        min=i;

        // 找出"a[i+1] ... a[n]"之间的最小元素,并赋值给min。
        for(j=i+1; j<n; j++)// 无序区的起始位置
        {
            if(a[j] < a[min])
                min=j;
        }

        // 若min!=i,则交换 a[i] 和 a[min]。
        // 交换之后,保证了a[0] ... a[i] 之间的元素是有序的。
        if(min != i)
        {
            int tmp = a[i];
            a[i] = a[min];
            a[min] = tmp;
        }
    }
}
六、堆排序
1、定义

    堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。我们知道,堆分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。

    鉴于最大堆和最小堆是对称关系,理解其中一种即可。本文将对最大堆实现的升序排序进行详细说明。

    最大堆进行升序排序的基本思想:
      ① 初始化堆:将数列a[1…n]构造成最大堆。
      ② 交换数据:将a[1]和a[n]交换,使a[n]是a[1…n]中的最大值;然后将a[1…n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1…n-1]中的最大值;然后将a[1…n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。

数组实现的二叉堆的性质:
    在第一个元素的索引为 0 的情形中:
    性质一:索引为i的左孩子的索引是 (2*i+1);
    性质二:索引为i的左孩子的索引是 (2*i+2);
    性质三:索引为i的父结点的索引是 floor((i-1)/2);

堆排序的时间复杂度:O(n*log2n) - 不稳定

2、实现(C++)
 /*
 * 最大堆实现升序排序
 * 参数说明:
 *     a -- 待排序的数组
 *     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
 *     end   -- 截至范围(一般为数组中最后一个元素的索引)
 */
void maxheap_down(int a[], int start, int end)
{
    int c = start;            // 当前(current)节点的位置
    int l = 2*c + 1;        // 左(left)孩子的位置
    int tmp = a[c];            // 当前(current)节点的大小
    for (; l <= end; c=l,l=2*l+1)
    {
        // "l"是左孩子,"l+1"是右孩子
        if ( l < end && a[l] < a[l+1])
            l++;        // 左右两孩子中选择较大者,即m_heap[l+1]
        if (tmp >= a[l])
            break;        // 调整结束
        else            // 交换值
        {
            a[c] = a[l];
            a[l]= tmp;
        }
    }
}
七、归并排序
1、定义

    将两个的有序数列合并成一个有序数列,我们称之为"归并"。归并排序(Merge Sort)就是利用归并思想对数列进行排序。根据具体的实现,归并排序包括"从上往下"和"从下往上"2种方式。
    1、从下往上的归并排序:将待排序的数列分成若干个长度为1的子数列,然后将这些数列两两合并;得到若干个长度为2的有序数列,再将这些数列两两合并;得到若干个长度为4的有序数列,再将它们两两合并;直接合并成一个数列为止。这样就得到了我们想要的排序结果。

    2、从上往下的归并排序:它与"从下往上"在排序上是反方向的。它基本包括3步:
    ① 分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
    ② 求解 – 递归地对两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。递归的终结条件是子区间长度为1。
    ③ 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]。

归并排序时间复杂度:归并排序的时间复杂度是O(Nlog2N)。
    假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?
    归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是O(N
log2N)。- 稳定

2、实现(C++)
/*
 * 将一个数组中的两个相邻有序区间合并成一个
 *
 * 参数说明:
 *     a -- 包含两个有序区间的数组
 *     start -- 第1个有序区间的起始地址。
 *     mid   -- 第1个有序区间的结束地址。也是第2个有序区间的起始地址。
 *     end   -- 第2个有序区间的结束地址。
 */
void merge(int* a, int start, int mid, int end)
{
    int *tmp = new int[end-start+1];    // tmp是汇总2个有序区的临时区域
    int i = start;            // 第1个有序区的索引
    int j = mid + 1;        // 第2个有序区的索引
    int k = 0;                // 临时区域的索引

    while(i <= mid && j <= end)
    {
        if (a[i] <= a[j])
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    }

    while(i <= mid)
        tmp[k++] = a[i++];

    while(j <= end)
        tmp[k++] = a[j++];

    // 将排序后的元素,全部都整合到数组a中。
    for (i = 0; i < k; i++)
        a[start + i] = tmp[i];

    delete[] tmp;
}

/*
 * 归并排序(从上往下)
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     start -- 数组的起始地址
 *     endi -- 数组的结束地址
 */
void mergeSortUp2Down(int* a, int start, int end)
{
    if(a==NULL || start >= end)
        return ;

    int mid = (end + start)/2;
    mergeSortUp2Down(a, start, mid); // 递归排序a[start...mid]
    mergeSortUp2Down(a, mid+1, end); // 递归排序a[mid+1...end]

    // a[start...mid] 和 a[mid...end]是两个有序空间,
    // 将它们排序成一个有序空间a[start...end]
    merge(a, start, mid, end);
}

/*
 * 对数组a做若干次合并:数组a的总长度为len,将它分为若干个长度为gap的子数组;
 *             将"每2个相邻的子数组" 进行合并排序。
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     len -- 数组的长度
 *     gap -- 子数组的长度
 */
void mergeGroups(int* a, int len, int gap)
{
    int i;
    int twolen = 2 * gap;    // 两个相邻的子数组的长度

    // 将"每2个相邻的子数组" 进行合并排序。
    for(i = 0; i+2*gap-1 < len; i+=(2*gap))
    {
        merge(a, i, i+gap-1, i+2*gap-1);
    }

    // 若 i+gap-1 < len-1,则剩余一个子数组没有配对。
    // 将该子数组合并到已排序的数组中。
    if ( i+gap-1 < len-1)
    {
        merge(a, i, i + gap - 1, len - 1);
    }
}

/*
 * 归并排序(从下往上)
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     len -- 数组的长度
 */
void mergeSortDown2Up(int* a, int len)
{
    int n;

    if (a==NULL || len<=0)
        return ;

    for(n = 1; n < len; n*=2)
        mergeGroups(a, len, n);
}
八、桶排序
1、定义

    桶排序(Bucket Sort)的原理很简单,它是将数组分到有限数量的桶里

    假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个"桶"。
    在排序时,逐个遍历数组a,将数组a的值,作为"桶数组r"的下标。当a中数据被读取时,就将桶的值加1。例如,读取到数组a[3]=5,则将r[5]的值+1。

桶排序的时间复杂度
    对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)log2(N/M))=O(N+N(log2N-log2M))=O(N+Nlog2N-Nlog2M)
    当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。- 稳定

2、实现(C++)
/*
 * 桶排序
 * 参数说明:
 *     a -- 待排序数组
 *     n -- 数组a的长度
 *     max -- 数组a中最大值的范围
 */
void bucketSort(int a[], int n, int max)
{
    int i,j;
    int buckets[max];

    // 将buckets中的所有数据都初始化为0。
    memset(buckets, 0, max*sizeof(int));

    // 1. 计数
    for(i = 0; i < n; i++) 
        buckets[a[i]]++; 

    // 2. 排序
    for (i = 0, j = 0; i < max; i++) 
    {
        while( (buckets[i]--) >0 )
            a[j++] = i;
    }
}

猜你喜欢

转载自blog.csdn.net/sinat_33924041/article/details/83545553