七大排序算法及其代码实现C++

(主要为了准备面试,把排序算法再总结实现一下)
在这里插入图片描述
图片引用自https://www.cnblogs.com/onepixel/articles/7674659.html,侵删

比较类排序

通过比较来决定元素间的相对次序,时间复杂度不能突破O(nlogn)。

一、交换排序

1、冒泡排序

  • 比较相邻元素,若第一个比第二个大,则交换位置。
  • 对每一队相邻元素作相同的工作,直至当前最大元素放在当前最靠后的位置
  • 重复1,2步骤,直至排序完成(第二小的元素被放到第二个位置)
//冒泡排序
//一次冒泡,对nums的前n个元素进行一次冒泡操作,将当前最大值放在最后面,即nums[n-1]的位置
void Bubble(vector<int>& nums,int n)
{
    for(int i=0;i<n-1;i++)
    {
        if(nums[i]>nums[i+1])
        {
            int temp = nums[i];
            nums[i] = nums[i+1];
            nums[i+1] = temp;
        }
    }
}

//先进行n = l的一次冒泡,直至n = 2的冒泡
void BubbleSort(vector<int>& nums) {
    int l = nums.size();
    for(int i=l;i>1;i--)
    {
        Bubble(nums,i);
    }
}

//函数调用
vector<int> sortArray(vector<int>& nums) {
    BubbleSort(nums);
    return nums;
}

2、快速排序

算法描述:

  • 从序列中选择一个轴点元素(位置0)
  • 利用轴点将序列分割成两个子序列
    将小于轴点的元素放在轴点前面,大于轴点的元素放在轴点后面,等于轴点的元素两边都可以
  • 对子序列进行1,2操作,直至子序列只剩下一个元素

本质:将每个元素都转换为轴点元素

复杂度分析:

若轴点元素左右分布极不均衡,则时间复杂度为O(N^2),为了防止这种情况,随机选取轴点

//快速排序

//对[begin,end)范围进行快速排序
void QuickSort(vector<int>& nums,int begin,int end){
    if(end-begin<2)
        return ;
    //确定轴点位置,并进行nums的重建操作
    int mid = pivotIndex(nums,begin,end);

    //对子序列进行快速排序
    QuickSort(nums,begin,mid);
    QuickSort(nums,mid+1,end);
}

//返回轴点元素的最终位置,并将大于pivot的元素放在右边,小于pivot的元素放在左边。
int pivotIndex(vector<int>& nums,int begin,int end){
    int pivot = nums[begin];

    //end指向最后一个元素
    end--;

    //reverse表示是否逆序(从后往前)
    //先从后往前遍历,若nums[end]<pivot,则需要将其移到begin处,并将begin后移一位,然后就该从前往后开始遍历。若nums[end]>=pivot,不需要移动end处元素,并将end前移一位,继续从后往前遍历。
    //从前往后遍历时,若nums[begin]>=pivot,则需要将其移到end处,并将end前移一位,然后就该从后往前开始遍历。若nums[begin]<pivot,不需要移动begin处元素,并将begin后移一位,继续从前往后遍历。
    //这样操作是将等于pivot的元素移动到pivot右侧,也可以移到左侧。
    
    bool reverse = true;

    //将nums[begin:end]的元素大于pivot的放在右侧,小于pivot的放在左侧
    while(begin<end)
    {
        if(reverse)
        {
            if(nums[end]<pivot)
            {
                nums[begin] = nums[end];
                begin++;
                reverse = false;
            }
            else
                end--;

        }
        else
        {
            if(nums[begin]>=pivot)
            {   
                nums[end] = nums[begin];
                end--;
                reverse = true;
            }
            else
                begin++;

        }
    }

   //begin=end时,指向的就是pivot最终的位置
     nums[begin] = pivot;

     return begin;
 }

//函数调用
vector<int> sortArray(vector<int>& nums) {
    QuickSort(nums,0,nums.size());
    return nums;
}

二、插入排序

1、简单插入排序

算法描述

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

版本一(常规)

//插入排序


//一次插入,将nums[n]个元素插入已经排好序的前n个元素中
void insert(vector<int>& nums,int n){
    int i = n;
    int key = nums[n];
    //注意i>0的条件
    while(i>0 && nums[i-1]>key)
    {
        nums[i] = nums[i-1];
        i--;

    }
    nums[i] = key;
}

//从nums[1]-nums[l-1]进行一次插入
void InsertSort(vector<int>& nums){
    int l = nums.size();
    for(int i=1;i<l;i++)
        insert(nums,i);
}

//函数调用
vector<int> sortArray(vector<int>& nums) {
    InsertSort(nums);
    return nums;
}

版本二(利用二分查找进行优化)
由于需要找到插入的位置,前面的数组已经是有序的,可以利用二分查找代替常规版本的线性查找

//二分查找:找到第一个比nums[idx_key]大的元素索引,这个索引即为nums[idx_key]需要插入的位置。这样操作会使得等于nums[idx_key]的值在nums[idx_key]左侧
int BinarySearch(vector<int>& nums,int idx_key){
    //找到比x大的第一个元素索引
    //在[begin,end)区间查找
    int begin = 0;
    int end = idx_key; 
    int key = nums[idx_key];
    while(begin<end)
    {
        
        int mid = (begin+end)>>1;
		
        if(nums[mid]<=key)
        {
        	//不取<=key的索引值,因此begin = mid+1,跳过mid这个位置
            begin=mid+1;         
        }  
        else
            end = mid;
    }
    return begin;
}

//将idx及其之后的元素后移,并将nums[idx_key]插入nums[idx]的位置
void insert_bs(vector<int>& nums,int idx_key,int idx){
    //idx为x需要插入的位置索引
    int key = nums[idx_key];
    for(int i=idx_key;i>idx;i--)
        nums[i] = nums[i-1];
    nums[idx] = key;
}

//从第二个元素到最后一个元素执行一次插入
vector<int> InsertSort_bs(vector<int>& nums) {
    for(int i=1;i<nums.size();i++)
    {
        int idx = BinarySearch(nums,i);
        //cout<<nums[i]<<idx<<endl;
        insert_bs(nums,i,idx);
    }
    return nums;
}

//函数调用
vector<int> sortArray(vector<int>& nums) {
    InsertSort_bs(nums);
    return nums;

2、希尔排序

算法描述

  • 先给定一个步长序列,或者生成一个步长序列:[数组长度/2,数组长度/2^2,…,1]
  • 若有k个步长,则对序列进行k趟排序
  • 每次排序选取第i个步长step(从大到小),将数组分为step个子序列,对这step个子序列使用插入排序。直至step=1进行排序后得到整个有序的数组。
//希尔排序

//生成步长序列
vector<int> generateStepSequence(int l)
{
    vector<int> stepsequence;
    l = l>>1;
    while(l>0){ 
        stepsequence.push_back(l);
        //cout<<l;
        l = l>>1;
    }
    return stepsequence;
}

//对某个step进行一次排序
void StepSort(vector<int>& nums,int step)
{
    //对step列依次进行排序
    for(int col=0;col<step;col++)
    {
        //对每列进行插入排序
        //每列元素索引为:col,col+step,col+step*2,...
        //从第二个元素开始插入
        for(int begin = col+step;begin<nums.size();begin+=step)
        {
            int cur = begin;
            int val = nums[begin];
            while(cur>col && val<nums[cur-step])
            {
                nums[cur] = nums[cur-step];
                cur-=step;
            }
            nums[cur] = val;
        }

    }
}

//希尔排序:对每个step进行一次stepsort
void ShellSort(vector<int>& nums)
{
    vector<int> stepsequence = generateStepSequence(nums.size());
    for(int i=0;i<stepsequence.size();i++)
        StepSort(nums,stepsequence[i]);
}

//函数调用
vector<int> sortArray(vector<int>& nums) {
    ShellSort(nums);
    return nums;
    }

三、选择排序

1、简单选择排序

每一轮在未排序数组中选择最大值放到最后。直至所有元素排序完毕。

算法描述

  • 从数组的前n个元素中查找最大值,并与第n+1个元素交换,即将最大值移到当前最末尾位置。
  • 第一趟排序,将整个数组的最大值放在最后,有序数组的长度加一,无序数组长度减一;
  • 继续对无序数组进行一次排序;直至无序数组长度为1
//选择排序
void swap(vector<int>& nums,int i1,int i2){
    int temp = nums[i1];
    nums[i1] = nums[i2];
    nums[i2] = temp;
}

//找到前n个未排序数组中最大元素的索引值
int findMax(vector<int>& nums,int n){
    int idx = 0;
    for(int i=0;i<n;i++)
    {
        if(nums[idx]<nums[i])
            idx = i;
    }
    return idx;
}

//从l(数组长度)长度的数组开始(整个数组),找到最大值,与最后一个元素交换位置,再对前l-1长度的数组重复操作,直至剩余数组长度为1
void SelectSort(vector<int>& nums) {
    int l = nums.size();
    for(int i=l;i>=1;i--)
    {
        int max_idx = findMax(nums,i);
        swap(nums,max_idx,i-1);
    }
}

//函数调用
vector<int> sortArray(vector<int>& nums) {
    SelectSort(nums);
    return nums;
    }

2、堆排序

对于选择排序算法的优化,由于选择排序每次需要选择当前的最大元素进行交换。简单选择排序是通过线性遍历找到最大值位置,堆排序是利用堆的性质查找最大值,将O(N)的查找时间复杂度降低为O(logN)。

  • 原地建堆build_heap
  • 重复以下操作,直至堆的元素数量为1:
    a)将首个元素(堆顶->最大值)与末尾元素交换
    b)堆的元素数量减1
    c)对0号元素进行Heapify操作
void swap(vector<int>& nums,int i1,int i2){
    int temp = nums[i1];
    nums[i1] = nums[i2];
    nums[i2] = temp;
}

void build_heap(vector<int>& nums){
    //build_heap分为自上而下和自下而上,自下而上时间复杂度较小O(N),自上而下为O(NlogN)
    //自下而上是从最后一个非叶子节点开始进行heapify操作,即将当前堆调整为大根堆
    int last_node = nums.size()-1;
    int parent = (last_node-1)/2;
    for(int i=parent;i>=0;i--)
    {
        Heapify(nums,nums.size(),i);
    }
}

//对于除堆顶元素外,左右子堆都符合大根堆特性的堆进行调整,即将堆顶元素取走后的恢复操作
//当前堆有n个元素,需要调整索引为idx的元素
void Heapify(vector<int>& nums,int n,int idx){
    if(idx>=n)
        return ;
	//堆顶元素的左右孩子索引
    int c1 = idx*2+1;
    int c2 = idx*2+2;

    int max = idx;
    //找到堆顶,左孩子,右孩子中最大的节点索引
    if(c1<n && nums[c1]>nums[max])
        max = c1;
    if(c2<n && nums[c2]>nums[max])
        max = c2;
    //如果最大值不为堆顶元素,则需要进行调整
    if(max!=idx)
    {
    	//将最大索引元素与堆顶交换,并对交换的一边子堆进行heapify操作
    	swap(nums,idx,max);
        Heapify(nums,n,max);
    }
    //return nums;

}

void HeapSort(vector<int>& nums) {
    int heapsize = nums.size(); 
    //原地建堆
    build_heap(nums);

    while(heapsize>1)
    {
    	//将最大值(第一个元素)与当前堆最后一个元素进行交换
        swap(nums,0,heapsize-1);
        
        //无序堆的长度减1
        heapsize--;
        //恢复堆的性质(对索引0进行恢复堆)
        Heapify(nums,heapsize,0);
    }
    
//函数调用
vector<int> sortArray(vector<int>& nums) {
    HeapSort(nums);
    return nums;
    }    

四、归并排序

算法描述

  • 不断将当前序列平均分割成两个子序列(直至每个序列中只有一个元素)
  • 不断将两个子序列合并为一个有序序列(直至只剩下一个有序序列)
//[begin,mid) [mid,end)两个区间进行有序合并
//原地合并,需要备份一个区间的数组,然后设置三个指针分别指向两个子数组和原数组
void merge(vector<int>& nums,int begin,int mid,int end){
    //mid为左边第一个元素,end为最后一个元素索引+1
    int left_size = mid-begin;
    int right_size = end -mid;

    int left[left_size];
    cout<<left_size<<" "<<right_size<<endl;
    

    //备份左边数组
    for(int i=0;i<left_size;i++)
        left[i] = nums[begin+i];

    //merge
    int li = 0,le = mid-begin;
    int ri = mid,re = end;
    //整个数组的指针(原地merge)
    int ai = begin;

    while(li<le && ri<re)
    {
        if(left[li]<nums[ri])
        {
            nums[ai] = left[li];
            li++;
            ai++;
        }
        else
        {
            nums[ai] = nums[ri];
            ri++;
            ai++;
        }

    }
    //若右边数组填充完,则将左边数组依次填充进去
    while(li<le)
    {
        nums[ai] = left[li];
        li++;
        ai++;
    }
    //若左边数组填充完,不需要进行操作,右边数组是有序排列在nums中的
    
}

//对[L,R)的区间进行归并排序
void mergeSort(vector<int>& nums,int L,int R){
    int M = (L+R)>>1;
    //当M=L或M=L时,表示需要merge的元素数量为0个,结束递归
    if(M-L<1 || R-M<1)
        return ;

    else
    {
        //divide
        mergeSort(nums,L,M);
        mergeSort(nums,M,R);

        //merge!!!重点:将[L,M) [M,R)两个区间进行有序合并
        merge(nums,L,M,R);
    }
    //return nums;
}

非比较类排序

不通过比较来决定元素间的相对次序,可以突破比较排序的时间复杂度。(这里不介绍了)

计数排序

桶排序

基数排序

复杂度分析

排序算法复杂度

参考链接
https://www.cnblogs.com/onepixel/articles/7674659.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值