九大排序算法及其实现- 插入.冒泡.选择.归并.快速.堆排序.计数.基数.桶排序

    闲着的时候看到一篇“九大排序算法在总结”,瞬间觉得之前数据结构其实都有学过,但当初大多数都只是老师随口带过,并没有仔细研究一下。遂觉:这是欠下的账,现在该还了。

    排序按照空间分类:

    In-place sort不占用额外内存或占用常数的内存 插入排序、选择排序、冒泡排序、堆排序、快速排序。

    Out-place sort:归并排序、计数排序、基数排序、桶排序。

  或者按照稳定性分类:

    stable sort:插入排序、冒泡排序、归并排序、计数排序、基数排序、桶排序。

    unstable sort:选择排序(5 8 5 2 9)、快速排序、堆排序。

  针对以上九种算法,我都根据时空复杂性,还有实现的思路做了简要介绍。并且进行了简单的测试。希望能给同样学习排序算法的同学一点帮助。

#ifndef __INCLUDE_MY_SORT__
#define __INCLUDE_MY_SORT__
#include <iostream>
#include <cstdio>
#include <vector>
#define INF 0x3f3f3f3f

using namespace std;
/*
    插入排序:
        时间复杂度 最坏情况(数组逆序): O(n*n)
                   最好情况(数组有序): O(n)
        空间复杂度 线性空间
*/
namespace Insert_sort{
    template<class T>
    void sort(T *a, int len){
        int i, j;
        for( i = 0; i < len; i++){
            j = i - 1;
            T key = a[i];
            while(j >= 0 && a[j] > key){
                a[j + 1] = a[j];
                j --;
            }
            a[j + 1] = key;
        }
    }
}

/*
    冒泡排序:
        时间复杂度 最坏情况   O(n*n)
                   最好情况   O(n*n)
        空间复杂度 线性空间
        思路:每次将待排序的值(0 : len - i - 1)按照大小尽可能放到最右边
              第一个循环是冒泡的轮数,第二个循环是冒泡的范围区域(未有序的区域)因为经过一次循环最大的一定在最右边,2次循环次大的一定在倒数第二个以此类推。
              这样就保证了,下一次只需要在未有序的范围内进行冒泡,算法是完备并且正确的。
*/
namespace Bubble_sort{
    template<class T>
    void sort(T *a, int len)
    {
        int i, j;
        for(i = 0; i < len; i ++)
        {
            for(j = 0; j < len - 1 - i; j ++){
                if(a[j] > a[j + 1])
                    swap(a[j],a[j + 1]);
            }
        }
    }
}

/*
    选择排序:
        时间复杂度 最坏情况  O(n*n)
                   最好情况  O(n*n)
        空间复杂度 线性空间
        思路:     每次选出最小的放到当前最走边
*/
namespace Selection_sort{
    template <class T>
    void sort(T *a, int len)
    {
        int i , j, min_val = a[0], min_pos = 0;
        for(i = 0; i < len; i ++)
        {
            min_val = a[i], min_pos = i;
            for(j = i; j < len; j ++)
            {
                if(min_val > a[j]){
                    min_val = a[j];
                    min_pos = j;
                }
            }
            swap(a[i],a[min_pos]);
        }
    }
}

/*
    归并排序:
        时间复杂度 最坏情况  O(nlgn)
                   最好情况  O(nlgn)
        空间复杂度 线性空间
        思路 :分治的思想 Divide(划分子问题)、Conquer(子问题求解)、Combine(将子解合并成原问题的解)。
               不断递推分解,将大区间每次从中分段,直到左右区间只剩下一个元素,进行求解并合并左右两个子区间,使其有序。
               不断递推返回,直到合并到最大的区间。
*/
namespace Merge_sort{
    template<class T>
    void merge(T *a, int p, int m, int q)
    {
        //printf("Merge: p = %d, m = %d, q = %d\n",p,m,q);
        int l = m - p + 1, r = q - m;
        T *L = (T*)malloc((l + 1) * sizeof(T));
        T *R = (T*)malloc((r + 1) * sizeof(T));
        memcpy(L, a + p, l * sizeof(T));
        memcpy(R, a + m + 1,r * sizeof(T));
        L[l] = INF;
        R[r] = INF;
        int i = 0,j = 0, k;
        for(k = p; k <= q; k ++)
        {
            if(L[i] < R[j]){
                a[k] = L[i];
                i ++;
            }
            else{
                a[k] = R[j];
                j ++;
            }
        }
        free(L);
        free(R);
    }

    template<class T>
    void divide(T *a, int p, int q)
    {
        if(p < q)
        {
            int m = (p + q) >>1;
            //cout<<"  p = "<<p<<" m = "<<m<<" q = "<<q<<endl;
            divide(a, p, m);
            divide(a, m + 1, q);
            merge(a, p, m, q);
        }
    }

    template<class T>
    void sort(T *a, int len)
    {
        divide(a, 0, len - 1);
    }
}

/*
    快速排序:
        时间复杂度 最坏情况(数组有序,每次Partition都划分成1 | n - 1,一共需要划分n次,每个partition的复杂度也是o(n)) :  O(n*n)
                   最好情况 :  O(nlgn)
        空间复杂度 线性空间
        思路 :分治的思想 Divide(划分子问题)、Conquer(子问题求解)、Combine(将子解合并成原问题的解)。
               不断以r = partition的位置换分小区间,这样让r左边区间都小于r,r右边区间都大于r。
*/
namespace Quick_sort{
    template <class T>
    int partition(T *a, int p, int q)
    {
        int rand_index = rand() % (q - p + 1) + p;//随机选择key值避免退化n*n复杂度
        if(rand_index < p || rand_index > q)
            rand_index = p;
        swap(a[p], a[rand_index]) ;
        int i = p,j = q,key = a[p];
        while(i < j){
            while(i < j && a[j] >= key) j --;
            swap(a[j], a[i]);
            while(i < j && a[i] <= key) i ++;
            swap(a[i], a[j]);
        }
        return i;
    }

    template <class T>
    void recursive_qsort(T *a, int p, int q)
    {
        if(p < q){
            int r = partition(a, p, q);
            recursive_qsort(a, p, r);
            recursive_qsort(a, r+1, q);
        }
    }

    template <class T>
    void sort(T *a, int len)
    {
        recursive_qsort(a, 0, len - 1);
    }
}

/*
    堆排序:
        时间复杂度 最坏情况  O(nlgn)
                   最好情况  O(nlgn)
        空间复杂度 线性空间
        算法动态示意图: https://en.wikipedia.org/wiki/Heapsort
        思路 :step 1 建立大顶堆(同一父节点的两个孩子之间的大小关系,不用纠结,只需要保证parent > max(lchild, rchild))
               step 2 排序,将大顶堆的第一个元素(最大)与最后一个元素(最小 or 次小 or 次次小 ...)交换位置,再次调整heap,使maximum到堆顶。
               repeat step 1.
*/

namespace Heap_sort{
    template <class T>
    void heap_adjust(T *a, int i, int len)
    {
        T tmp = a[i];
        int lchild = i * 2 + 1, rchild = i * 2 + 2, largest = i;
        if(rchild < len){
            if(a[largest] < a[rchild])
                largest = rchild;
        }
        if(lchild < len){
            if(a[largest] < a[lchild])
                largest = lchild;
        }
        if(largest != i){
            swap(a[largest], a[i]);
            heap_adjust(a, largest, len);
        }
    }

    template <class T>
    void build_max_heap(T *a, int len)
    {
        for(int i = len / 2 - 1; i >= 0; i --)
        {
            heap_adjust(a, i, len);
        }
    }

    template <class T>
    void sort(T *a, int len)
    {
        build_max_heap(a, len);
        int heap_num = len;
        for(int i = len - 1; i >= 1; i --)
        {
            swap(a[0],a[i]);
            heap_num --;//已经有序的元素不在参与堆排序
            heap_adjust(a, 0, heap_num);
        }
    }
}

/*
    计数排序:
        时间复杂度 最坏情况  O(n+k)
                   最好情况  O(n+k)
        空间复杂度 线性空间
        思路 :非比较排序,用空间换时间,适用于固定范围的且元素较小的数组排序。
               对于val ai, 比它小的元素有cnt[ai]个,那么ai一定放在cnt[ai] - 1 (下标从0开始)这个位置。
*/

namespace Counting_sort{
    const int Max_val= 10000, Max_len = 10000;
    template <class T>
    void sort(T *a, int len)
    {
        int i, j, cnt[Max_val + 1];
        T *rank = (T*)malloc(len * sizeof(T));
        for(i = 0; i <= Max_val; i ++)
            cnt[i] = 0;
        for(i = 0; i < len; i ++)
            cnt[a[i]] ++;
        for(i = 0; i < Max_val; i ++)
            cnt[i + 1] += cnt[i];
        for(i = 0; i < len; i ++)
        {
            rank[ --cnt[a[i]]] = a[i];//val ai 可能有多个所以下一个 ai 的位置应该会靠前1位
        }
        memcpy(a, rank, len * sizeof(T));
        free(rank);
    }
}

/*
    基数排序:
        时间复杂度 O(d(n+radix)) (设待排序列为n个记录,d个关键码,关键码的取值范围为radix)
        空间复杂度 线性空间
        思路 :非比较排序,用空间换时间。
               按照位数进行排序,从第0位开始,使用桶辅助排序,类似计数排序的思想,数个数,就是把对应位相同的num放到一起,最后按照0 - 9的优先级从新排序数组。
               一共重复最大数的位数次。
*/
namespace Radix_sort{

    template <class T>
    T get_max_val(T *a, int len)
    {
        int i;
        T max_val = a[0];
        for(i = 0; i < len; i ++)
        {
            if(max_val < a[i])
                max_val = a[i];
        }
        return max_val;
    }

    template <class T>
    int compute_dig_num(T max_val)
    {
        int dig_num = 1, test_val = 9, radix = 10;
        while(test_val < max_val)
        {
            dig_num ++;
            radix *= 10;
            test_val =  radix - 1;
        }
        return dig_num;
    }

    template <class T>
    void sort(T *a, int len)
    {
        if(len <= 0) return ;
        T max_val = get_max_val(a, len);
        int dig_num = compute_dig_num(max_val), i, j, k, cnt = 0;
        vector<T>bucket[10];
        int radix = 1;
        for( i = 0; i < dig_num; i ++)
        {
            for( j = 0; j < 10; j ++)
                bucket[j].clear();
            for( j = 0; j < len; j ++)
            {
                int dig = (a[j] / radix) %10;
                bucket[dig].push_back(a[j]);
            }
            cnt = 0;
            for( j = 0; j < 10; j ++)
            {
                for(k = 0; k < bucket[j].size(); k ++)
                {
                    a[cnt ++] = bucket[j][k];
                }
            }
            radix *= 10;
        }
    }
}

/*
    桶排序:
        时间复杂度 最优情况 O(n))
                   最坏情况 O(nlgn)
        空间复杂度 线性空间
        思路 :非比较排序,用空间换时间。
               最坏情况运行时间:当分布不均匀时,全部元素都分到一个桶中,则O(n^2),
               这里实现的是整数排序,正常的话桶排序的数据范围是[0,1)。主要体现的是桶排序的思想。
               桶内排序可以使用插入 堆 或者快速排序。这样最坏情况就是O(nlgn)。
*/
namespace Bucket_sort{
    const int Max_val = 91000;
    template <class T>
    void sort(T *a, int len)
    {
        int i, j ;
        T cnt[Max_val];
        for(i = 0; i < Max_val; i ++) cnt[i] = 0;
        for(i = 0; i < len; i ++)
        {
            if(a[i] > Max_val)
                return ;
            cnt[a[i]]++;
        }
        j = 0;
        for(i = 0; i < Max_val; i ++)
        {
            while(cnt[i]){
                a[j++] = i;
                cnt[i] --;
            }

        }
    }
}
#endif//__INCLUDE_MY_SORT__

╮(╯▽╰)╭ 论文还没看完 又开始瞎捣鼓了

时间: 2024-12-18 03:12:44

九大排序算法及其实现- 插入.冒泡.选择.归并.快速.堆排序.计数.基数.桶排序的相关文章

八大内部排序算法(上)-冒泡、直接插入、简单选择、快速

八大内部排序算法(上)冒泡.直接插入.简单选择.快速 排序分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 1.直接插入排序 将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表.即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止. 要点:设立哨兵,作为临时存储和判断数组边界之用. 直接插入实现如下:

十大排序算法之(三)——选择排序

#1,选择排序简介 选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完. 选择排序是不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面),我个人认为,按照数值来讲,这两个5没差别,所以这是不是决定这个算法不稳定还有待商榷. #2,c++实现算法 #include<iostream>#include&l

排序算法合集(冒泡,选择,插入,堆排,快排)

1.冒泡排序 最初在学c语言时,老师就教的这个排序算法,原理比较简单:从数组下标为0处开始遍历,相邻之间进行比较,若a[i]>a[i+1],则exchange(a[i],a[i+1]),当然也可以将小的往后传递,将此过程不断进行,那么最后数组就有序了. 要点:(1)每遍历一遍,末尾就得到一个最大值(或最小值),那么接下来的遍历是不是每次都减少一个元素就好了,因为后边的已经排好序了啊. (2)遍历n-1遍就排好序了,因为最后一遍只剩一个元素了,它一定放那儿,所以最后一遍就不用遍历了. 当然如果数据

冒泡 选择 插入 希尔 堆 归并 快速 排序算法

排序相关概念 排序:对一序列对象根据某个关键字进行排序: 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面: 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面: 内排序:所有排序操作都在内存中完成: 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行: 排序耗时的操作:比较.移动: 排序分类: (1)交换类:冒泡排序.快速排序:此类的特点是通过不断的比较和交换进行排序: (2)插入类:简单插入排序.希尔排序:此类的特点是通过插入的

常见排序算法详解(冒泡、选择、插入、快速、希尔、归并)

一.排序算法 1.冒泡排序(Bubble Sort) 定义:是一种简单的排序算法.它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端. 原理: 比较相邻的元素.如果第一个比第二个大(升序),就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,最后的元素会是最大的数. 针对所有的元素重复以上的

排序算法(交换法,选择法,插入排序,冒泡法,快速排序算法,C语言举例)

交换法:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动. 简单选择排序:的基本思想:第1趟,在待排序记录r[1]~r[n]中选出最小的记录,将它与r[1]交换;第2趟,在待排序记录r[2]~r[n]中选出最小的记录,将它与r[2]交换;以此类推,第i趟在待排序记录r[i]~r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕. 插入排序法:有一个已经有序的

排序算法(三)冒泡、选择排序的Python实现及算法优化详解

说在前面 最近一年太忙,博客长草了.近日用Python实现了常用排序算法,供大家参考. Java版本排序算法及优化,请看以前的文章. <排序算法之简单排序(冒泡.选择.插入)> <排序算法(二)堆排序> 1.排序概念 这里不再赘述,请参看前面2篇文章 2.简单排序之冒泡法Python实现及优化 原理图 2.1.基本实现 num_list = [     [1, 9, 8, 5, 6, 7, 4, 3, 2],     [1, 2, 3, 4, 5, 6, 7, 8, 9] ] nu

各种排序算法总结篇(高速/堆/希尔/归并)

1.高速排序 交换排序有:冒泡(选择)排序和高速排序,冒泡和选择排序的时间复杂度太高,思想非常easy临时不讨论.高速排序基于一种分治的思想,逐步地使得序列有序. #include <iostream> #include <conio.h> using namespace std; int arrs[] = { 23, 65, 12, 3, 8, 76, 345, 90, 21, 75, 34, 61 }; int arrLen = sizeof(arrs) / sizeof(ar

CUDA(六). 从并行排序方法理解并行化思维——冒泡、归并、双调排序的GPU实现

在第五讲中我们学习了GPU三个重要的基础并行算法: Reduce, Scan 和 Histogram,分析了 其作用与串并行实现方法. 在第六讲中,本文以冒泡排序 Bubble Sort.归并排序 Merge Sort 和排序网络中的双调排序 Bitonic Sort 为例, 讲解如何从数据结构课上学的串行并行排序方法转换到并行排序,并附GPU实现代码. 在并行方法中,我们将考虑到并行方法需要注意的特点进行设计,希望大家在读完本文后对GPU上并行算法的设计有一些粗浅的认识.需注意的特点如下: 1