十大排序算法及其实现(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) O(n2) O(1) 稳定
插入排序 O(n2) O(n) O(n2) O(1) 稳定
shell排序 O(n1.3) O(n) O(n2) O(1) 不稳定
选择排序 O(n2) O(n2) O(n2) O(1) 不稳定
快速排序 O(nlogn) O(nlogn) O(n2) O(logn) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(1) 稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
计数排序 O(n+k) O(n+k) O(n+k) O(k) 稳定
桶排序 O(n+k) O(n+k) O(n2) O(n+k) 稳定
基数排序 O(n×k) O(n×k) O(n×k) O(n+k) 稳定
  • n – 数据规模
  • k –“桶”的个数

一、冒泡排序(Bubble Sort)

平均时间复杂度: O(n2)
空间复杂度: 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. 动图

还在路上,稍等...

扫描二维码关注公众号,回复: 170040 查看本文章

二、插入排序(Insertion Sort)

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

平均时间复杂度: O(n2)
空间复杂度: 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. 动图

还在路上,稍等...


三、希尔排序(Shell Sort)

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

平均时间复杂度: O(n1.3)
空间复杂度: 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. 动图

还在路上,稍等...


四、选择排序(Selection Sort)

平均时间复杂度: O(n2)
空间复杂度: 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. 动图

还在路上,稍等...


五、快速排序(Quick Sort)

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

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. 动图

还在路上,稍等...


六、归并排序(Merge Sort)

平均时间复杂度: O(nlogn)
空间复杂度: 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. 动图

还在路上,稍等...


七、堆排序(Heap Sort)

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

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. 动图

还在路上,稍等...


八、计数排序(Counting Sort)

平均时间复杂度: O(n+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. 动图

还在路上,稍等...


九、桶排序(Bucket Sort)

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

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 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. 动图

还在路上,稍等...


十、基数排序(Radix Sort)

平均时间复杂度: 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. 动图

还在路上,稍等...


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

猜你喜欢

转载自blog.csdn.net/koala_tree/article/details/79958965