十大经典排序算法总结(c++)

前言

在这里插入图片描述
名词解释:n:数据规模 k:桶的个数 In-place:占据常数内存 out-place:占额外内存

在这里插入图片描述
比较排序:常见的快速排序、归并排序、堆排序、冒泡排序 等属于比较排序 。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置 。
优势:适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
非比较排序:非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置 。
特点:非比较排序时间复杂度底(O(n)),但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

1.冒泡排序

算法思想:从头开始,每次比较两元素,若大者在前,则交换两元素,直至数组末尾,此时最大元素为数组最后的元素。除去最右的元素,我们对剩余的元素做同样的工作,如此重复下去,直到排序完成。
稳定算法

//冒泡排序,基础版,时间复杂度O(n^2) 
int bubblesort(vector<int>& array){
	int n=array.size();
	if(n<2){return array;}
	for(int i=0;i<n;i++){
		for(int j=0;j<n-1-i;j++){
			if(array[j+1]<array[j]){
				int temp=array[j];
				array[j]=array[j+1];
				array[j+1]=temp;
			}
		}
	}
	return array;
}

//优化版,加一个标志位,判断当前轮是否发生过交换事件标志位,若这一轮都没有未发生交换,则表明列表已有序,直接退出。
int bubblesort(vector<int>& array){
	int n=array.size();
	if(n<2){return array;}
	for(int i=0;i<n;i++){
		bool isExchange=false;
		for(int j=0;j<n-1-i;j++){
			if(array[j+1]<array[j]){
				int temp=array[j];
				array[j]=array[j+1];
				array[j+1]=temp;
				isExchange=true;
			}
		}
		if(!isExchange){
			break;
		}
	}
	return array;
}	

2.选择排序

算法思想:
搜索整个列表,找出最小项,若此项不为第1项,则与第1项交换位置;
重复上述步骤,每次搜索未被排序的剩余列表,并将最小元素与已排序段的后一位交换,直至列表所有元素均被排序

//选择排序 时间复杂度O(n^2) 
int selectSort(vector<int> arr){
	int n =arr.size();
	for(int i=0;i<n-1;i++){
		int min=i;
		for(int j=i+1;j<n;j++){
			if(arr[min]>arr[j]){
				min=j;
			}
		}
		int temp=a[i];
		arr[i]=arr[min];
		arr[min]=temp;
	}
	return arr;
}

3.插入排序

算法思想:将无序元素插到有序元素中去
步骤1: 从第一个元素开始,该元素可以认为已经被排序;
步骤2: 取出下一个元素,在已经排序的元素序列中从后向前扫描;
步骤3: 如果该元素(已排序)大于新元素,将该元素移到下一位置(腾位置);
步骤4: 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
步骤5: 将新元素插入到该位置后;
步骤6: 重复步骤2~5。

//时间复杂度O(n^2),不稳定,
//插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序)
int insertSort(vector<int> arr){
	int n=arr.size();
	for(int i=1;i<n;i++){
		j=i-1;
		temp=arr[i];
		while(j>=0&&arr[j]>temp){
			arr[j+1]=arr[j];
			j--;
			}
		arr[j+1]=temp;
	}
	return arr;
}
		

4.希尔排序

希尔排序是希尔(Donald Shell) 于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本。 希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
步骤
选择一个增量序列,初始增量gap=length/2,后续元素依次为前一元素除2,直至gap=1;
每轮以gap为步长,在列表上进行采样,将列表分为gap个小组,在每个小组内进行选择排序;
重复第二步,直至gap=1;
O在这里插入图片描述

//希尔排序,O(nlogn),不稳定
//核心思想还是使用插入排序算法
//通过分组,让数据在小规模内有序,减小递归增量使得整体有序
int shellSort(vector<int> arr){
	int n=arr.size();
	int gap=n/2;
	while(gap>0){
		for(int i=gap;i<n;i++){
			temp=arr[i]
			int j=i-gap;
			while(j>=0&&arr[j]>temp){
				arr[j+gap]=arr[j];
				j -=gap;
			}
			arr[j+gap]=temp;
		}
		gap /=2;
	}
	return arr;
}

5.归并排序

归并排序 是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法步骤
步骤1:把长度为n的输入序列分成两个长度为n/2的子序列;
步骤2:对这两个子序列分别采用归并排序;
步骤3:将两个排序好的子序列合并成一个最终的排序序列。

//归并排序 O(nlogn) 稳定 需要O(n)的额外空间
//归并操作部分
void merge(vector<int> arr,int low,int mid,int high){
	int i=low,j=mid+1;
	vector<int> temp;
	int t=0;
	while(i<=mid &&j<=high){
		if(arr[i]<arr[j]){
			temp[t++]=arr[i++];
		}
		else{
			temp[t++]=arr[j++];
		}
	}
	while(i<=mid){
		temp[t++]=arr[i++];
	}
	while(j<=high){
		temp[t++]=arr[j++];
	}
	t=0;
	while(low<=high){
		arr[low++]=temp[t++];
	}
}
void mergeSort(vector<int> arr,int low,int high){
	if(low<high){
		int mid=low+(high-low)/2;
        //左子数组融合排序
    	mergeSort(arr, low, mid);
        //右子数组融合排序
   		mergeSort(arr, mid + 1, high);
        //已经排序好的子数组有序融合
    	merge(arr, low, mid, high);
    }
}
	
public class MergeSort {
    // 非递归式的归并排序
    public static int[] mergeSort(int[] arr) {
        int n = arr.length;
        // 子数组的大小分别为1,2,4,8...
        // 刚开始合并的数组大小是1,接着是2,接着4....
        for (int i = 1; i < n; i += i) {
            //进行数组进行划分
            int left = 0;
            int mid = left + i - 1;
            int right = mid + i;
            //进行合并,对数组大小为 i 的数组进行两两合并
            while (right < n) {
                // 合并函数和递归式的合并函数一样
                merge(arr, left, mid, right);
                left = right + 1;
                mid = left + i - 1;
                right = mid + i;
            }
            // 还有一些被遗漏的数组没合并,千万别忘了
            // 因为不可能每个字数组的大小都刚好为 i
            if (left < n && mid < n) {
                merge(arr, left, mid, n - 1);
            }
        }
        return arr;
    }
}

6.快速排序

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
步骤1:从数列中挑出一个元素,称为 “基准”(pivot );
步骤2:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
步骤3:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

//快速排序 O(nlogn) 不稳定
void swap(vector<int>& arr,int i,int j){
	int temp=arr[i];
	arr[i]=arr[j];
	arr[j]=temp;
}
int partion(vector<int> arr,int low,int high){
	int privot=arr[low]; //以第一个数据作为基准
	while(low<high){
		while((low<high)&&arr[low]<=privot){
			low++;
		}
		swap(arr,low,high);
		while(low<high && array[high]>=privot){
			high--;
		}
		swap(arr,low,high);
	}
	return low; //基准数据在数组中应该存在的位置
}
void quickSort(vector<int> arr;int low,int high){
	if(low<high){
		int index=partion(array,low,high);
		quickSort(arr,low,index-1);
		quickSort(arr,index+1,high);
	}
}


当数据量很小(N<=20)时,快速排序效果不如插入排序,因为快速排序不稳定且有递归开销

7.堆排序

堆排序的基本思路:
堆排序的基本思想是:将待排序序列构造成一个大顶堆(升序),此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
在这里插入图片描述
步骤1:将待排序序列构造成一个大顶堆,从最后一个非叶子节点开始调整,最后一个非叶子节点i=(n-2)/2,然后i–(i为相邻的非叶子节点)
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换

//时间复杂度O(nlogn) 不稳定
class Head{
public:
	int headsort(vector<int> arr){
		int n=arr.size();
		//首先将待排序的数组构建成大顶堆
		//从最后一个非叶子节点开始操作
		for(int i=(n-2)/2;i>=0;i--){
			downAdjust(arr,i,n-1);
		}
		//步骤二 不断将堆顶元素与末尾元素进行交换
		for(int i=n-1;i>=0;i--){
			int temp=arr[i];
			arr[i]=arr[0];
			arr[0]=temp;
			//调整打乱的堆
			downAdjust(arr,0;i-1); //i-1既是将末尾元素放置,因为此时末尾元素为最大值
		}
		return arr;
	}
	//元素下沉操作,从根节点开始调整堆
	void downAdjust(vector<int> arr,int parent,int n){
		int temp=arr[parent];
	   //先定位到其左节点
		int child=2*parent-1while(child<=n){
			//如果右节点>左节点,则定位到右节点
			if(child+1<=n&&(arr[child]<arr[child+1])){
				child++;
			}
			if(arr[child]<temp){break;}//满足要求,不必调整
			arr[parent]=arr[child] //互换
			arr[child]=temp;
			parent=child;  //将该节点作为父节点,继续调整
			child=2*parent-1;
		}
		arr[parent]=temp;
	}
};				
				

8.计数排序

计数排序(Counting sort) 是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对有确定范围的整数进行排序。

8.1 算法描述
步骤1:找出待排序的数组中最大和最小的元素;
步骤2:统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
步骤3:对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
步骤4:反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

//时间复杂度O(n+k),空间复杂度O(k) 稳定
int countingSort(vector<int> arr){
  int min=arr[0],max=arr[0];
  for(int i=1;i<arr.size();i++){
  	if(arr[i]<min){min=arr[i];}
  	if(arr[i]>max){max=arr[i];}
  }
  int bucketlen=max-min+1;
  //int bucketmin=0-min; 
  //优化版本取最大最小值的差,所以构造的时候需要减偏移量min,最后在加上
  vector<int> bucket(bucketlen,0);
  for(int i=0;i<bucketlen;i++){
  	bucket[arr[i]-min]++;
  }
  int i=0;
  for(int j=0;j<bucketlen;j++){
	while(buckec[j]>0){
		arr[i++]=j+min;
		bucket[j]--;
	}
 }
 return arr;
}
		

算法分析:计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。

9.桶排序

桶排序 是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排
算法描述
步骤1:人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
步骤2:遍历输入数据,并且把数据一个一个放到对应的桶里去;
步骤3:对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
步骤4:从不是空的桶里把排好序的数据拼接起来。

//稳定 时间复杂度O(n+k),空间复杂度O(n+k)
void bucketSort(vector<int> arr,int bucketsize){ //设置一个桶装几个数
  int min=arr[0],max=arr[0];
  for(int i=1;i<arr.size();i++){
  	if(arr[i]<min){min=arr[i];}
  	if(arr[i]>max){max=arr[i];}
  }
  int bucketcount=(max-min+1)/bucketsize; //计算需要几个桶
  vector<vector<int>> buckets(bucketcount);
  for(int i=0;i<arr.size();i++){
  	int index=(arr[i]-min)/bucketsize;
  	buckets[index].push_back(arr[i]);
  }
  int index=0;
  for(vector<int> bucket:buckets){
 	 if(!bucket.empty()){
  		quickSort(bucket); //选择一个排序方式给桶中数据排序
  		for(auto num:bucket){
  			arr[index++]=num;
  		}
  	}
  }
}
  		
  

稳定算法;
常见排序算法中最快的一种;
适用于小范围(最大值和最小值差值较小),独立均匀分布的数据;
可以计算大批量数据,符合线性期望时间;
外部排序方式,需额外耗费n个空间;

10.基数排序

基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数;
算法步骤:
1.将各待比较元素数值统一数位长度,即对数位短者在前补零;
2.根据个位数值大小,对数组进行排序;
3.重复上一步骤,依次根据更高位数值进行排序,直至到达最高位;
在这里插入图片描述

//时间:O(kn) 空间O(k+n)
public class RadioSort {
    public static int[] radioSort(int[] arr) {
        if(arr == null || arr.length < 2) return arr;

        int n = arr.length;
        int max = arr[0];
        // 找出最大值
        for (int i = 1; i < n; i++) {
            if(max < arr[i]) max = arr[i];
        }
        // 计算最大值是几位数
        int num = 1;
        while (max / 10 > 0) {
            num++;
            max = max / 10;
        }
        // 创建10个桶
        ArrayList<LinkedList<Integer>> bucketList = new ArrayList<>(10);
        //初始化桶
        for (int i = 0; i < 10; i++) {
            bucketList.add(new LinkedList<Integer>());
        }
        // 进行每一趟的排序,从个位数开始排
        for (int i = 1; i <= num; i++) {
            for (int j = 0; j < n; j++) {
                // 获取每个数最后第 i 位是数组
                int radio = (arr[j] / (int)Math.pow(10,i-1)) % 10;
                //放进对应的桶里
                bucketList.get(radio).add(arr[j]);
            }
            //合并放回原数组
            int k = 0;
            for (int j = 0; j < 10; j++) {
                for (Integer t : bucketList.get(j)) {
                    arr[k++] = t;
                }
                //取出来合并了之后把桶清光数据
                bucketList.get(j).clear();
            }
        }
        return arr;
    }
}

稳定算法;
适用于正整数数据(若包含负数,那么需要额外分开处理);
对于实数,需指定精度,才可使用此算法。

8,9,10三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序: 根据键值的每位数字来分配桶
计数排序: 每个桶只存储单一键值
桶排序: 每个桶存储一定范围的数值

十大经典排序算法中的三种排序算法分别是冒泡排序、希尔排序和选择排序。 冒泡排序是一种简单直观的入门排序算法,其原理是从第一个元素开始,与后面的元素逐个比较,如果顺序不对就交换,直到没有可比较的元素为止。 希尔排序是插入排序的一种高效改进版本,也称为“缩小增量排序”。该算法将记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量的逐渐减小,每组包含的关键词越来越多,当增量减至1时,整个文件被分成一组,排序完成。 选择排序是基于冒泡排序的优化,减少了交换的次数。每次遍历后找出最小/最大的元素,然后与第一个元素交换,再从剩下的元素中重复这个过程。 以上是关于冒泡排序、希尔排序和选择排序的简要介绍。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [c++十大经典排序算法](https://blog.csdn.net/zhoujiajie0521/article/details/122183332)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [十大经典排序算法C++)](https://blog.csdn.net/qq_52639492/article/details/122106147)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值