排序

本文详细介绍了各种排序算法,包括直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序以及非比较排序的计数排序和基数排序。通过原理分析和代码实现,展示了每种排序算法的工作方式和优化方法。
摘要由CSDN通过智能技术生成

1 插入排序
1.1 直接插入排序
1.1.1 原理分析
这里写图片描述
1.1.2 代码实现

//直接插入排序
void InsertSort(int *a, size_t n)
{
    assert(a);
    for (size_t i = 0; i < n - 1; i++)
    {
        int end = i;
        int tmp = a[end + 1];
        while (end >= 0)
        {
            if (a[end]>tmp)
            {
                a[end + 1] = a[end];
                end--;
            }
            else
                break;
        }
        a[end + 1] = tmp;
    }
}

测试结果:
这里写图片描述
这里写图片描述

1.2 希尔排序
1.2.1 原理分析
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1)插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
2)但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
这里写图片描述
1.2.2 代码实现

//1.gap>1,预排序
//2.gap=1,直接插入排序
void ShellSort(int *a,size_t n )
{
    assert(a);
    //预排序
    int gap = n;//增量
    while (gap >= 1)
    {
        gap = gap / 3 + 1;
        for (size_t i = 0; i < n - gap; ++i)
        {
            int end = i;
            int tmp = a[end + gap];
            while (end >= 0 && a[end]>tmp)
            {
                a[end + gap] = a[end];
                end = end - gap;
            }
            a[end + gap] = tmp;
        }
        gap--;
    }   
}

测试结果:
这里写图片描述这里写图片描述
2 选择排序
2.1 选择排序
2.1.1 原理分析
从n个元素中选出最小的放在最左边,选出最大的放在最右边,再对除过最左最右的元素进行上述操作。
这里写图片描述
2.1.2 代码实现

void SelectSort(int *a, size_t n)
{
    assert(a);
    int begin = 0;
    int end = n - 1;
    while (begin < end)
    {
        int min = begin;
        int max = end;
        for (int i = begin; i <= end; i++)
        {
            if (a[i] < a[min])
            {
                min = i;
            }
            else if (a[i]>a[max])
            {
                max = i;            
            }
        }
        swap(a[begin], a[min]);
        if (max == begin)
        {
            max = min;
        }
        swap(a[max], a[end]);
        begin++;
        end--;
    }
}

运行结果:
这里写图片描述
这里写图片描述
2.2 堆排序
2.2.1 原理分析
1)将初始待排序关键字序列(R1,R2….Rn)构建成大堆,此堆为初始的无序区
2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn)
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成
这里写图片描述
2.2.2 代码实现

void AdjustDown(int* heap, int k, int root)
{
    assert(heap);
    for (int i = root; i >= 0; i--)
    {
        int parent = i;
        int child = i * 2 + 1;
        while (child < k)
        {
            if (child+1<k&&heap[child]<heap[child + 1])
            {
                child++;
            }
            if (heap[parent]<heap[child])
            {
                swap(heap[parent], heap[child]);
                parent = child;
                child = parent * 2 + 1;
            }
            else
            {
                break;
            }
        }

    }
}
void HeapSort(int* a, int n)
{
    assert(a);
    for (int i = (n - 2) / 2; i >= 0; i--)
    {
        AdjustDown(a, n, i);
    }
    int end = n - 1;
    while (end > 0)
    {
        swap(a[0],a[end]);
        AdjustDown(a, end, 0);
        end--;
    }
}

3 交换排序
3.1 冒泡排序
3.1.1 原理分析
冒泡排序:依次比较相邻的数据,将最大的数据放在最后;即第一趟先比较第1个和第2个数,大数在后,小数在前,再比较第2个数与第3个数,大数在后,小数在前,以此类推则将最大的数”滚动”到最后一个位置;第二趟则将次大的数滚动到倒数第二个位置……第n-1(n为无序数据的个数)趟即能完成排序。
这里写图片描述
优化方法:对冒泡排序算法进行简单的优化,用一个标记来记录在一趟的比较过程中是否存在交换,如果不存在交换则整个数组已经有序退出排序过程,反之则继续进行下一趟的比较。
3.1.2 代码实现

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

运行结果:
这里写图片描述这里写图片描述

优化后的:

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

3.2 快速排序
快速排序使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
步骤:

  • 1)先从数列中取出一个数作为基准数
  • 2)分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
  • 3)再对左右区间重复第二步,直到各区间只有一个数

快速排序的三种方法:1)左右指针法;2)挖坑法;3)前后指针法
递归方法:
3.2.1 左右指针法
这里写图片描述

int PartSort1(int* a, int left, int right)
{
    assert(a != NULL);
    int start = left;
    int end = right;
    int div = a[end];
    while (start < end)
    {
        while (start < end&&a[start] <= div)
        {
            start++;
        }
        while (start < end&&a[end] >= div)
        {
            end--;
        }
        swap(a[start], a[end]);
    }
    swap(a[start], a[right]);
    return start;
}
void QuickSort(int* a, int left,int right)
{
    if (left >= right)
    {
        return;
    }
    int div = PartSort1(a, left, right);
    if (left<div-1)
        QuickSort(a, left, div - 1);
    if (div+1<right)
        QuickSort(a, div + 1, right);
}

3.2.2 挖坑法

int PartSort2(int* a, int left, int right)
{
    assert(a != NULL);
    int start = left;
    int end = right;
    int div = a[end];
    while (start < end)
    {
        while (start < end&&a[start] <= div)
        {
            start++;
        }
        if (start < end)
            a[end--] = a[start];
        while (start < end&&a[end] >= div)
        {
            end--;
        }
        if (start < end)
            a[start++] = a[end];
    }
    a[start] = div;
    return start;
}
void QuickSort(int* a, int left, int right)
{
    if (left >= right)
    {
        return;
    }
    int div = PartSort2(a, left, right);
    if (left < div - 1)
        QuickSort(a, left, div - 1);
    if (div + 1 < right)
        QuickSort(a, div + 1, right);
}

3.2.3 前后指针法

int PartSort3(int* a, int left, int right)
{
    assert(a != NULL);
    int div = a[right];
    int cur = left;
    int prev = left-1;
    while (cur < right)
    {
        if (a[cur] <= div&&++prev != cur)
            swap(a[cur], a[prev]);
        cur++;
    }
    if (a[++prev]!=div)
        swap(a[prev], a[right]);
    return prev;
}
void QuickSort(int* a, int left, int right)
{
    if (left >= right)
    {
        return;
    }
    int div = PartSort3(a, left, right);
    if (left < div - 1)
        QuickSort(a, left, div - 1);
    if (div + 1 < right)
        QuickSort(a, div + 1, right);
}

3.2.4 优化方法:三数取中法
快速排序是一种交换类的排序,它同样是分治法的经典体现。在一趟排序中将待排序的序列分割成两组,其中一部分记录的关键字均小于另一部分。然后分别对这两组继续进行排序,以使整个序列有序。在分割的过程中,枢纽值的选择至关重要,本文采取了三位取中法,可以很大程度上避免分组”一边倒”的情况。快速排序平均时间复杂度也为O(nlogn)

int GetMidIndex(int* a, int left, int right)
{
    assert(a);
    if (left >= right)
        return -1;
    int mid = left + ((right - left) >> 1);
    if (a[right] > a[left])
    {
        if (a[right] < a[mid])
        {
            return right;
        }
        else if (a[left] > a[mid])
        {
            return left;
        }
        else
        {
            return mid;
        }
    }
    else
    {
        if (a[mid] > a[left])
        {
            return left;
        }
        else if (a[right] > a[mid])
        {
            return right;
        }
        else
        {
            return mid;
        }
    }
}
int PartSort(int* a, int left, int right)
{
    assert(a != NULL);
    int div = GetMidIndex(a,left,right);
    int key = a[div];
    swap(a[div], a[right]); 
    int cur = left;
    int prev = left-1;
    while (cur < right)
    {
        if (a[cur] <= key&&++prev != cur)
        {
            swap(a[cur], a[prev]);
        }
        cur++;
    }
    swap(a[++prev], a[right]);
    return prev;
}
void QuickSort(int* a, int left, int right)
{
    if (left >= right)
    {
        return;
    }
     //当区域很小的时候,插入排序比快排递归效率高
    if (right - left <= 20)
    {
        InsertSort(a + left, right - left + 1);
    }
    int div = PartSort(a, left, right);
    if (left < div - 1)
        QuickSort(a, left, div - 1);
    if (div + 1 < right)
        QuickSort(a, div + 1, right);
}

3.2.5 非递归法

int PartSort1(int* a, int left, int right)
{
    assert(a != NULL);
    int start = left;
    int end = right;
    int div = a[end];
    while (start < end)
    {
        while (start < end&&a[start] <= div)
        {
            start++;
        }
        while (start < end&&a[end] >= div)
        {
            end--;
        }
        swap(a[start], a[end]);
    }
    swap(a[start], a[right]);
    return start;
}
void QuickSortNOR(int* a, int left, int right)
{
    stack<int> S;
    if (left < right)
    {
        S.push(right);
        S.push(left);
    }
    while (!S.empty())
    {
        int start = S.top();
        S.pop();
        int end = S.top();
        S.pop();
        int div = PartSort1(a, start, end);
        if (start < div - 1)
        {
            S.push(div-1);
            S.push(start);
        }
        if (div + 1 < end)
        {
            S.push(end);
            S.push(div + 1);
        }
    }
}

4 归并排序
4.1 原理
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。
这里写图片描述
4.2 代码实现

void _MergeSort(int* a,int left,int right,vector<int> tmp)
{
    if (left >= right)
    {
        return;
    }
    assert(a);
    int mid = left + ((right - left) >> 1);
    //划分子区间
    _MergeSort(a, left, mid,tmp);
    _MergeSort(a, mid+1,right,tmp);
    //归并
    int begin1 = left;
    int end1 = mid;
    int begin2 = mid + 1;
    int end2 = right;
    while (begin1 <= end1&&begin2 <= end2)
    {
        if (a[begin1] < a[begin2])
        {
            tmp.push_back(a[begin1]);
            begin1++;
        }
        else
        {
            tmp.push_back(a[begin2]);
            begin2++;
        }
    }
    while (begin1 <= end1)
    {
        tmp.push_back(a[begin1]);
        begin1++;
    }
    while (begin2 <= end2)
    {
        tmp.push_back(a[begin2]);
        begin2++;
    }
    int index = 0;
    while (left <= right){
        a[left++] = tmp[index++];
    }
}

5 非比较排序(计数排序和基数排序)
5.1 计数排序
5.1.1 原理
给定一组要排序的序列,找出这组序列中的最大值,然后开辟一个最大值加1大小的数组,将这个数组里面的元素全部置零,然后用这个数组统计出要排序的序列中各个元素出现的次数。等到统计完成的时候,排序就已经完成了。
这里写图片描述
算法优化:
计数排序适用于数据比较集中的序列排序。
如果是序列56 ,77,82,则开辟的数组下标56以前的空间就浪费了,基于节省空间的角度进行考虑,可以开辟更小的临时数组统计次数。
方法:找出要排序的这组元素中的最大值和最小值,这样就确定了这组元素的范围,然后开辟这个范围加1大小的数组,然后再将要排序的元素映射到这个新开辟的数组中就可以了。

5.1.2 代码实现
简单实现:

int GetMaxNum(int* a, int n)
{
    assert(a != NULL);
    int ret = a[0];
    for (int i = 1; i < n; i++)
    {
        if (a[i]>ret)
        {
            ret = a[i];
        }
    }
    return ret;
}
void Count_Sort(int* a, int n)
{
    assert(a != NULL);
    int Max = GetMaxNum(a, n);
    int * tmp = new int[Max + 1];
    memset(tmp, 0, sizeof(int)*(Max + 1));
    for (int i = 0; i < n; i++)
    {
        tmp[a[i]]++;
    }
    int index = 0;
    for (int i = 0; i < Max + 1; i++)
    {
        while (tmp[i] != 0)
        {
            a[index++] = i;
            tmp[i]--;
        }
    }
    delete tmp;
}

优化代码实现:

int GetLength(int* a, int n)
{
    assert(a != NULL);
    int ret = 0;
    int max = 0;
    int min = 0;
    for (int i = 1; i < n; i++)
    {
        if (a[i]>a[max])
        {
            max = i;
        }
        if (a[i] < a[min])
        {
            min = i;
        }
    }
    ret = a[max] - a[min]+1;
    return ret;
}
void Count_Sort(int* a, int n)
{
    assert(a != NULL);
    int len = GetLength(a, n);
    int * tmp = new int[len];
    memset(tmp, 0, sizeof(int)*len);
    for (int i = 0; i < n; i++)
    {
        tmp[a[i]]++;
    }
    int index = 0;
    for (int i = 0; i < len; i++)
    {
        while (tmp[i] != 0)
        {
            a[index++] = i;
            tmp[i]--;
        }
    }
    delete tmp;
}

5.2 基数排序
5.2.1 原理
构建一个足够大的数组Array[],数组大小需要保证能够把要排序的所有元素都包含在这个数组上 。
这里写图片描述
从上述可以看到,分别根据数据的个位,十位等进行排序,可知数据排序次数为数据的最大长度。
这里写图片描述
优化方法:
以一张二维数组的表来存储这些无序的元素,使用二维数组有一个很明显的不足就是二维数组太过稀疏。数组的利用率为 低。
可用一种压缩空间的方法,且时间复杂度并没有偏离得太厉害。设计两个辅助数组,一个是 count[],一个是 bucket[]。count 用于记录数据在 bucket[]中的的下标,然后再计算属于bucket[]的位置,并修改相应位置的 count 值。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
5.2.2 代码实现

#define N 10
int GetMaxbit(int* a, int n)//求数组中最大位数
{
    int bit = 1;
    int p = 10;
    for (int i = 0; i < n; i++)
    {
        if (a[i] >= p)
        {
            bit++;
            p = p * 10;
        }
    }
    return bit;
}
void CountSort(int* a, int n)
{
    if (a == NULL||n<1)
    {
        return;
    }
    int bit = GetMaxbit(a, n);
    int count[10];
    int tmp[N];
    int radix = 1;
    for (int i = 1; i <= bit; ++i)
    {
        for (int j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器  
        for (int j = 0; j < n; j++)
        {
            int index = (a[j] / radix) % 10;
            count[index]++;
        }
        for (int j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp中a[j]的位置

        for (int j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中  
        {
            int index = (a[j] / radix) % 10;
            tmp[count[index] - 1] = a[j];
            count[index]--;
        }
        for (int j = 0; j < n; j++) //将临时数组的内容复制到data中  
            a[j] = tmp[j];
        radix = radix * 10;
    }
}

总:排序算法性能分析

类别排序方法时间复杂度(平均情况)时间复杂度(最好情况)时间复杂度(最坏情况)空间复杂度稳定性
插入排序插入排序O(N^2)O(N)O(N^2)O(1)稳定
插入排序shell排序O(N^1.3)O(N)O(N^2)O(1)不稳定
选择排序选择排序O(N^2)O(N^2)O(N^2)O(1)不稳定
选择排序堆排序O(N* logN)O(N *logN)O(N *logN)O(1)不稳定
交换排序冒泡排序O(N^2)O(N)O(N^2)O(1)稳定
交换排序快速排序O(N* logN)O(N *logN)O(N *2)O(logN)不稳定
归并排序归并排序O(N* logN)O(N *logN)O(N *logN)O(N)稳定

计数排序:时间复杂度:O(N), 空间复杂度O(最大数-最小数)
基数排序:时间复杂度:O(N*位数),空间辅助度O(N)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值