7种常用排序算法的实现示例

其实写排序算法的博客已经有很多了,其中不乏某些细心的博主去仔细讲解各种排序的过程,甚至有使用gif图来表现排序过程的博客,还有对已有排序算法进行改进的,我表示很佩服这些博主,谢谢你们。

这里附上一些我参考过的博客:
7种排序算法(系列博客) - 静默虚空
常用排序算法总结(一) - SteveWang
[直观学习排序算法] 视觉直观感受若干常用排序算法 - todayx
白话经典算法系列 - MoreWindows
常用排序算法稳定性、时间复杂度分析 - jiuyueguang
八大排序算法


然后附上我重新写的排序算法

这里的排序算法示例都用函数模板来写

  • 简单排序算法:
    • 选择排序
    • 冒泡排序
    • 插入排序
  • 复杂排序算法:
    • 快速排序
    • 归并排序
    • 堆排序
    • shell排序

选择排序

  • 原理:遍历元素集合,每次遍历找到剩下的集合中最大\最小的元素放入已排序集合中,直到找完为止。
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 算法稳定性:不稳定排序。使用序列6 9 6 3 2来举例,第一个6与3交换,导致第一个6排到了第二个6后面,所以选择排序是不稳定的排序算法。
  • 算法示例
template <class T>
void sort_array_select(T* dataArray, int dataSize)
{
    //遍历数据集合
    for (int i = 0; i < dataSize; i++)
    {
        //记录最小索引
        int minIndex = i;
        //遍历剩余数据集合
        for (int j = i; j < dataSize; j++)
        {
            //查找更小的值
            if (dataArray[minIndex] > dataArray[j])
            {   
                //保存更小值的索引
                minIndex = j;
            }
        }
        //判断当前索引处是否是最小值
        if (minIndex != i)
        {
            //将找到的最小值与当前索引处的值交换
            T temp = dataArray[i];
            dataArray[i] = dataArray[minIndex];
            dataArray[minIndex] = temp;
        }
    }
}

冒泡排序

  • 原理:遍历元素集合,依次比较相邻元素,将相邻元素中较大\较小者移向一端,每次遍历找到剩余数据集合中较大\较小者,直到全部排序完成。
  • 时间复杂度
    • 最佳(已经顺序排好的集合):O(n)
    • 最差(已经逆序拍好的集合):O(n^2)
  • 空间复杂度:O(1)
  • 算法稳定性:稳定的排序。因为比较与交换均发生在相邻的元素之间,对于两个相等的元素不会进行交换,所以是稳定的排序。
  • 算法示例
template <class T>
void sort_array_bubble(T* dataArray, int dataSize)
{
    //遍历集合
    for (int i = 0; i < dataSize; i++)
    {
        //遍历剩余元素集合
        for (int j = 0; j < dataSize - i - 1; j++)
        {
            //比较相邻元素大小
            if(dataArray[j] > dataArray[j + 1])
            {
                //将较大元素后移
                T temp = dataArray[j];
                dataArray[j] = dataArray[j + 1];
                dataArray[j + 1] = temp;
            }
        }
    }
}

插入排序

  • 原理:将数据集合中第一个数据视为已排序集合,依次获取未排序集合中的元素,将获取到的元素插入到已排序集合中的正确位置,直到全部排序完成。
  • 时间复杂度
    • 最佳(已排序集合):O(n)
    • 最差(逆序已排序集合):O(n^2)
  • 空间复杂度:O(1)
  • 算法稳定性:稳定的排序算法。因为比较的过程发生在相邻元素之间,对于相等的元素,算法中不会改变他们的相对位置,所以是稳定的排序算法。
  • 算法示例
template <class T>
void sort_array_insert(T* dataArray, int dataSize)
{
    //遍历数据集合(从1开始,0号元素已排序)
    for (int i = 1; i < dataSize; i++)
    {
        //获取未排序集合中第一个元素
        T temp = dataArray[i];
        int j = i;
        //依次与已排序集合中元素比较,找到正确位置
        while(j > 0 && temp < dataArray[j - 1])
        {
            dataArray[j] = dataArray[j - 1];
            j--;
        }
        //取到的元素放入已排序列表中正确位置
        dataArray[j] = temp;
    }
}

快速排序

  • 原理:应用了分治的思想和以递归取代循环的思想。取一个元素作为flag,并将数据集合分为大于(等于)flag和小于(等于)flag两个子集,然后对子集进行同样的操作,直到子集元素个数为1或0,则所有元素完成排序。
  • 时间复杂度
    • 最差(每次取到的flag都在边界):O(n^2)
    • 最佳(每次取到的flag都在中间):O(nlog2n)
  • 空间复杂度:O(1)
  • 算法稳定性:不稳定的排序。因为比较和替换不是发生在相邻元素之间,而是从某个方向开始找到满足条件的值,然后进行替换,这样可能导致两个相同元素的相对位置变化,所以是不稳定的排序方式。
  • 算法示例
template <class T>
void sort_array_quick(T* dataArray, int left, int right)
{
    //递归退出条件
    if (left >= right)
    {
        return;
    }
    //取flag,并控制左右范围
    T flag = dataArray[left];
    int sub_left = left;
    int sub_right = right;
    //根据flag来整理数据集合
    while(sub_left < sub_right)
    {
        //在右侧找小的值换到左侧
        //此时dataArray[sub_left]中的值是冗余的
        while (sub_left < sub_right && dataArray[sub_right] >= flag)
        {
            sub_right--;
        }
        if (sub_left < sub_right)
        {
            dataArray[sub_left] = dataArray[sub_right];
        }
        //在左侧找大的值换到右侧
        //此时dataArray[sub_right]中的值是冗余的
        while (sub_left < sub_right && dataArray[sub_left] <= flag)
        {
            sub_left++;
        }
        if (sub_left < sub_right)
        {
            dataArray[sub_right] = dataArray[sub_left];
        }
    }
    //上面的步骤进行完成后,dataArray[sub_left]中的值是冗余的,这里将flag放回
    dataArray[sub_left] = flag;
    //以flag为中心,左侧的值小于等于flag,右侧的值大于等于flag
    //分别对左侧的值的集合和右侧的值的集合进行递归再次排序划分
    sort_array_quick(dataArray, left, sub_left - 1);
    sort_array_quick(dataArray, sub_left + 1, right);
}

归并排序

  • 原理:应用了分治的思想和以递归取代循环的思想。将待排序数据集合划分为两个子集,对子集分别进行排序,排序完成后将两个有序子集中的元素。
  • 时间复杂度:O(nlog2n)
  • 空间复杂度:O(n)
  • 算法稳定性:稳定的排序算法。在元素集合被拆分为n个子集合之后,合并集合时,是通过对已排序集合中值最相近的两个元素进行比较并存储的,所以不会造成值相同的元素相对位置变化。
  • 算法示例
//按顺序合并集合
template <class T>
void array_merge(T* dataArray, int left, int mid, int right, T* sortedArray)
{
    int i = left;
    int j = mid + 1;
    int count = 0;
    
    //将dataArray中left->mid和mid+1->right部分的元素按顺序放入sortedArray中
    while (i <= mid && j <= right)
    {
        if (dataArray[i] < dataArray[j])
        {
            sortedArray[count++] = dataArray[i++];
        }
        else
        {
            sortedArray[count++] = dataArray[j++];
        }
    }
    
    //剩余元素直接放入sortedArray
    while (i <= mid)
    {
        sortedArray[count++] = dataArray[i++];
    }
    while (j <= right)
    {
        sortedArray[count++] = dataArray[j++];
    }
    
    //排序好的元素放回dataArray
    for (int i = 0; i < count; i++)
    {
        dataArray[left + i] = sortedArray[i];
    }
}

//拆分集合
template <class T>
void sort_array_merge(T* dataArray, int left, int right, T* sortedArray)
{
    //递归停止条件
    if (left >= right)
    {
        return;
    }
    
    //集合分为两个子集
    int mid = (left + right) / 2;
    //继续拆分
    sort_array_merge(dataArray, left, mid, sortedArray);
    sort_array_merge(dataArray, mid + 1, right, sortedArray);
    
    //按顺序合并集合
    array_merge(dataArray, left, mid, right, sortedArray);
}

堆排序

  • 原理:应用了二叉堆的特点,即父节点的值总是大于(小于)子节点的值。这样每一次将待排序集合调整为堆时,便能得到待排序集合中的一个最值。堆排序分为两步:第一步是建立堆,将无序的集合调整为满足堆的条件的集合;第二步是依次取得最值,此时只破坏了堆顶,以堆顶为根进行一次调整,形成一个新的堆,然后循环第二步。
  • 时间复杂度:O(nlog2n)
  • 空间复杂度:O(1)
  • 算法稳定性:不稳定的排序算法。因为比较与交换不是发生在相邻元素之间,两个相同的元素相邻时会被分配到不同的子树中,在调整子树时可能导致值相同的元素的相对位置发生变化。
  • 算法示例
//调整为最大堆,保证父节点值大于子节点
template <class T>
void heap_update(T* dataArray, int rootIndex, int arraySize)
{
    //递归终止条件,rootIndex处应为非叶子节点
    if (rootIndex >= arraySize / 2)
    {
        return;
    }
    
    //计算左右子节点的index
    int left_child = rootIndex * 2 + 1;
    int right_child = rootIndex * 2 + 2;
    
    //查找父、左子、右子节点中最大值
    int largest = rootIndex;
    
    if (left_child < arraySize && dataArray[left_child] > dataArray[largest])
    {
        largest = left_child;
    }
    if (right_child < arraySize && dataArray[right_child] > dataArray[largest])
    {
        largest = right_child;
    }
    //将最大值替换到父节点位置
    if (largest != rootIndex)
    {
        T temp = dataArray[rootIndex];
        dataArray[rootIndex] = dataArray[largest];
        dataArray[largest] = temp;
        
        //largest所处位置元素相对其子节点来说,又是一个被破坏的堆顶,所以继续调整
        heap_update(dataArray, largest, arraySize);
    }
    
    //对左右子节点分别进行调整
    //heap_update(dataArray, left_child, arraySize);
    //heap_update(dataArray, right_child, arraySize);
}

//建立堆。即逆序对所有非叶子节点进行一次堆调整。
template <class T>
void heap_build(T* dataArray, int arraySize)
{
    for (int i = arraySize / 2 - 1; i >= 0; i--)
    {
        heap_update(dataArray, i, arraySize);
    }
}

//堆排序
template <class T>
void sort_array_heap(T* dataArray, int arraySize)
{
    //建立堆
    heap_build(dataArray, arraySize);
    
    //循环获得堆顶元素并调整堆
    int count = arraySize;
    while (count > 1)
    {
        //将堆顶元素与待排序数组末尾元素交换
        T temp = dataArray[0];
        dataArray[0] = dataArray[count - 1];
        dataArray[count - 1] = temp;
        
        //调整堆,只破坏了堆顶,这里以堆顶为root,对待排序的部分进行堆调整
        count--;
        heap_update(dataArray, 0, count);
    }
}

shell排序

  • 原理:对直接插入法排序的改良。因为直接插入法排序在元素基本有序的情况下效率最高,所以将待排序元素依次划分为n组(n为size/2,size/4,... 首先保持元素数量最少,组内排序完成后再重新划分为元素更多的组,保持直接插入法的高效),然后对组内进行直接插入法排序。
  • 时间复杂度
    • 最差:O(n^2)
    • 最佳(有序排列的集合):O(nlog2n)
  • 空间复杂度:O(1)
  • 算法示例
template <class T>
void sort_array_shell(T* dataArray, int arraySize)
{
    //使用step划分组
    for (int step = arraySize / 2; step > 0; step /= 2)
    {
        //逐个元素进行组内插入排序
        for (int i = step; i < arraySize; i++)
        {
            //组内直接插入排序
            T temp = dataArray[i];
            int k = i - step;
            //在组内依次向前查找正确位置
            while (k >= 0 && dataArray[k] > temp)
            {
                dataArray[k + step] = dataArray[k];
                k -= step;
            }
            //元素插入到正确位置
            dataArray[k + step] = temp;
        }
    }
}

上面所有的算法示例在排序一个int类型的数组时,是正常可用的。但是很多都有优化的空间(比如看到一篇博客中对插入法排序写了多种实现方法),而且使用临时变量来交换两个值的过程也值得思考。

总结:以上排序算法只是提供一种思想,在我们面临遍历大量数据、从大量数据中查找某个值等问题的时候,其中的某些点是可以借鉴的。其中的分段、构建二叉树的思想是很值得学习的,以此告诫自己思维不要太刻板。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 7种常用的排序算法总结 2016.04.30PoetryAlgorithm 排序算法:一种能将一串数据依照特定的排...
    raining_804f阅读 783评论 0 0
  • 总结一下常见的排序算法。 排序分内排序和外排序。内排序:指在排序期间数据对象全部存放在内存的排序。外排序:指在排序...
    jiangliang阅读 1,323评论 0 1
  • 贪心算法 贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上...
    fredal阅读 9,195评论 3 52
  • // 排序算法编程实践 #include using namespace std; // 冒泡排序 void Bu...
    该倒闭了阅读 667评论 0 50
  • 如何是好 细雨是 暗暗担忧的春 趴在我肩头啜泣 那夏日多么危险 有时雷声太响 云又不肯白 命运 每个人 都是被掷出...
    易华安阅读 239评论 0 1