经典排序算法

本文详细介绍了几种常见的排序算法,包括归并排序、快速排序、冒泡排序、插入排序、选择排序和堆排序,讨论了它们的基本思想、时间复杂度以及稳定性。归并排序和插入排序是稳定排序,而快速排序和堆排序则是不稳定排序。在实际应用中,需要根据数据特性和需求选择合适的排序算法。
摘要由CSDN通过智能技术生成

十大排序

需要首先清楚的点:

稳定性排序:如果在待排序的序列中,存在多个具有相同的关键字的记录,若经过排序这些记录的相对次序不变,就是排序前a=b,但是a在b前面,排序后a还是在b前面。不稳定举出一个反例就行。

归并排序

思想:

  1. 基于分治的思想。将一个待排序的数组从中间分成两个子数组,然后再对子数组进行相同的分操作,直到只有一个元素,此时这个元素认为就是有序的了。然后开始合并,合并就是不断地取出两个有序数组中的比较小的一个放在辅助数组中,直到将所有的有序数组中的元素取完。

实现:

void merge(vector<int>&num, vector<int>&tmp, int l, int mid, int r) {
	int i = l;
	int j = mid + 1;
	int dex = 0;
	while (i <= mid && j <= r) {
		if (num[i] <= num[j]) {
			tmp[dex++] = num[i++];
		}
		else
		{
			tmp[dex++] = num[j++];
		}
	}
	while(i<=mid)tmp[dex++] = num[i++];
	while(j<=r)tmp[dex++] = num[j++];
//这里注意num[],递归过程中变量的变化
	for (int k = 0; k <dex; ++k) {
		num[l+k] = tmp[k];
	}
}
void bsort(vector<int>&num, vector<int>&tmp,int l, int r) {
	if (l < r) {
		int mid = (r - l) / 2 + l;
		bsort(num,tmp, l, mid);
		bsort(num,tmp, mid + 1, r);
		merge(num, tmp,l, mid, r);
	}
}

时间复杂度:O(nlogn)
稳定性:稳定排序

快速排序

原理:

  1. 排序思想:首先从待排序的数组中选取一个主元(先确定分割点),然后将数组中的其他元素根据主元的大小分成两部分,小于等于主元的在数组前面,大于等于主元的在数组后面,这时候主元就是已经排好序的元素了,然后数组以这个主元为界限分成了两个待排序的子数组;接着采用递归的方式依次对这两个子数组做同样的操作,直到最后的子数组只有一个元素或者没有元素为止。
  2. 分割操作:选取第一个元素作为主元i,然后令l=i+1,r在数组的最后一个位置,l向右遍历的过程中如果遇到大于或等于主元的元素就停止移动,r向左遍历的过程中如果遇到小于或等于主元的元素就停止移动。当l.r都停止移动的时候,如果这时候l<r,就交换二者指向的元素,继续遍历直到l>=r为止,此时分割结束。最后还要将主元和j指向的元素交换位置。

代码实现:

//快排模板
    void qsort(vector<int>&nums,int l,int r){
        if(l>=r)return ;//递归返回条件,l==r就一个元素默认有序
        int center=partion(nums,l,r);//找到分割点之后,对分割点左边的元素再进行分治,直到只有一个元素或者已经有序为止
        qsort(nums,l,center-1);//注意左闭右闭区间
        qsort(nums,center+1,r);
    }
    //分割操作
    int partion(vector<int>&arr,int l,int r){
        int key=arr[l];//先假设第一个元素是主元去找分割点。主元要放到正确的位置,放到正确的位置就是已经排好序的位置,就是此刻的分割点。
        int fast=l+1,last=r;//双向调整,将主元作为分割点,令主元左边的元素<=主元<=主元右边的元素
        while(fast<=last){//左闭右闭区间,fast==last为结束条件
            while(fast<=last&&arr[fast]<=key)fast++;//找左区间第一个大于主元的元素
            while(fast<=last&&arr[last]>=key)last--;//找右区间第一个小于主元的元素
            if(fast>last)break;//如果左区间没有大于主元并且右区间没有小于主元的数组元素不用变化,已经是在这个主元的条件下部分有序了
            swap(arr[fast],arr[last]);//否则交换二者元素,这里位置也交换了。所以后面是返回last。
        }
        //swap(arr[fast],arr[l]);//在数组上操作元素,变化的是数组中的元素
        //swap(arr[fast],key);//这样数组中的元素没有改变,只是交换了key和arr[fast]的元素
        arr[l]=arr[last];//直接赋值操作比较好,将主元的元素和遍历到的临界元素交换,使主元称为分割点
        arr[last]=key;//
        return last;//用的arr[last]赋值就返回last,返回分割点位置
    }

平均时间复杂度:最好的情况就是主元选取的是不大不小的中间元素,这样每次分割都能从数组的中间分割了,此时时间复杂度是O(nlogn)。
最坏时间复杂度:o(n2).因为如果选取的主元使得主元左边的元素都是0,右边n-1个元素,就要分割n次,每次分割的时间复杂度都是o(n)。
稳定性:不稳定排序

冒泡排序

原理:

  1. 将第一个元素和第二个元素比较,如果第一个比第二个大,交换这两个元素的位置,此时第二个比第一个大,再将第二个和第三个元素比较,如果第二个元素比第三个元素大,交换他们的位置…对每一对元素都做同样的工作,遍历完所有元素之后就可以确定右边的是最大的元素;然后除去这个最大的元素,对剩下的所有元素做相同的工作,直到排序完成。

代码:

void bubblesort(vector<int>&nums,int n){
    bool swapped;
    for(int i=1;i<n;i++){
        swapped=false;//设置一个标记来判断有没有交换
        for(int j=1;j<n-i+1;j++){
            if(nums[j-1]>nums[j]){//可能一直比较,但不用交换,所以优化可以设置一个标记来判断有没有交换
                swap(nums[j],nums[j-1]);
                swapped=ture;
		}
}
        if(!swapped)break;//如果一直没有交换的话,说明序列就已经是排好序的,直接退出就行
}
}

时间复杂度:O(n*2)
稳定性:稳定排序

插入排序

思想:
假定第一个元素是已经排好序的,后面元素是未排序的,然后把把未排序的一个一个插入到有序的集合中,如何插入呢:把有序集合从后向前扫一遍,找到比这个元素小的元素就插入在这个元素的后面。和选择排序区别就是只需要比较有序集合中的元素,先根据元素大小找到插入位置,然后插入到比这个元素小的位置上。

代码:

ector<int> InsertSort(vector<int> list)
{
	vector<int>result = list;
	if (result.empty())   //判断容器内容是否为空
		return result;
	//第一个元素是有序的,所以从下标为1的元素开始
	for (int i = 1; i < result.size(); i++)  
	{
		int temp = result[i];
		int j = i - 1;
		//取出第i个元素,将其与之前的元素进行比较,
		//直到找到比它小的元素或者遍历完所有元素将其放在开头
		for (j; j >= 0 && result[j] > temp; j--) 
		{
			result[j + 1] = result[j];//还未找到比当前元素小的就一直遍历下去直到到数组最前端
		}
		result[j + 1] = temp;//找到比当前元素小的就直接插入到这个元素后面。
	}
	return result;

时间复杂度:O(n*2)
稳定性:稳定排序

选择排序

所谓选择:就是每次从未排序元素中选择出最大或者最小的元素放入已经排好序的元素集合中。
算法思想:

  1. 减而治之:
  2. 贪心:
  3. 循环不变量:若干个变量在循环的过程中,保持了他们各自的性质,循环的过程:初始,循环过程中,结束

算法流程:

  1. 对于一个无序集合,先假设第一个元素是最大或者最小的元素,此时这个元素还不是已经排好序的
  2. 将这个假设最大或者最小的元素和剩下的元素一一比较,如果剩下的元素比他大或者小就更新最小值下标,直到和剩下的元素一一比较结束。
  3. 此时定了整个未排序集合的最小或最大值,和集合中的第一个元素进行交换,此时集合中的第一个元素就是已经排好序的了。
  4. 同样的方式,假设集合的第二个元素是最大或者最小的元素,重复1-3,将整个集合都排好序为止。

实质:每次选择一个元素都需要从剩下未排序的集合中选取,这就意味着每次确定选择一个元素之前都需要进行未排序集合中所有元素的一一比较。
稳定性:不稳定,有可能交换的时候将前面的数和后面的数交换位置了。
时间复杂度:o(n^2)

void selection_sort(vector<int>&nums,int n){
    for(int i=0;i<n-1;i++){//只需要比较到最后一个元素的前一个元素,不然会溢出
        int minIndex=i;//默认第一个元素为最小的元素,我们关注下标
        for(int j=i+1;j<n;j++){//在未排序的数组中找最小的元素,每次比较一个元素都需要再找一遍,比较麻烦
            if(nums[j]<nums[minIndex])minIndex=j;
        }
        swap(nums[minIndex],nums[i]);//交换最小值和无序区间的第一个元素
	}
}

堆排序

原理:排序就是把堆顶的元素与最后⼀一个元素交换,交换之后破坏了堆的特性,我们再把堆中剩余的元素再次构成⼀个大顶堆,然后再把堆顶元素与最后第二个元素交换…如此往复下去,等到剩余的元素只有⼀个的时候,此时的数组就是有序的了
代码:

  //堆排-二叉堆先搞定
    //上浮,数组下标从0开始
    void swim(vector<int>&arr,int start){
        int c=start;//当前节点的位置,数组中最后一个元素的位置
        int p=(c-1)/2;//父节点的位置
        int tmp=arr[c];//当前节点的元素值,建立最大堆,当前节点的元素值先保存
        while(c>0){//插入c要大于0
            if(tmp<=arr[p])break;//建立大顶堆,如果插入的元素值小于等于其父节点的值,已经有序了
            else{
                arr[c]=arr[p];//单向赋值即可,移动的是要插入的值,对要插入的值做出改变即可
                c=p;//索引改变,直接交换也可
                p=(p-1)/2;//同时父节点更新
            }
        }
        arr[c]=tmp;//可以直接swap,这里是单向赋值。抓住上浮到父节点大于子节点为止,然后赋值
    }
    //插入
    void inSert(vector<int>&arr,int data){
        arr.push_back(data);//将数据插入到数组末尾,然后开始上浮调整
        swim(arr,arr.size()-1);//
    }
    //下沉
    void sink(vector<int>&arr,int start,int end){
        int c=start;//当前节点的位置
        int l=2*c+1;//当前节点的左孩子节点的位置
        int tmp=arr[c];//当前节点的数值
        while(l<=end){//左闭右闭区间,
            if(l+1<=end&&arr[l]<arr[l+1]){//左右孩子中找到最大的,交换最大的,因为是大顶堆
                l++;//
            }
            if(tmp>=arr[l])break;//如果当前节点已经大于其左右孩子的最大值,就已经满足条件了
            else{//这里是单向赋值,一直修改父节点,到退出循环再把当前节点赋值。也可以直接swap,
                arr[c]=arr[l];//否则,左右孩子中最大的节点赋值给父节点,
                c=l;//更新下标
                l=2*l+1;//然后更新孩子节点,继续找下一个三角是否满足根节点最大
            }
        }
        arr[c]=tmp;//最后找到的位置,将当前节点的值更新
    }
    //删除,一般是删除堆顶的元素,把最后一个元素放到堆顶,然后让其开始下沉
    void reMove(vector<int>&arr,int data){
        arr[0]=arr.back();//已经是大顶堆,然后将最后一个元素赋值给堆顶,再调整即可
        arr.pop_back();
        sink(arr,0,arr.size()-1);
    }
    //构建,给一个无序的完全二叉树,然后将其构建成大顶堆。数组形式。
    //让所有非叶子节点下沉,不过是从非叶子节点下沉然后向上直到根节点,
    void buildMaxHeap(vector<int>&arr,int heapSize){
        for(int i=(heapSize+1)/2-1;i>=0;i--){//非叶子节点的计算,n/2-1.n是元素个数
            sink(arr,i,heapSize);//左闭右闭
        }
    }
    //堆排序,建立的大顶堆只是堆顶元素最大,
    //依次将堆顶的元素移除放入数组,这里直接和最后一个元素交换就行,然后调整剩余的未排序的数组,之后重复交换堆顶和数组末尾元素
    //其实是移除堆顶元素,数组长度减一的操作,这里原地操作了,也可以用辅助数组来存储堆顶元素
    void heapSort(vector<int>&arr){
        int n=arr.size();
        buildMaxHeap(arr, n-1);//先构建堆,左闭右闭区间
        for(int i=n-1;i>=1;i--){//依次交换堆顶和数组尾部元素,这是数组最后一个元素就是最大值,依次类推,然后调整未排序的数组
            swap(arr[i],arr[0]);//交换
            sink(arr,0,i-1);//交换的堆顶元素是排好序的,调整剩下的未排序元素即可。依次类推
        }
    }
    //堆STL使用,优先队列
    //priority_queue,每次只能访问位于队头的元素。
    //先进队列的不一定先出,而是优先级最大的元素先出队列。默认是大顶堆。
    //STL自定义排序
    struct cmp{
        bool operator()(int a,int b){
            return a>b;//>对应greater,升序排列,小顶堆
        }
    };
    vector<int> stl_heapSort(vector<int>&arr){
        priority_queue<int,vector<int>,cmp>q(arr.begin(),arr.end());//
        int n=arr.size();
        vector<int>result;
        while(!q.empty()){//priority_queue没有迭代器,所以只能通过移除一个个元素来进行访问
            result.push_back(q.top());//普通队列是q.front();
            q.pop();
        }
        //reverse(result.begin(),result.end());
        return result;
    }

熟悉STL使用即可
时间复杂度:O(nlogn)
稳定性:不稳定排序
##计数排序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值