首页 > 其他分享 >十大排序总结

十大排序总结

时间:2023-03-19 16:01:17浏览次数:48  
标签:总结 include target 十大 nums int vec 排序

Introduction

​ 本篇是对十大排序的总结,会涉及每个排序的重要步骤、时间复杂度、空间复杂度、稳定性、代码实现

Summary

十大排序算法

排序算法 最差时间复杂度 空间复杂度 平均时间复杂度 数据对象稳定性
冒泡排序 \(O(n^2)\) \(O(1)\) \(O(n^2)\) 稳定
插入排序 \(O(n^2)\) \(O(1)\) \(O(n^2)\) 稳定
选择排序 \(O(n^2)\) \(O(1)\) \(O(n^2)\) 数组不稳定,链接稳定
希尔排序 \(O(n^2)\) \(O(1)\) \(O(nlog_2n)\) 不稳定
归并排序 \(O(nlog_2n)\) \(O(n)\) \(O(nlog_2n)\) 稳定
快速排序 \(O(n^2)\) \(O(log_2n)\) \(O(nlog_2n)\) 不稳定
桶排序 \(O(n)\) \(O(m)\) \(O(n)\) 稳定
基数排序 \(O(n^2)\) \(O(r)\).r为基数 \(O(kn)\) 稳定
计数排序 \(O(n+m)\) \(O(n+m)\) \(O(n+m)\) 稳定
堆排序 \(O(nlog_2n)\) \(O(1)\) \(O(nlog_2n)\) 不稳定

不稳定:快速排序,选择排序,希尔排序,堆排序(简称,快选希堆)

非原地:归并、计数、桶、基数

冒泡排序(Bubble)

  • 思想:比较交换相邻元素

  • 主要步骤

    1. 比较相邻元素.若第一个比第二大,则交换
    2. 对每一对相邻元素进行同样的工作,会使得最后一个元素的值是最大的
    3. 针对所有元素重复以上步骤,除开最后一个元素
  • 动态图
    图片

  • 实现

    输入输出描述如下:
    image-20230318152116825

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    int main() 
    {
        int temp;
        vector<int> target{20,413,3,53,90,324};
    
        for(int i = 0; i < target.size() - 1; ++i)
        {
            for(int j = 0; j < target.size() - i - 1; ++j)
            {
                if(target[j] > target[j+1])
                    swap(target[j], target[j+1]);
            }
        }
        
        for(auto const& n : target)
        {
            cout << n << ',' <<' ';
        }
    
        return 0;
    }
    

选择排序(Select)

  • 思想:找最小元素,放到目标位置

  • 适用:数据量少

  • 主要步骤

    1. 在未排序的序列中寻找最小(大)元素,放在序列的起始位置
    2. 从剩余未排序的序列中继续重复上述操作,直到所有元素排序完毕
  • 动态图
    图片

  • 实现

    输入输出描述:
    image-20230318152623041

    #include <iostream>
    #include <vector>
    using namespace std;
    
    void select(vector<int>&);
    
    int main() 
    {
        int nums;
        cin >> nums;
        vector<int> vec(nums);  
        //输入目标序列
        for(int i = 0; i < nums; ++i)
        {
            cin >> vec[i];
        }
    
        //快排
        select(vec);
    
        for(const auto& n : vec)
            cout << n << ' ';
        
        return 0;
    }
    
    void select(vector<int>& vec)
    {
        for(uint i = 0; i < vec.size() - 1; i++)
        {
            int minIndex = i;	//最小值索引
            for(uint j = i + 1; j < vec.size(); j++)
            {
                if(vec[j] < vec[minIndex])
                    minIndex = j;
            }
            //交换
            int temp = vec[i];
            vec[i] = vec[minIndex];
            vec[minIndex] = temp;
        }
    }
    

插入排序(Insert)

  • 思想:与抓牌类似,插入牌中适合位置

  • 适用:基本有序

  • 主要步骤

    1. 从第一个元素开始,将第一个元素视为已经排序后的序列
    2. 取出下一个元素,并在已排序的序列中从后向前扫描
    3. 若已排序的元素大于新元素,则该元素向后移动一位
    4. 重复步骤三,直至找到合适位置(小于等于新元素),将新元素插入该位置
    5. 重复步骤2-4
  • 动态图
    图片

  • 实现

    输入输出描述:
    image-20230318161554722

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Insert
    {
    public:
        void MySort(vector<int>& nums)
        {
            for(uint i = 1; i < nums.size(); ++i)
            {
                if(nums[i] < nums[i-1])
                {
                    int j = i - 1;
                    int tar = nums[i];    //待排序元素
                    //nums[i] = nums[i-1];    //后移一个元素
                    while(j >= 0 && tar < nums[j])
                    {
                        nums[j+1] = nums[j];
                        j--;
                    }
                    nums[j+1] = tar;
                }
            }
        }
    };
    
    int main() 
    {
        int _size;
        cin >> _size;
        vector<int> target(_size, 0);
        for(int i = 0; i < _size; ++i)
        {
            cin >> target[i];
        }
    
        Insert insert;
        insert.MySort(target);
    
        for(const auto& n : target)
        {
            cout << n << " ";
        }
    
        return 0;    
    }
    

希尔排序(Shell)

希尔排序是更高效的插入排序

  • 思想:间隔分组+自定义排序

  • 适用:数据量大

  • 主要步骤

    1. 将整个待排序列分割为多个子序列
    2. 按照增量分别进行插入排序
    3. 再依次缩减增量进行插入排序
  • 动态图
    图片

  • 实现

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Shell
    {
    public:
    	void MySort(vector<int>& nums)
    	{
    		int s = nums.size();
    		int gap = s / 2;	//增量
    		while (gap > 0)
    		{
    			for (int i = gap; i < s; ++i)
    			{
    				int j = i;
    				while (j >= gap && nums[j] < nums[j - gap])
    				{
    					swap(nums[j], nums[j - gap]);
    					j -= gap;
    				}
    			}
                //缩减增量
    			gap /= 2; 
    		}
    	}
    };
    
    int main()
    {
    	vector<int> vec{ 9, 7, 5, 3, 1, 0, 8, 4, 6, 2 };
    	Shell shell;
    	shell.MySort(vec);
    	for (const auto& n : vec)
    	{
    		cout << n << " ";
    	}
    
    	return 0;
    }
    

归并排序(Merge)

  • 思想:分治(递归)

  • 适用:不受数据影响,所需空间和n成正比

  • 主要步骤

    1. 将长度为n的待排序序列分成两个长度为n/2的子序列
    2. 对这两个子序列采用归并排序
    3. 将两个排序后的子序列合并为最终的序列
  • 动态图
    图片

  • 实现

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Merge
    {
    public:
    	void MySort(vector<int>& nums, int l, int r)
    	{
    		if (l < r)
    		{
    			int mid = l + ((r - l) >> 1);
    			MySort(nums, l, mid);
    			MySort(nums,mid + 1, r);
    			MergeSort(nums, l, r);
    		}
    	}
    
    	void MergeSort(vector<int>& nums, int l, int r)
    	{
    		vector<int> temp(nums.size());
    		int mid = l + ((r - l) >> 1);
    		int p = l;
    		int q = mid + 1;
    		int k = l;
    
    		while (p <= mid && q <= r)
    		{
    			if (nums[p] < nums[q])
    			{
    				temp[k++] = nums[p++];	//若右边元素大于左边元素,跳过
    			}
    			else
    			{
    				temp[k++] = nums[q++];	//若右边元素小于等于左边元素,将右边元素赋值给左边
    			}
    		}
    		
    		//赋值余下元素
    		while(p <= mid)
    			temp[k++] = nums[p++];
    
    		while (q <= r)
    			temp[k++] = nums[q++];
    		
    		for (int i = l; i <= r; ++i)
    		{
    			nums[i] = temp[i];
    		}
    
    
    	}
    };
    
    int main()
    {
    	vector<int> vec{ 9, 7, 5, 3, 1, 0, 8, 4, 6, 2 };
    	Merge merge;
    	merge.MySort(vec, 0 , vec.size() - 1);
    	for (const auto& n : vec)
    	{
    		cout << n << " ";
    	}
    
    	return 0;
    }
    

快速排序(Quick)

  • 思想:选择中轴元素,小于它的放左边,大于它的放右边,重复直至各区间只剩一个数

  • 适用:广泛(最快)

  • 主要步骤

    1. 以未排序序列中的第一个元素作为基准元素
    2. 两个哨兵,一个在最左边,一个在最右边。右边的向左移动,一旦找到小于基准的数则停止,左边的向右移动,一旦找到大于基准的数停止,交换他们
    3. 重复步骤2,直至右哨兵的索引 == 左哨兵的索引
    4. 交换基准元素和左哨兵
    5. 以左哨兵为基准划分左右两边界,分别再进行快排
  • 动态图
    图片

  • 实现

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Qucik
    {
    public:
    	void MySort(vector<int>& nums, int start, int end)
    	{
    		if (start >= end) return;	//判断区间是否只剩一个元素
    		
    		int l = start;
    		int r = end;
    		int temp = nums[l];	//基准元素
    		while (l < r)
    		{
    			//最右边的先动
    			//找小于基准的
    			while (l < r && nums[r] >= temp)
    			{
    				r--;
    			}
    
    			//找大于基准的
    			while (l < r && nums[l] <= temp)
    			{
    				l++;
    			}
    
    			//交换符合条件的两个数
    			if (r > l)
    			{
    				swap(nums[l], nums[r]);
    			}
    		}
    
    		swap(nums[l], nums[start]);	//l == r 处赋予基准值,再进行分治,如此使得左边的数都小于基准值,右边的数都大于基准值
    		//以基准值为分界点
    		MySort(nums, start, l - 1);
    		MySort(nums, l + 1, end);
    	}
    };
    
    int main()
    {
    	vector<int> vec{ 9, 7, 5, 3, 1, 0, 8, 4, 6, 2 };
    	Qucik qucik;
    	qucik.MySort(vec, 0 , vec.size() - 1);
    	for (const auto& n : vec)
    	{
    		cout << n << " ";
    	}
    
    	return 0;
    }
    

    image-20230318181113462

计数排序(Count)

  • 思想:利用数组下标确定元素的正确位置

  • 适用:max和min的差值不大,待排序序列中所有数均为整数

  • 主要步骤:

    1. 找出待排序序列中最大和最小的元素
    2. 统计序列中每个值为i的元素出现的次数,存入计数数组c的第i项
    3. 对所有计数累加
    4. 将每个元素i放在新数组的第C[i]项,每放一个元素就将C[i]减一
  • 动态图
    图片

  • 实现

    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    class Count
    {
    public:
    	void MySort(vector<int>& target, vector<int>& temp)
    	{
    		//保证待排序序列非空
    		if (target.size() == 0) return;
    
    		//使用target容器中的最大值+1作为计数容器的大小
    		int vecCountLen = (*max_element(target.begin(), target.end())) + 1;
    		vector<int> vecCount(vecCountLen, 0);	//计数容器
    
    		//统计每个键值出现的次数
    		for (int i = 0; i < target.size(); ++i)
    		{
    			vecCount[target[i]]++;
    		}
    
    		//后面的键值出现的位置是前面所有键值出现的次数之和
    		for (int i = 1; i < vecCountLen; ++i)
    		{
    			vecCount[i] += vecCount[i - 1];
    		}
    
    		//将键值放于目标位置.也就是从中取出键值,每取一个,对应计数器减1,直到所有计数器为0,使得目标序列排序完毕
    		for (int i = target.size(); i > 0; --i)
    		{
    			temp[--vecCount[target[i - 1]]] = target[i - 1];
    		}
    	}
    };
    
    int main()
    {
    	vector<int> vec{ 9, 7, 5, 3, 1, 0, 8, 4, 6, 2 };
    	vector<int> temp(vec.size(), 0);
    	Count count;
    	count.MySort(vec, temp);
    	for (const auto& n : temp)
    	{
    		cout << n << " ";
    	}
    
    	return 0;
    }
    

桶排序(Bucket)

  • 思想:将值为i的元素放入i号桶,最后依次把桶里的元素倒出来

  • 适用:均匀分布的数据

  • 主要步骤:

    1. 设置一个定量的数组作为空桶
    2. 从序列中把项目一一放进对应的桶中
    3. 对每个不是空的桶子排序
    4. 从桶中把项目放回原来的序列
  • 动态图
    图片

  • 实现

    #include <iostream>
    #include <vector>
    #include <list>
    #include <algorithm>
    
    using namespace std;
    
    class Bucket
    {
    public:
    	void MySort(vector<int>& target)
    	{
    		//初始化桶
    		vector<list<int>> buckets(target.size());
    
    		//将数据放入桶中并进行排序
    		for (const auto& n : target)
    		{
    			int index = GetBucketIndex(n);
    			InsertSort(buckets[index], n);
    		}
    
    		//排序后,从桶中取数据,放入原数组
    		int i = 0;
    		for (const auto& bucket : buckets)
    		{
    			for (const auto& b : bucket)
    			{
    				target[i++] = b;
    			}
    		}
    	}
    
    	//获得桶的序号
    	int GetBucketIndex(int num)
    	{
    		return num / 3;
    	}
    
    	//使用插入排序将数据放入对应桶中
    	void InsertSort(list<int>& bucket, int n)
    	{
    		bool flag = true;
    		for (auto it = bucket.begin(); it != bucket.end(); ++it)
    		{
    			//小于目标值,插入当前位置
    			if (n <= *it)
    			{
    				bucket.insert(it, n);
    				flag = false;
    				break;
    			}
    		}
    		if (flag)
    			bucket.emplace_back(n);
    	}
    };
    
    int main()
    {
    	vector<int> vec{ 9, 7, 5, 3, 1, 0, 8, 4, 6, 2 };
    	Bucket bucket;
    	bucket.MySort(vec);
    	for (const auto& n : vec)
    	{
    		cout << n << " ";
    	}
    
    	return 0;
    }
    

基数排序(Radix)

  • 思想:是桶排序的一种, 按数位分桶

  • 适用:min和max的差值不大

  • 主要步骤

    1. 取得数组中的max及其位数
    2. 从最低位开始取每个位组成radix数组
    3. 对radix进行计数排序
  • 动态图
    图片

  • 实现

    #include <iostream>
    #include <vector>
    #include <list>
    #include <algorithm>
    
    using namespace std;
    
    class Radix
    {
    public:
    	void MySort(vector<int>& target, int d)
    	{
    		int p = 1;
    		vector<vector<int>> buckets(10, vector<int>(target.size()));
    		vector<int> order(10);
    
    		while (p < d)
    		{
    			//分桶
    			for (const auto& n : target)
    			{
    				int index = n / p % 10;	//获得桶号
    				buckets[index][order[index]] = n;	//将n放入第index号桶的order[index]
    				order[index]++;	//放入的位置++
    			}
    
    			//排序
    			int k = 0;
    			for (int i = 0; i < 10; ++i)
    			{
    				if (order[i] == 0) continue;	//当前位置没有放入
    				for (int j = 0; j < order[i]; ++j)
    				{
    					target[k++] = buckets[i][j];
    				}
    				order[i] = 0;	//当前位置计算完后清零
    			}
    
    			p *= 10;
    		}
    	}
    };
    
    int main()
    {
    	vector<int> vec{ 9, 7, 5, 3, 1, 0, 8, 4, 6, 2 };
    	Radix radix;
    	radix.MySort(vec, 10);
    	for (const auto& n : vec)
    	{
    		cout << n << " ";
    	}
    
    	return 0;
    }
    

堆排序

推荐这个讲解【算法】排序算法之堆排序 - 知乎 (zhihu.com)或是参见STL

  • 思想:利用堆进行排序

  • 主要步骤

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

    #include <iostream>
    #include <vector>
    #include <list>
    #include <algorithm>
    
    using namespace std;
    
    class Heap
    {
    public:
    	void MySort(vector<int>& target)
    	{
    		int s = target.size();
    
    		//构造初始堆,自底向上
    		//从第一个非叶子节点(倒数第二行的最后一个)开始调整
    		//左右子节点中较大的和父节点交换
    		for (auto i = s / 2 - 1; i >= 0; --i)
    		{
    			HeadAdjust(target, s, i);
    		}
    
    		/*
    		排序
    			1.交换nums[0](nums[0]为最大值)和nums[i]
    			2.再次构建大顶堆
    		*/
    		for (int i = s - 1; i > 0; --i)
    		{
    			swap(target[i], target[0]);
    
    			//再次构建
    			HeadAdjust(target, i, 0);
    		}
    		
    	}
    
    	//调整堆
    	//len是nums的长度,i是当前三个节点中的根节点
    	void HeadAdjust(vector<int>& nums, int len, int i)
    	{
    		int index = 2 * i + 1;
    
    		//将较小值下沉,较大值上位
    		while (index < len)
    		{
    			//index 指向子节点中较大的
    			if (index + 1 < len)
    			{
    				if (nums[index + 1] > nums[index])
    				{
    					index = index + 1;
    				}
    			}
    
    			//较大子节点和根节点进行比较
    			if (nums[index] > nums[i])
    			{
    				//交换
    				swap(nums[index], nums[i]);
    				//更新
    				i = index;
    				index = 2 * i + 1;
    			}
    			else
    			{
    				break;
    			}
    		}
    	}
    };
    
    int main()
    {
    	vector<int> vec{ 9, 7, 5, 3, 1, 0, 8, 4, 6, 2 };
    	Heap heap;
    	heap.MySort(vec);
    	for (const auto& n : vec)
    	{
    		cout << n << " ";
    	}
    
    	return 0;
    }
    

reference

十大经典排序算法大梳理 (动图+代码) (qq.com)

标签:总结,include,target,十大,nums,int,vec,排序
From: https://www.cnblogs.com/chenglixue/p/17233394.html

相关文章

  • 今日总结
    上午学了几个小时编程,中午去买了日抛,下午上了课,去练习设计动作了,晚上徒步从长安公园走了近五公里回到了学校,但是去吃了自助餐陪女朋友,逃了礼仪队的训练。希望不会被弄死。......
  • Lambda 表达式总结
    1Lambda表达式简介​Lambda表达式是JDK8的新特性,主要用于简化匿名内部类的定义,帮助用户方便、高效地书写优雅的代码。​Lambda表达式实现的必须是一个接......
  • adb常用命令总结
    1前言​ADB(AndroidDebugBridge)即Android调试桥,采用监听SocketTCP端口的方式通讯。连接手机有2种方式:有线连接、无线连接。​(1)有线连接​使用数据线......
  • numpy数组初始化方法总结
    1使用list初始化a=np.array([[1,2,3],[4,5,6]],dtype='float32')#a=[[1.2.3.],[4.5.6.]]2赋值与复制(1)赋值a=np.array([1,2,3])b=aprint(bisa)#Trueb[0]......
  • 【RPC高性能框架总结】5.高性能nio框架netty(中)
    接上一篇《​​4.高性能nio框架netty(上)​​》上一篇我们编写了使用Netty框架开发的客户端的启动类“NettyTestClient”以及业务处理类“NettyTestClientHandler”,本篇我......
  • 【Docker学习总结】8.Docker-查看和删除镜像
    上一篇技术总结,我们使用常用的Docker命令创建了容器,并在容器中搭建了Nginx环境、部署了一个静态网页,并成功在宿主机中访问容器中的静态网页,以及使用浏览器在宿主机映射容器......
  • 【Java邮件开发】3.邮件协议总结与邮件服务器的工作原理
    我们来对邮件协议进行总结,并探讨邮件服务器的工作原理一、邮件协议剖析1.指令过程描述记得上一篇总结,我们手动敲指令发邮件的时候,登录smtp服务器的......
  • "全类型" 排序(选择、冒泡) 回调函数
    直接上代码若代码有可优化或某处不合理,欢迎指正,不胜感激。#include<stdio.h>#include<stdlib.h>#include<string.h>intcompare_double(void*dst_addr,void*sr......
  • 李沐多模态串讲视频总结 ALBEF VLMo BLIP CoCa BEITv3 模型简要介绍
    开场多模态串讲的上篇是比较传统的多模态任务多模态最后的模态交互很重要传统的缺点是都用了预训练的目标检测器,训练和部署都很困难。ViLT把预训练的目标检......
  • 二维数组冒泡排序
    0.本文结构概述二维数组在内存中是线性存储二维数组排序(C语言代码)1.二维数组在内存中是线性存储2.二维数组排序(C语言代码)#include<stdio.h>intmain(intarg......