常见排序算法的实现(归并排序、快速排序、堆排序、选择排序、插入排序、希尔排序)

这篇博客主要实现一些常见的排序算法。例如:

//冒泡排序

//选择排序

//简单插入排序

//折半插入排序

//希尔排序

//归并排序

//双向的快速排序

//单向的快速排序

//堆排序

对于各个算法的实现原理,这里不再多说了,代码中注释较多,结合注释应该都能理解算法的原理,读者也可自己google一下。另外,注释中有很多点,比如边界条件、应用场景等已经用 * 标记,* 越多,越应该多注意。

下面是实现:

//冒泡排序
void BubbleSort(int *arr, int n)
{
    if(NULL == arr || n < 2)
        return ;

    for(int i = 0; i < n; ++i)  //i 趟数
    {
        for(int j = 0; j < n-i-1; ++j)  //j 第i趟需要比较的次数, 一定是从头开始比较,因为最后一个元素已经有序
        {
            if(arr[j] > arr[j+1])   //*
            {
                std::swap(arr[j], arr[j+1]);    //如果前面的一个元素较大,就交换
            }
        }
    }
}
//选择排序
void SelectSort(int *arr, int n)
{
    if(NULL == arr || n < 2)
        return ;

    for(int i = 0; i < n; ++i)      //每次从当前位置往后找一个最小的值,放在当前位置
    {
        int minIndex = i;           //找最小值时用来记录下标
        for(int j = i; j < n; ++j)  //从i位置开始往后 找到一个最小值
        {
            if(arr[j] < arr[minIndex])
            {
                minIndex = j;   //找到比arr[i]小的时候,记录最小值下标
            }
        }
        if(minIndex != i)
        {
            std::swap(arr[i], arr[minIndex]);   //把最小值放到i位置
        }
    }
}
//简单插入排序
void SimpleInsertSort(int *arr, int n)
{
    if(NULL == arr || n < 2)
        return ;

    int i = 0, j = 0;
    int save = 0;
    for(i = 1; i < n; ++i)  //默认第一个元素已经有序,从第二个元素开始,把每个元素插入到前面有序的合适位置
    {
        save = arr[i];      //保存当前的值,如果移动元素,可能会被覆盖
        for(j = i-1; j >= 0; j--)
        {
            if(arr[j] < save)
                break;
            arr[j+1] = arr[j];  //移动元素
        }
        if(j+1 != i)
            arr[j+1] = save;    //在合适位置放上之前保存的数
    }
}
//折半插入排序
void BinaryInsertSort(int *arr, int n)
{
    if(NULL == arr || n < 2)
        return ;

    int i = 0, j = 0;
    int save = 0;
    for(i = 1; i < n; ++i)
    {
        save = arr[i];
        int low = 0, high = i - 1;
        int mid = 0;
        while(low <= high)  //找一个位置 放要插入的数, 循环结束后,low > high
        {                   //折半插入比简单插入的优点就在这里,能很快找到要插入的位置,减少了比较次数
            mid = low + (high-low)/2;
            if(arr[mid] < save)
                low = mid + 1;
            //else if(arr[mid] > save)
            else
                high = mid - 1;
        }
        for(j = i; j > low; --j) //移动元素,arr[low]是比save大的第一个数字,拷贝到这个数为止,并不能减少移动元素的次数
        {
            arr[j] = arr[j-1];
        }
        arr[j] = save;  //arr[low]放上save
    }
}
//希尔排序
//严蔚敏版本,实现的不太好,里面自己指定了 步长(用step数组存储,一定要保证数组最后一个元素为1, 原因下面有解释)
//void ShellInsert(int *arr, int n, int gap)    //这个函数说白了就是插入排序,只不过是把插入排序中的步长1换成了gap
//{
//  assert(arr);
//
//  int i = 0, j = 0;
//  int save = 0;
//  for(i = 0+gap; i < n; i+=gap)   //每隔一个步长的所有数做一次插入排序
//  {
//      save = arr[i];
//      for(j = i-gap; j >= 0; j-=gap)  //找个合适的位置放待插入的数
//      {
//          if(arr[j] < save)
//              break;
//          arr[j+gap] = arr[j];
//      }
//      if(j+gap != i)
//          arr[j+gap] = save;  //放数
//  }
//}
//
//void ShellSort(int *arr, int n, int *step, int t) //step里面存放的是每次希尔排序的步(t是step的长度), step一定是降序排列的,最后一个步长一定为1
//{
//  if(NULL == arr || NULL == step)
//      return ;
//
//  for(int i = 0; i < t; ++i)
//  {
//      ShellInsert(arr, n, step[i]);   //每次找一个步长进行插入排序
//  }
//}

void ShellSort(int *arr, int len)
{
    assert(arr && len>0);

    int gap = len;    //gap是每次希尔插入的步长
    while(gap > 1)
    {
        gap = gap/3 + 1; //最后加1能保证gap的最后一个值一定是1,因为之前gap大于1的过程都是为最后一个简单插入做准备(称为预处理)

        int cur = gap;
        for(cur = gap; cur < len; ++cur)  //下面就是简单插入排序,只不过插入排序的步长为gap
        {
            int tmp = arr[cur];
            int findIndex = cur - gap;
            while(findIndex >= 0  && arr[findIndex] > tmp)
            {
                arr[findIndex+gap] = arr[findIndex];
                findIndex -= gap;
            }
            arr[findIndex+gap] = tmp;
        }
    }
}
//归并排序
void Merge(int *arr, int begin, int mid, int end)   // 把arr中的 [begin, mid]、 [mid+1, end] 两个有序片段排成一个有序序列
{
    assert(arr);

    int *brr = new int[end+1-begin];    //临时数组,用来保存临时有序序列
    int i = 0, j = 0;       //两个有序片段的指针
    int k = 0;
    for(i = begin, j = mid+1; i<=mid && j<=end; )
    {
        if(arr[i] <= arr[j])    //如果两个数相等,默认先放前面一段的数i指向的片段,这也保证了归并排序是稳定的
            brr[k++] = arr[i++];
        else
            brr[k++] = arr[j++];
    }

    while(i <= mid)
        brr[k++] = arr[i++];    //如果子数组还有剩余元素没有插入到临时数组中,直接拷贝全部元素到临时数组中
    while(j <= end)
        brr[k++] = arr[j++];

    for(i = begin; i <= end; ++i)   //转移临时数组到原数组中 ***
        arr[i] = brr[i-begin];

    delete []brr;   //释放临时数组
}
//双向的快速排序
int partition(int *arr, int begin, int end) //[begin, end] ***
{
    int key = arr[begin];  //枢轴默认取第一个元素
    int save = arr[begin];

    int left = begin, right = end;
    while(left < right)
    {
        while(left<right && arr[right] >= key)
            --right;
        arr[left] = arr[right];

        while(left<right && arr[left] <= key)
            ++left;
        arr[right] = arr[left];
    }
    arr[left] = save;   //left是枢轴元素所在处,left前面的元素都比arr[left]小,后面的元素都比arr[left]大

    return left;
}

//双向的快速排序,需要前后指针往中间遍历
void QuickSort_TwoWay(int *arr, int begin, int end)     // [begin, end]
{
    if(NULL == arr || begin >= end)
        return ;

    if(begin < end)
    {
        int partiIndex = partition(arr, begin, end);
        QuickSort_TwoWay(arr, begin, partiIndex-1);     // [begin, partiIndex-1]
        QuickSort_TwoWay(arr, partiIndex+1, end);       // [partiIndex+1, end]
    }
}
//单向的快速排序,只需要一个指针从前往后扫描, 该方法特别适合链表的排序 ******
void QuickSort_OneWay(int *arr, int begin, int end)     // [begin, end]
{
    if(NULL == arr || begin >= end)
        return ;

    int index = begin+1;        //往后找比key小的数******
    int key = arr[begin];       //相当于枢轴
    int mid = begin;            //相当于partition,用于标记左右有序的分界******
    for(index=begin+1 ; index <= end; ++index)
    {
        if(arr[index] < key)    //找小
        {
            if(++mid != index)  //防止自己跟自己交换
                std::swap(arr[index], arr[mid]);
        }
    }
    std::swap(arr[begin], arr[mid]);    //mid位置处放入枢轴

    QuickSort_OneWay(arr, begin, mid-1);    //递归排序左半部分
    QuickSort_OneWay(arr, mid+1, end);      //递归排序左半部分
}
//堆排序 方法1:常规情况
void AdjustDown(int *arr, int len, int root) //调整以root为根的子树满足堆的特点(这里实现的是大堆, 下面还有优化)
{
    int parent = root;
    int child = 2*root + 1;   //默认root的左子树比右子树大

    while(child < len)
    {
        if(child+1 < len && arr[child+1] > arr[child])  //如果右子树大,调整child
            ++child;
        if(arr[parent] < arr[child]) //如果子树比根节点大,交换
        {
            std::swap(arr[parent], arr[child]);
            parent = child;          //交换完成后,以child为根的子树可能不满足堆的特点,需要向下重新调整(AdjustDown)
            child = 2*parent + 1;
        }
        else
            break;
    }
}

void HeapSort(int *arr, int len)
{
    assert(arr && len>0);

    for(int root = len/2-1; root >= 0; --root)  //建堆   len/2 - 1是第一个非叶子节点的下标
    {
        AdjustDown(arr, len, root);  //从第一个非叶子节点一直调整到根节点(下标为0)
    }

    for(int i = 0; i < len-1; ++i)
    {
        std::swap(arr[0], arr[len-1-i]);  //根节点是最大的元素,根节点和最后一个元素交换,最大元素处于最后
        AdjustDown(arr, len-1-i, 0);      //重新调整树满足堆,但是节点个数要减1(因为最后一个元素已经有序)
    }
}

//堆排序 方法2:使用仿函数+模板函数
template<class T>
class Great
{
    public:
        bool operator() (const T& left, const T& right)  //对象重载了(), 可以像函数一样使用,例如: great(3, 1) 返回true
        {
            return left > right;
        }
};

template<class T>
class Less
{
    public:
        bool operator() (const T& left, const T& right)
        {
            return left < right;
        }
};

//***
//该方法实现为模板函数,通过给函数传进一个Great或Less的对象,从而动态实现大堆或小堆
template<class Compare>
void AdjustDown(int *arr, int len, int root, Compare com) //用法 AdjustDown(arr, len, root, Great<int>())
{
    int parent = root;
    int child = 2*root + 1;
    while(child < len)
    {
        if(child+1 < len && com(arr[child+1], arr[child]))
            ++child;
        if(com(arr[child], arr[parent]))
        {
            std::swap(arr[parent], arr[child]);
            parent = child;
            child = 2*parent + 1;
        }
        else
            break;
    }
}

//堆排序 方法3:使用仿函数+模板类
template<class T, template<typename T> class Compare = Less >
class Heap
{
    public:
        Heap(T *arr, int sz)
            :_arr(arr)
            ,_size(sz)
        {
            //建堆
            for(int root = _size/2 - 1; root >= 0; --root)
                AdjustDown(_size, root);
        }

        ~Heap()
        {
            //神马都不用做
        }

        //功能同上面的AdjustDown
        void AdjustDown(int len, int root)
        {
            Compare<T> com;

            int parent = root;
            int child = 2*parent + 1;

            while(child < len)
            {
                if(child+1 < _size && com(_arr[child+1], _arr[child]))
                    ++child;
                if(com(_arr[child], _arr[parent]))
                {
                    std::swap(_arr[parent], _arr[child]);
                    parent = child;
                    child = 2*parent + 1;
                }
                else
                    break;
            }
        }

        void PrintArray()
        {
            for(int i = 0; i < _size; ++i)
                cout<<_arr[i]<<" ";
            cout<<endl;
        }

    //protected:
    public:
        T *_arr;
        int  _size;
};

template<class T>
void HeapSort(T *arr, int len)
{
    Heap<T, Great> hp(arr, len);
    for(int i = 0; i < len; ++i)
    {
        std::swap(hp._arr[0], hp._arr[len-1-i]);
        hp.AdjustDown(len-1-i, 0);
    }
}   

常见的排序就是这么多了。。。

下面附上一张各种排序的时间复杂度,空间复杂度,以及稳定性的比较:

时间: 2024-10-06 21:49:39

常见排序算法的实现(归并排序、快速排序、堆排序、选择排序、插入排序、希尔排序)的相关文章

探讨排序算法的实现

排序算法是我们工作中使用最普遍的算法,常见的语言库中基本都会有排序算法的实现,比如c标准库的qsort,stl的sort函数等.本文首先介绍直接插入排序,归并排序,堆排序,快速排序和基数排序等比较排序算法,然后介绍计数排序,基数排序等具有线性时间的排序算法.本文主要讨论算法的实现方法,并不会过多介绍基本理论. 评价一个排序算法优劣适用与否,一般需要从三个方面来分析 时间复杂度.用比较操作和移动操作数的最高次项表示,由于在实际应用中最在乎的是运行时间的上限,所以一般取输入最坏情况的下的运行时间作为

七种排序算法的实现和总结

最近把七种排序算法集中在一起写了一遍. 注释里有比较详细的说明. 1 /*排序算法大集合**/ 2 #include <stdio.h> 3 #include <string.h> 4 #include <stdlib.h> 5 6 //------------------快速排序------------------// 7 /* 8 核心: 9 如果你知道多少人该站你前面,多少人站你后面,你一定知道你该站哪个位置. 10 算法: 11 1.选取分界数,参考这个分界数,

Python学习(三) 八大排序算法的实现(下)

本文Python实现了插入排序.基数排序.希尔排序.冒泡排序.高速排序.直接选择排序.堆排序.归并排序的后面四种. 上篇:Python学习(三) 八大排序算法的实现(上) 1.高速排序 描写叙述 通过一趟排序将要排序的数据切割成独立的两部分,当中一部分的全部数据都比另外一部分的全部数据都要小,然后再按此方法对这两部分数据分别进行高速排序,整个排序过程能够递归进行,以此达到整个数据变成有序序列. 1.先从数列中取出一个数作为基准数. 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全

Python 排序算法的实现

冒泡排序: 1 def bubble(l): 2 length = len(l) 3 for i in range(length): 4 for j in range(i+1, length): 5 if l[i] > l[j]: 6 l[i], l[j] = l[j], l[i] 7 print l 选择排序: 1 def select(l): 2 length = len(l) 3 for i in range(length): 4 minn = i 5 for j in range(i+1

排序 之 冒泡排序 简单选择排序 直接插入排序 希尔排序

排序的基本概念 假设含有n个记录的序列为{r1,r2,--,rn},其相应的关键字分别为{k1,k2,--,kn},需确定1,2,--,n的一种排序p1,p2,--,pn,使其相应的关键字满足kp1≤kp2≤--≤kpn非递减(或非递增)关系,即使得序列称为一个按关键字有序的序列{rp1,rp2,--,rpn},这样的操作就称为排序. 排序的稳定性 假设ki=kj(1≤i≤n,1≤j≤n,i≠j),且在排序前的序列中ri领先于rj(即i<j).如果排序后ri仍领先于rj,则称所用的排序方法是稳定

插入排序) 希尔排序 (最小增量排序)

/** * (插入排序) 希尔排序 (最小增量排序) * @author Cinn * */public class shellSort { /**     * @param args     */    public static void main(String[] args) {        // TODO Auto-generated method stub        int[] array= {48,58,50,98,69,51,27,99,100};        shlees

8大排序算法---我熟知3(归并排序/快速排序/堆排序)

排序算法: 快排: o(nlogn) o(1)不稳定 归并:o(nlogn) o(n) 稳定 基数: 冒泡 睡眠 面条 烙饼 1.quicksort: 返回条件:start >=end private = a[start]+a[end]/2 while(left <= right) while(left <= right && a[left] < privot) while(left <= right && a[right] > priv

软考笔记第六天之各排序算法的实现

对于前面的排序算法,用c#来实现 直接插入排序: 每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序.第一趟比较前两个数,然后把第二个数按大小插入到有序表中: 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中:依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程.直接插入排序属于稳定的排序,最坏时间复杂性为O(n^2),空间复杂度为O(1).直接插入排序是由两层嵌套循环组成的.外层循环标识并决定待比较的数值.内层循环为待比较数值确定其最终位

各种排序算法的实现、总结

排序算法 比较排序 2.非比较排序 计数排序.基数排序 排序算法实现 假定序列array[10]={73,22,93,43,55,14,28,65,39,81}  (以升序为例) 直接插入排序 直接插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕. 1 void Insertsort(int* array,size_t n) 2 { 3 assert(array); 4 for (size_t i = 0; i < n-1; i++) 5 { 6 in