内排序|数据结构与算法|C++

汇总:
数据结构笔记|C++

1.三个时间复杂度为O(n2)的排序算法

1.1插入排序

1.1.1直接插入排序

//插入排序
void insertSort(int* d){//递增
    int N=sizeof(d)/sizeof(int);
    for(int i=0;i<N;i++){
        for(int j=i;j<0;j--){
            if(d[j]<d[j-1]){
                swap(d[j],d[j-1]);   
                break;
          	}             
        }
    }
}
平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(n2)O(n2)初始逆序O(n)初始正序n2n2nO(1)稳定全局有序

1.2冒泡排序

使最小的元素一直上浮

//冒泡排序
void bubbleSort(int* d){
    int N=sizeof(d)/sizeof(int);
    for(int i=0;i<N;i++){
        for(int j=N-1;j>i;j++){
            if(d[j-1]>d[j])
                swap(d[j-1],d[j]);
        }
    }
}
平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(n2)O(n2)初始反序O(n)初始正序n2n2nO(1)稳定全局有序

1.3选择排序

每步从待排序的元素中选出关键字最小的元素,放到已排序的序列的最后

//选择排序
void selectSort(int* d){
    int N=sizeof(d)/sizeof(int);
    for(int i=0;i<N;i++){//待放的位置
        int minIdx=i;
        for(int j=i+1;j<N;j++){
            if(d[j]<d[minIdx]){
                minIdx=j;
            }
        }
        swap(d[i],d[minIdx]);
    }
}
平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(n2)O(n2)O(n2)n2nnO(1)不稳定全局有序
  • 简单选择排序的效率与初始数据的顺序性无关(就算正序逆序,都需要每次比较剩余全部未比较元素),每进行一趟排序归位一个元素
  • 由于每次都在选,所以不稳定,eg.
排序前:
2,4,4*3
排序后:
2,3,4*4

2.Shell排序

incr:增量
分组(组内下标差=增量)->增量/2直到1
在这里插入图片描述

//shellSort
void insertSort(int* d,int begin,int n,int incr){
    for(int i=begin;i<n;i+=incr){
        for(int j=i-incr;j>=begin;j-=incr){
            if(d[i]<d[j])
                swap(d[i],d[j]);
        }
    }
}

void shellSort(int* d,int n){
    for(int i=n/2;i>2;i/=2){
        for(int j=0;j<i;j++){
            insertSort(d,j,n-j,i);
        }
    }
    insertSort(d,0,n,1);
}
平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(n1.5)O(n2)增量序列为[2,4,8,…,22]O(n1.5)增量序列[3,6,12,…,3k]❓n2❓n2log2nO(1)不稳定

3.归并排序

算法核心:分治法
重要的两个操作:

  • 如何合并
  • 复制子序列->原序列
    在这里插入图片描述

经典实现:

//Standard implementation
void mergeSort(int* d,int* temp,int left,int right){
    if(left==right)
        return;
    int mid=(left+right)/2;
    mergeSort(d,temp,left,mid);
    mergeSort(d,temp,mid+1,right);
    for(int i=left;i<right;i++){
        temp[i]=d[i];
    }
    int i1=left;int i2=right;
    for(int curr=left;curr<right;curr++){
        
    }
}

优化实现

这个算法在复制时,把第二个子数组中的元素的顺序颠倒了一下。现在两个子数组从两端开始运行,向中间推进,使得这两个子数组的两端互为另一个数组的监视哨(sentinel)
从而不用像上面的算法那样需要检查子序列被处理完的情况


平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(nlog2n)O(nlog2n)O(nlog2n)不确定不确定log2nO(n)稳定不全局有序

特点:

  • 时间效率与待排序数据无关
  • 最好、最坏、平均时间复杂度一致

4.快速排序

由冒泡排序改进
原理:
选定基准值(privot,一般为第一个或者中间一个),将比基准值大的放到后面区间,比基准值小的放的前面区间,这个叫做一趟划分(partition)。
再对前后区间进行划分,直到区间只有一个元素为止。
注意:
如果l从i-1开始,循环条件:l<r
如果l从i开始,循环条件:l<=r
在这里插入图片描述

int partition(int* arr,int l,int r,int i,int pivot){
    while(l<=r){
        while(arr[l]<pivot)
            l++;
        while(arr[r]>=pivot)
            r--;
        swap(arr[l],arr[r]);
    }
    swap(arr[i],arr[r]);
    return r;
}

void quickSort(int* arr,int i,int j){
    if(j<=i) return;//0 or 1 element
    int pivot=arr[i];
    int l=i+1;
    int r=j;
    int k=partition(arr,l,r,i,pivot);
    quickSort(arr,i,k-1);
    quickSort(arr,k+1,j);
}
平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(nlog2n)O(n2)初始正序(每个privot轴值都未能把数组划分好)O(nlog2n)随机不确定不确定log2nO(log2n)不稳定

5.堆排序

5.1堆的实现:

template<typename E>
class heap{
private:
    E* Heap;
    int maxsize;
    int n;

    //helper function to put element in its correct place
    void siftdown(int pos){
        while(!isLeaf(pos)){
            int lc=leftChild(pos);
            int rc=rightChild(pos);
            //三值的比较
            if(rc<n&&Heap[rc]>Heap[lc])
                lc=rc;//最大的孩子赋给rc
            if(Heap[pos]>Heap[rc])return;
            swap(Heap[pos],Heap[rc]);
            pos=rc;
        }
    }
    void siftup(int pos){
        while(pos!=0&&(Heap[parent(pos)]<Heap[pos])){
            swap{Heap[parent(pos)],Heap[pos]};
            pos=parent(pos);
        }
    }
public:
    heap(E* e,int num,int max){
        Heap=h;n=num;maxsize=max;buildHeap();
    }
    int size(){return n;}
    bool isLeaf(int pos)const{
        return (pos>=n/2)&&pos<n;//(n-1)/2为leaf
    }
    int leftChild(int pos)const{
        return 2*pos+1;
    }
    int rightChild(int pos)const{
        return 2*pos+2;
    }
    int parent(int pos)const{
        return (pos-1)/2;
    }
    void buildHeap(){
        for(int i=0n/2-1;i>=0;i--)siftdown(i);
    }

    void insert(const E&it){
        assert(n<maxsize);
        Heap[n++]=it;//append to the last
        siftup(n);
    }

    E removefirst(){
        assert(n>0);
        swap(Heap[0],Heap[--n]);//第一个与最后一个交换
        if(n>0)siftdown(0);
        return Heap[n];//注意n为最后一个元素的后一个元素的下标
    }

    E remove(int pos){
        assert(pos>=0&&pos<n);
        if(pos==(n-1)) n--;//最后一个元素,无需操作,只需要减一
        else{
            swap(Heap[pos],Heap[--n]);
            siftup(pos);
            siftdown(pos);
        }
        return Heap[n];
    }
};

5.2堆排序

template<typename E>
void heapSort(E A[],int n){
    E maxval;
    heap<E> H(A,n,n);
    for(int i=0;i<n;i++){
        maxval=H.removefirst();
    }
}

5.3补充:C++中priority_queue的使用

平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区

6.计数排序

非比较排序
根据出现的次数和数组的逻辑结构进行排序

6.1原理

什么是计数排序
在这里插入图片描述

6.2实现

void countSort(int* arr,int N){
    int max=*max_element(arr,arr+N);//max_element返回数组的最大值的指针
    int min=*min_element(arr,arr+N);
    int n = max-min+1;
    int count[n]{0};//储存出现的次数,下标x为arr对应元素x,count[x]为出现次数
    int temp[N];//储存正确排序数组

    for(int i=0;i<N;i++)
        count[arr[i]]++;
    
    for(int i=1;i<N;i++)//更新count[i]为前缀和
        count[i]=count[i-1]+count[i];

    for(int i=N-1;i>=0;i--){
        temp[count[arr[i]]]=arr[i];
        count[arr[i]]--;        
    }

    for(int i=0;i<N;i++)//复制正确数组到原数组
        arr[i]=temp[i];
}

6.2分析

  • 非比较排序
  • 根据conut由后向前遍历,每输出一次count[x]-=1
  • 适用于在一定范围内整数(max与min差距不要过大)
平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(n+k)O(k)稳定

6.4应用

7.分配排序和基数排序

分配排序

平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区

基数排序

LSD法:从低位到高位依次排序
在这里插入图片描述

基数排序描述

int getMax(int* a,int n){
    int idx=0;
    int max=a[0];
    for(int i=0;i<n;i++){
        if(max<a[i])
            max=a[i];
    }
    return max;
}

int getIndex(int a,int exp){
    return a/exp;
}

int countSort_LSD(int* a,int n,int exp){
    int temp[n];
    int max=a[0],min=a[0];
    int N=max-min+1;
    int count[9]{};//每一位只有九个桶
    
    for(int i=0;i<n;i++){//count[i]=检索位数exp为i的数字个数
        count[getIndex(a[i],exp)]++;
    }

    for(int i=1;i<9;i++){//count[i]=前缀和
        count[i]=count[i]+count[i-1];
    }

    for(int i=n-1;i>=0;i--){//temp:排好序的数组,count[]--,每输出一个,对应位置减一个
        temp[count[getIndex(a[i],exp)]--]=a[i];
    }

    for(int i=0;i<n;i++){
        a[i]=temp[i];
    }
}

void radix_sort(int* a,int n){
    int max=getMax(a,n);

    for(int exp=1;max/exp>0;exp*=10){
        countSort_LSD(a,n,exp);
    }
}
平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
O(d*n)O(d*n)O(d*n)dO(n+k)稳定不一定
  • d:最大位数,n:排序个数,k:桶的个数
  • 稳定条件:从后向前输出
  • 非比较排序

8.对各种排序算法的实验比较

总结

算法名称平均最坏最坏条件最好最好条件比较次数移动次数排序趟数空间复杂度稳定性有序区
插入
冒泡
选择
Shell
归并
快排
分配
基数

8.排序问题的下限

8.1

O ( n l o g n ) O(nlogn) O(nlogn)

8.2说明

8.2.1上限和下限问题

  • 一个问题的上限定义为已知算法中速度最快的渐进时间代价
  • 下限为解决这个问题所有算法的最佳可能效率,包括那些未设计出来的算法

8.2.2估计问题下限的一种简单方法

  • 计算必须读入的输入长度及必须写出的输出长度。任何算法的时间代价当然都不可能小于它的I/O时间

8.2.3排序问题的下限

  • 没有任何一种基于关键码比较的排序算法可以把最差执行时间降低到O(nlogn)以下

8.3证明

证明:在最差情况下,任何一种基于比较的算法都需要Ω(nlogn)的时间代价

8.3.1判定树(decision tree)

一棵可以模拟任何判定程序的二叉树
在这里插入图片描述
高度:h
比较元素个数:n
叶结点:n!(如果是完全二叉树,最多 2 h 2^h 2h)(二叉树第i层最多有 2 i 2^i 2i个结点,i=0,1,2,…)

8.3.2证明

2 h ≥ n ! h ≥ l o g 2 n ! ≥ l o g ( n e ) n = n l o g n − n l o g e ≈ n l o g n 2^h \geq n! \\ h \geq log_2 n! \geq log( \frac{n}{e})^n\\ = nlogn-nloge \approx nlogn 2hn!hlog2n!log(en)n=nlognnlogenlogn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值