各排序算法总结及实现

1. 插入排序(InsertSort)

插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。适用于数据在1000一下的场合下使用插入排序,或者重复排序不超过200数据项的序列。
//遍历数组,遍历到i时,a0,a1…ai-1是已经排好序的,取出ai,从ai-1开始向前和每个比较大小,如果小于,则将此位置元素向后移动,继续先前比较,如果不小于,则放到正在比较的元素之后。可见相等元素比较是,原来靠后的还是拍在后边,所以插入排序是稳定的。

void insertion_sort (int a[], int n) {
    int i,j,v;
    for (i=1; i<n; i++) {
      //如果第i个元素小于第j个,则第j个向后移动
        for (v=a[i], j=i-1; j>=0&&v<a[j]; j--)
            a[j+1]=a[j];
        a[j+1]=v;
    }
}

2.希尔排序(ShellSort)

Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序(减少一次排序的数据长度),以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,归并排序,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。

void shell_sort(int a[], int n)
{
    int d, i, j, temp; //d为增量
    for(d = n/2;d >= 1;d = d/2) //增量递减到1使完成排序
    {
        for(i = d; i < n;i++)   //插入排序的一轮
        {
            temp = a[i];
            for(j = i - d;(j >= 0) && (a[j] > temp);j = j-d)
            {
                a[j + d] = a[j];
            }
        a[j + d] = temp;
        }
    }
}

3.选择排序

选择排序是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
//遍历数组,遍历到i时,a0,a1…ai-1是已经排好序的,然后从i到n选择出最小的,记录下位置,如果不是第i个,则和第i个元素交换。此时第i个元素可能会排到相等元素之后,造成排序的不稳定。

void selection_sort (int a[], int n) {
    int i,j,pos,tmp;
    for (i=0; i<n-1; i++) {
      //寻找最小值的下标
        for (pos=i, j=i+1; j<n; j++)
            if (a[pos]>a[j])
                pos=j;
        if (pos != i) {
            tmp=a[i];
            a[i]=a[pos];
            a[pos]=tmp;
        }
    }
}

4.堆排序

堆排序适合于数据量非常大的场合(百万数据)。

堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。

堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。

void heapAdjust(int a[], int i, int nLength)
{
    int nChild;
    int nTemp;
    for (nTemp = a[i]; 2 * i + 1 < nLength; i = nChild)
    {
        // 子结点的位置=2*(父结点位置)+ 1
        nChild = 2 * i + 1;
        // 得到子结点中较大的结点
        if ( nChild < nLength-1 && a[nChild + 1] > a[nChild])
            ++nChild;
        // 如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
        if (nTemp < a[nChild])
        {
            a[i] = a[nChild];
            a[nChild]= nTemp;
        }
        else
        // 否则退出循环
            break;
    }
}

// 堆排序算法
void heap_sort(int a[],int length)
{
    int tmp;
    // 调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
    //length/2-1是第一个非叶节点,此处"/"为整除
    for (int i = length / 2 - 1; i >= 0; --i)
        heapAdjust(a, i, length);
    // 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
    for (int i = length - 1; i > 0; --i)
    {
        // 把第一个元素和当前的最后一个元素交换,
        // 保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
      ///  Swap(&a[0], &a[i]);
          tmp = a[i];
          a[i] = a[0];
          a[0] = tmp;
        // 不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
        heapAdjust(a, 0, i);
    }
}

5.冒泡排序

冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。

void BubbleSort(int *a, size_t size)
{
    assert(a);
    int count = 0;
    for (size_t i = 0; i < size-1; i++)//循环条件保证每个数都被比较
    {
        for (size_t j = 0; j < size -1- i; j++)//循环的次数根据i的变化而不同,每次-1
        {
            if (a[j]>a[j + 1])//比较相邻的两个数
            {
                swap(a[j], a[j + 1]);
                count++;
            }
        }
        if (count == 0)//当一次交换都没有的时候,序列有序,跳出循环
        {
            break;
        }
    }
}

6.快速排序

快速排序是一个就地排序,分而治之,大规模递归的算法。
快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。
//快速排序首先找到一个基准,下面程序以第一个元素作为基准(pivot),然后先从右向左搜索,如果发现比pivot小,则和pivot交换,然后从左向右搜索,如果发现比pivot大,则和pivot交换,一直到左边大于右边,此时pivot左边的都比它小,而右边的都比它大,此时pivot的位置就是排好序后应该在的位置,此时pivot将数组划分为左右两部分,可以递归采用该方法进行。快排的交换使排序成为不稳定的。

int mpartition(int a[], int l, int r) {
    int pivot = a[l];

    while (l<r) {
        while (l<r && pivot<=a[r]) r--;
        if (l<r) a[l++]=a[r];
        while (l<r && pivot>a[l]) l++;
        if (l<r) a[r--]=a[l];
    }
    a[l]=pivot;
    return l;
}

void quick_sort (int a[], int l, int r) {

    if (l < r) {
        int q = mpartition(a, l, r);
        msort(a, l, q-1);
        msort(a, q+1, r);
    }
}

7. 归并排序(MergeSort)

归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。
参考链接:https://www.cnblogs.com/lizr-ithouse/p/5839384.html

private static void merge(int[] a, int low, int mid, int high) {
        int i = low; //左数组下一个要进行比较的元素的索引
        int j = mid + 1; //右数组下一个要进行比较的元素的索引
        int N = high + 1; //本次归并的元素数目
        int[] tmpArray = new int[N]; //用于暂时存放比较后的元素
        for (int k = low; k <= high; k++) {
            if (i > mid) {  //左数组元素已全比较完
                tmpArray[k] = a[j++];
            } else if (j > high) { //右数组元素已全比较完
                tmpArray[k] = a[i++];
            } else if (a[j] < a[i]) { //右数组元素小于左数组
                tmpArray[k] = a[j++];
            } else {  //右数组元素大于等于左数组
                tmpArray[k] = a[i++];
            }
        }
        for (int k = low; k < N; k++) {
            a[k] = tmpArray[k];
        }
    }

分而治之的归并排序:
参考链接:https://www.cnblogs.com/chengxiao/p/6194356.html


//将有二个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{
    int i = first, j = mid + 1;
    int m = mid,   n = last;
    int k = 0;

    while (i <= m && j <= n)
    {
        if (a[i] <= a[j])
            temp[k++] = a[i++];
        else
            temp[k++] = a[j++];
    }

    while (i <= m)
        temp[k++] = a[i++];

    while (j <= n)
        temp[k++] = a[j++];

    for (i = 0; i < k; i++)
        a[first + i] = temp[i];
}
void mergesort(int a[], int first, int last, int temp[])//分解复杂度O(logn)
{
    if (first < last)
    {
        int mid = (first + last) / 2;
        mergesort(a, first, mid, temp);    //左边有序
        mergesort(a, mid + 1, last, temp); //右边有序
        mergearray(a, first, mid, last, temp); //再将二个有序数列合并//合并复杂度O(n)
    }
}

bool MergeSort(int a[], int n)
{
    int *p = new int[n];
    if (p == NULL)
        return false;
    mergesort(a, 0, n - 1, p);
    delete[] p;
    return true;

8. 基数排序(RadixSort)

基数排序也可以看作一种桶排序,不断的使用不同的标准对数据划分到桶中,最终实现有序。基数排序的思想是对数据选择多种基数,对每一种基数依次使用桶排序。
基数排序的步骤:以整数为例,将整数按十进制位划分,从低位到高位执行以下过程。
1. 从个位开始,根据0~9的值将数据分到10个桶桶,例如12会划分到2号桶中。
2. 将0~9的10个桶中的数据顺序放回到数组中。
重复上述过程,一直到最高位。

int getNumInPos(int num,int pos) //获得某个数字的第pos位的值
{
    int temp = 1;
    for (int i = 0; i < pos - 1; i++)
        temp *= 10;

    return (num / temp) % 10;
}

#define RADIX_10 10    //十个桶,表示每一位的十个数字
#define KEYNUM 5     //整数位数
void radix_sort(int* pDataArray, int iDataNum)
{
    int *radixArrays[RADIX_10];    //分别为0~9的序列空间
    for (int i = 0; i < RADIX_10; i++)
    {
        radixArrays[i] = new int[iDataNum];
        radixArrays[i][0] = 0;    //index为0处记录这组数据的个数
    }

    for (int pos = 1; pos <= KEYNUM; pos++)    //从个位开始到31位
    {
        for (int i = 0; i < iDataNum; i++)    //分配过程
        {
            int num = getNumInPos(pDataArray[i], pos);
            int index = ++radixArrays[num][0];
            radixArrays[num][index] = pDataArray[i];
        }

        for (int i = 0, j =0; i < RADIX_10; i++) //写回到原数组中,复位radixArrays
        {
            for (int k = 1; k <= radixArrays[i][0]; k++)
                pDataArray[j++] = radixArrays[i][k];
            radixArrays[i][0] = 0;
        }
    }
}

9. 总结

其中基于插入算法有:直接插入排序,希尔排序;
基于选择:直接选择排序,堆排序;
基于交换:冒泡排序,快速排序;
速度较快的:快速排序,归并排序,堆排序(还适用于百万数据量),希尔排序等
下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/u012617944/article/details/81138852
今日推荐