十大排序算法及其实现(C++ & Python)

作者大树先生
博客http://blog.csdn.net/koala_tree
知乎https://www.zhihu.com/people/dashuxiansheng
GitHubhttps://github.com/KoalaTree
2018 年 4 月 16 日

经典的几大排序算法,网上各种版本代码质量层次不齐。在此想自己做个总结,一方面希望通过这次总结加深自己对几种排序算法的认识和记忆,另一方面也希望能写下来与大家分享。

每个算法力求给出普通解法和最优解法,当前部分排序算法还没有给出最优解,待后续的更新和补充。

排序算法说明

1. 算法优劣说明

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;

时间复杂度: 一个算法执行所耗费的时间;
空间复杂度: 运行完一个程序所需内存的大小。

2. 排序算法总结

排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
冒泡排序 O(n2) O ( n 2 ) O(n) O ( n ) O(n2) O ( n 2 ) O(1) O ( 1 ) 稳定
插入排序 O(n2) O ( n 2 ) O(n) O ( n ) O(n2) O ( n 2 ) O(1) O ( 1 ) 稳定
shell排序 O(n1.3) O ( n 1.3 ) O(n) O ( n ) O(n2) O ( n 2 ) O(1) O ( 1 ) 不稳定
选择排序 O(n2) O ( n 2 ) O(n2) O ( n 2 ) O(n2) O ( n 2 ) O(1) O ( 1 ) 不稳定
快速排序 O(nlogn) O ( n log ⁡ n ) O(nlogn) O ( n log ⁡ n ) O(n2) O ( n 2 ) O(logn) O ( log ⁡ n ) 不稳定
归并排序 O(nlogn) O ( n log ⁡ n ) O(nlogn) O ( n log ⁡ n ) O(nlogn) O ( n log ⁡ n ) O(1) O ( 1 ) 稳定
堆排序 O(nlogn) O ( n log ⁡ n ) O(nlogn) O ( n log ⁡ n ) O(nlogn) O ( n log ⁡ n ) O(1) O ( 1 ) 不稳定
计数排序 O(n+k) O ( n + k ) O(n+k) O ( n + k ) O(n+k) O ( n + k ) O(k) O ( k ) 稳定
桶排序 O(n+k) O ( n + k ) O(n+k) O ( n + k ) O(n2) O ( n 2 ) O(n+k) O ( n + k ) 稳定
基数排序 O(n×k) O ( n × k ) O(n×k) O ( n × k ) O(n×k) O ( n × k ) O(n+k) O ( n + k ) 稳定
  • n – 数据规模
  • k –“桶”的个数

一、冒泡排序(Bubble Sort)

平均时间复杂度: O(n2) O ( n 2 )
空间复杂度: O(1) O ( 1 )

1. 算法描述

  • (1) 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • (2) 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • (3) 针对所有的元素重复以上的步骤,除了最后一个;
  • (4) 重复步骤1~3,直到排序完成。

2. C++实现

2-1:基本实现

void BubbleSort(int arr[], int n)
{
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

2-2:升级版

  • 加入isSwap标志,如果某次比较没有发生交换,即说明了已经有序,后面就无须进行遍历了。
void BubbleSort(int arr[], int n)
{
    for (int i = 0; i < n - 1; i++) {
        bool isSwap = false; 
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                isSwap = true;
            }
        }

        if(!isSwap) return;
    }
}

2-3:升级版++

  • 在升级版本的基础上再增添情况:已经遍历出部分有序的序列后,那部分也不用进行遍历,也就是说:发生交换的地方之后的地方不用遍历。
  • 使用current 和 last 来分别记录当前交换位置和最后一次交换位置。
void BubbleSort(int arr[], int len){
    int i,temp;
    //记录位置,当前所在位置current和最后发生交换的地方last
    int current,last = len - 1;
    while(last > 0) {
        current = 0;
        for(i = 0;i < last;++i){
            if(arr[i] > arr[i+1]){
                temp = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = temp;
                //记录当前的位置,如果没有发生交换current值即for循环初始化的0
                current = i;
            }
        }
        //若current = 0即已经没有可以交换的元素了,即已经有序了
        last = current;
    }
}

3. python实现

# 冒泡排序
def bubble_sort(lists):
    count = len(lists)
    for i in range(0, count):
        for j in range(i + 1, count):
            if lists[i] > lists[j]:
                lists[i], lists[j] = lists[j], lists[i]
    return lists

4. 动图

《十大排序算法及其实现(C++ & Python)》

二、插入排序(Insertion Sort)

插入排序的基本原理通俗的来讲就是扑克牌原理,按照从大或者从小的顺序进行排序。

平均时间复杂度: O(n2) O ( n 2 )
空间复杂度: O(1) O ( 1 )

1. 算法描述

一般来说,插入排序都采用 in-place 在数组上实现。

  • (1) 从第一个元素开始,该元素可以认为已经被排序;
  • (2) 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • (3) 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • (4) 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • (5) 将新元素插入到该位置后;
  • (6) 重复步骤2~5。

2. C++实现

void InsertSort(int arr[],int n){
    for (int i =1;i <= n;++i){
        for(int j = i;j > 0;--j){
            if(arr[j] < arr[j -1]){
                int temp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = temp;
            }
        }
    }
}

3. python实现

def Insert_sort(lists):
    for i in range(1, len(lists)):
        key = lists[i]
        j = i - 1
        while j>=0 and lists[j]>key:
            lists[j+1] = lists[j]
            j = j - 1
        lists[j+1] = key
    return lists

4. 动图

《十大排序算法及其实现(C++ & Python)》

三、希尔排序(Shell Sort)

希尔排序是插入排序的一种更高效的改进版本。
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

平均时间复杂度: O(n1.3) O ( n 1.3 )
空间复杂度: O(1) O ( 1 )

1. 算法描述

  • (1) 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • (2) 按增量序列个数k,对序列进行k 趟排序;
  • (3) 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

2. C++实现

void ShellSort(int array[]){
    int index = sizeof(array)/2;
    int temp=0;
    while(index>=1){
        for(int i=index;i<length;i++){
            for(int j=i-index;j>=0;j-=index){
                if(array[j]>array[j+index]){
                    temp = array[j];
                    array[j] = array[j+index];
                    array[j+index]=temp;
                }
            }
        }
        index = index/2;
    }

}

3. python实现

def shell_sort(list):
    n = len(list)
    # 初始步长
    gap = round(n / 2)
    while gap > 0:
        for i in range(gap, n):
            # 每个步长进行插入排序
            temp = list[i]
            j = i
            # 插入排序
            while j >= gap and list[j - gap] > temp:
                list[j] = list[j - gap]
                j -= gap
            list[j] = temp
        # 得到新的步长
        gap = round(gap / 2)
    return list

4. 动图

《十大排序算法及其实现(C++ & Python)》

四、选择排序(Selection Sort)

平均时间复杂度: O(n2) O ( n 2 )
空间复杂度: O(1) O ( 1 )

1. 算法描述

  • (1) 初始状态:无序区为R[1..n],有序区为空;
  • (2) 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • (3) n-1趟结束,数组有序化了。

2. C++实现

void SelectSort(int a[],int n)  
{  
    for(int i=0; i<n-1; i++)  
    {  
        int index=i;  //无序区的第一个元素
        for(int j=i+1; j<n; j++)  
            if(a[j]<a[index]) //寻找无序区内的最小值 
                index=j;

        if(index!=i) //把找到的最小值放到无序区的最前面 
        {  
            int tmp=a[index];  
            a[index]=a[i];  
            a[i]=tmp;  
        }  
    }  
}  

3. python实现

def selection_sort(list):
    n=len(list)
    for i in range (0,n):
        min = i
        for j in range(i+1,n):
            if list[j]<list[min]:
                min=j

        if min != i:
            list[min], list[i] = list[i], list[min]
    return list

4. 动图

《十大排序算法及其实现(C++ & Python)》

五、快速排序(Quick Sort)

平均时间复杂度: O(nlogn) O ( n log ⁡ n )
空间复杂度: O(logn) O ( log ⁡ n )

1. 算法描述

  • (1) 从数列中挑出一个元素,称为 “基准”(pivot);
  • (2) 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • (3) 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

2. C++实现

void swap(int* data, int i, int j){
    if(i == j) return;
    int temp = data[i];
    data[i] = data[j];
    data[j] = temp;
}

// 分区操作
int partition(int data[], int length, int start, int end){
    if(data == nullptr || length <= 0 || start < 0 || end >= length)
        throw new std::exception("Invalid Parameter");

    int index = rand() % (end - start + 1) + start; // 随机选择基准
    swap(data, index, end);
    int small = start - 1;
    for(index = start; index < end; ++index){
        if(data[index] < data[end]){
            ++small;
            if(small != index)
                swap(data, small, index);
        }
    }

    ++small;
    swap(data, small, end);

    return small;
}


void quickSort(int data[], int length, int start, int end){
    if(start == end) return;

    int index = partition(data, length, start, end);
    if(index > start)
        quickSort(data, length, start, index-1);
    if(index < end)
        quickSort(data, length, index+1, end);
}

3. python实现

def quick_sort(list):
    less = []
    pivotList = []
    more = []

    # 递归出口
    if len(list) <= 1:
        return list
    else:
        # 将第一个值做为基准
        pivot = list[0]
        for i in list:
            # 比基准小的值放到less数列
            if i < pivot:
                less.append(i)
            # 比基准大的值放到more数列
            elif i > pivot:
                more.append(i)
            # 将和基准相同的值保存在基准数列
            else:
                pivotList.append(i)
        # 对less数列和more数列继续进行排序
        less = quick_sort(less)
        more = quick_sort(more)
        return less + pivotList + more

4. 动图

《十大排序算法及其实现(C++ & Python)》

六、归并排序(Merge Sort)

平均时间复杂度: O(nlogn) O ( n log ⁡ n )
空间复杂度: O(n) O ( n )

归并排序是利用分治的思想去实现元素的排序。

  • 分治法:将原问题分解为几个规模较小,但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。
  • 归并排序将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序,最后进行合并。

1. 算法描述

  • (1) 把长度为n的输入序列分成两个长度为n/2的子序列;
  • (2) 对这两个子序列分别采用归并排序;
  • (3) 将两个排序好的子序列合并成一个最终的排序序列。

2. C++实现

void Merge(int arr[], int reg[], int start, int end) {
    if (start >= end)return;
    int len = end - start, mid = (len >> 1) + start;

    //分成两部分
    int start1 = start, end1 = mid;
    int start2 = mid + 1, end2 = end;
    //然后合并
    Merge(arr, reg, start1, end1);
    Merge(arr, reg, start2, end2);


    int k = start;
    //两个序列一一比较,哪的序列的元素小就放进reg序列里面,然后位置+1再与另一个序列原来位置的元素比较
    //如此反复,可以把两个有序的序列合并成一个有序的序列
    while (start1 <= end1 && start2 <= end2)
        reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];

    //然后这里是分情况,如果arr2序列的已经全部都放进reg序列了然后跳出了循环
    //那就表示arr序列还有更大的元素(一个或多个)没有放进reg序列,所以这一步就是接着放
    while (start1 <= end1)
        reg[k++] = arr[start1++];

    //这一步和上面一样
    while (start2 <= end2)
        reg[k++] = arr[start2++];
    //把已经有序的reg序列放回arr序列中
    for (k = start; k <= end; k++)
        arr[k] = reg[k];
}

void MergeSort(int arr[], const int len) {
    //创建一个同样长度的序列,用于临时存放
    int  reg[len];
    Merge(arr, reg, 0, len - 1);
}

3. python实现

  • 在合并的时候,判断L和R是否为空,若其中之一如L为空,则将另外一个如R中剩余的部分添加到A的末尾.
def Merge(Left, Right):
    i, j = 0, 0
    results = []
    while i < len(Left) and j < len(Right): # 注意这里的循环判断条件,即为不添加哨兵的方式
        if Left[i] <= Right[j]:
            results.append(Left[i]) # error处,注意
            i += 1
        else:
            results.append(Right[j])
            j += 1
    results += Left[i:] # 这里使用Left[i:]不会报溢出错误的原因是,list切片超出范围为空:[]
    results += Right[j:]
    return results


def Merge_sort(lists):
    if len(lists) <= 1:
        return lists
    mid = len(lists) // 2
    left = Merge_sort(lists[:mid])
    right = Merge_sort(lists[mid:])
    return Merge(left, right)

4. 动图

《十大排序算法及其实现(C++ & Python)》

七、堆排序(Heap Sort)

平均时间复杂度: O(nlogn) O ( n log ⁡ n )
空间复杂度: O(nlogn) O ( n log ⁡ n )

1. 算法描述

  • (1) 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • (2) 将堆顶元素R1与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • (3) 由于交换后新的堆顶R1可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R1与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

2. C++实现

//堆排序
void HeapSort(int arr[],int len){
    int i;
    //初始化堆,从最后一个父节点开始
    for(i = len/2 - 1; i >= 0; --i){
        Heapify(arr,i,len);
    }
    //从堆中的取出最大的元素再调整堆
    for(i = len - 1;i > 0;--i){
        int temp = arr[i];
        arr[i] = arr[0];
        arr[0] = temp;
        //调整成堆
        Heapify(arr,0,i);
    }
}

void Heapify(int arr[], int first, int end){
    int father = first;
    int son = father * 2 + 1;
    while(son < end){
        if(son + 1 < end && arr[son] < arr[son+1]) ++son;
        //如果父节点大于子节点则表示调整完毕
        if(arr[father] > arr[son]) break;
        else {
         //不然就交换父节点和子节点的元素
            int temp = arr[father];
            arr[father] = arr[son];
            arr[son] = temp;
            //父和子节点变成下一个要比较的位置
            father = son;
            son = 2 * father + 1;
        }
    }
}

3. python实现


def heap_sort(list):
    # 创建最大堆
    for start in range((len(list) - 2) // 2, -1, -1):
        sift_down(list, start, len(list) - 1)

    # 堆排序
    for end in range(len(list) - 1, 0, -1):
        list[0], list[end] = list[end], list[0]
        sift_down(list, 0, end - 1)
    return list

# 最大堆调整
def sift_down(lst, start, end):
    root = start
    while True:
        child = 2 * root + 1
        if child > end:
            break
        if child + 1 <= end and lst[child] < lst[child + 1]:
            child += 1
        if lst[root] < lst[child]:
            lst[root], lst[child] = lst[child], lst[root]
            root = child
        else:
            break

4. 动图

《十大排序算法及其实现(C++ & Python)》

八、计数排序(Counting Sort)

平均时间复杂度: O(n+k) O ( n + k )
空间复杂度: O(k) O ( k )

1. 算法描述

  • (1) 找出待排序的数组中最大和最小的元素;
  • (2) 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  • (3) 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  • (4) 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

2. C++实现

//计数排序
int* countSort(int* A,int k,int n) //[0,k)范围内n个数
{
    int* tmp = new int[k];
    int* s = new int[n];
    memset(tmp, 0, sizeof(int) * k);
    for (int i = 0; i < n; i++) //原始数组中的计数
        tmp[A[i]]++;
    for (int i = 0; i < k - 1; i++) //记录不大于该数的数字个数
        tmp[i + 1] += tmp[i];
    for (int i = n - 1; i >= 0; i--) //逆序输出
        s[--tmp[A[i]]] = A[i]; //计数哈希数组-1即为应当对应的秩,用原数组的数赋值
    delete[] tmp;
    return s;
}

3. python实现

def count_sort(list):
    min = 2147483647
    max = 0
    # 取得最大值和最小值
    for x in list:
        if x < min:
            min = x
        if x > max:
            max = x
    # 创建数组C
    count = [0] * (max - min +1)
    for index in list:
        count[index - min] += 1
    index = 0
    # 填值
    for a in range(max - min+1):
        for c in range(count[a]):
            list[index] = a + min
            index += 1
    return list

4. 动图

《十大排序算法及其实现(C++ & Python)》

九、桶排序(Bucket Sort)

平均时间复杂度: O(n+k) O ( n + k )
空间复杂度: O(n+k) O ( n + k )

1. 算法描述

  • (1) 设置一个定量的数组当作空桶;
  • (2) 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  • (3) 对每个不是空的桶进行排序;
  • (4) 从不是空的桶里把排好序的数据拼接起来。

2. C++实现

void bucketSort(vector<int>& vec)  
{  
    int length=vec.size();  
    vector<int> buckets(length,0);//准备一堆桶,容器的下标即待排序数组的键值或键值经过转化后的值 
    //此时每个桶中都是没有放值的,所以都是0 

    for(int i=0;i<length;++i)  
    {  
        buckets[vec[i]]++;//把每个值放入到对应的桶中 
    }  

    int index=0;  
    for(int i=0;i<length;++i)  
    {//把值取出,空桶则直接跳过 
        for(int j=0;j<buckets[i];j++)  
        {  
            vec[index++]=i;  
        }  
    }  
}  

3. python实现

def bucket(lst):
    buckets = [0] * ((max(lst) - min(lst))+1)
    for i in range(len(lst)):
        buckets[lst[i]-min(lst)] += 1
    res=[]
    for i in range(len(buckets)):
        if buckets[i] != 0:
            res += [i+min(lst)]*buckets[i]
    return res

4. 动图

《十大排序算法及其实现(C++ & Python)》

十、基数排序(Radix Sort)

平均时间复杂度: O(n×k) O ( n × k )
空间复杂度: O(n+k) O ( n + k )

1. 算法描述

  • (1) 取得数组中的最大数,并取得位数;
  • (2) arr为原始数组,从最低位开始取每个位组成radix数组;
  • (3) 对radix进行计数排序(利用计数排序适用于小范围数的特点)。

2. C++实现

void countSort(vector<int>& vec,int exp)  
{//计数排序 
    vector<int> range(10,0);  

    int length=vec.size();  
    vector<int> tmpVec(length,0);  

    for(int i=0;i<length;++i)  
    {  
        range[(vec[i]/exp)%10]++;  
    }  

    for(int i=1;i<range.size();++i)  
    {  
        range[i]+=range[i-1];//统计本应该出现的位置 
    }  

    for(int i=length-1;i>=0;--i)  
    {  
        tmpVec[range[(vec[i]/exp)%10]-1]=vec[i];  
        range[(vec[i]/exp)%10]--;  
    }  
    vec=tmpVec;  
}  

void radixSort(vector<int>& vec)  
{  
    int length=vec.size();  
    int max=-1;  
    for(int i=0;i<length;++i)  
    {//提取出最大值 
        if(vec[i]>max)  
            max=vec[i];  
    }  

    //提取每一位并进行比较,位数不足的高位补0 
    for(int exp=1;max/exp>0;exp*=10)  
        countSort(vec,exp);  
}  

3. python实现

import math
def radix_sort(lists, radix=10):
    k = int(math.ceil(math.log(max(lists), radix)))
    bucket = [[] for i in range(radix)]
    for i in range(1, k+1):
        for j in lists:
            bucket[j/(radix**(i-1)) % (radix**i)].append(j)
        del lists[:]
        for z in bucket:
            lists += z
            del z[:]
    return lists

4. 动图

《十大排序算法及其实现(C++ & Python)》

  • 以上图片来自维基百科及网友总结。
点赞