基础排序算法

2023-04-12

基础排序算法

冒泡排序


十分经典的排序算法,其思想就是依次比较相邻的几个元素,如果逆序就进行交换,如此一趟排序就可以将最大的一个数放到最后,或者将最小的一个数放到最前,就像冒泡一样,因此得名冒泡排序。 算法实现:


void BubbleSort(vector&v){
	int sz=v.size();
	for(int i=1;iv[j]){//有等号时没有稳定性
				swap(v[j-1],v[j]);
				ok=false;
			}
		}
		if(ok)break;
	}
}

时间复杂度:O(N^2) 空间复杂度:O(1) 稳定性:稳定 当for循环的判断为>=或者<=时不具备稳定性,因为交换了等值数


插入排序


插入排序就像打牌时插牌一样,拿到一个数之后向已经有序的数组插入,只需要不断向前交换直到前面没有数或者前一个数不大于这个数为止。 算法实现:


void InsertSort(vector&v){
	for(int i=1;i=0&&v[tmp-1]>v[tmp]){
			swap(v[tmp-1],v[tmp]);
			--tmp;
		}
	}
}

时间复杂度:O(N^2) 需要注意,插入排序在排序基本有序的数据时有独特的优势,最好可以达到O(N),在排序完全逆序数时是最坏情况,为O(N^2) 空间复杂度:O(1) 稳定性:稳定 与冒泡排序同理,若交换等值数不具备稳定性


选择排序


十分朴素的一种排序算法,通过对数据进行一次完整遍历来选择出最值,然后将其放到最左或者最右。 算法实现:


void SelectSort(vector&v){
	int left=0,right=v.size()-1;
	while(leftv[maxi])maxi=j;
			if(v[j]

时间复杂度:O(N^2) 空间复杂度:O(1) 稳定性:不稳定 这里需要格外注意,很多人认为选择排序选最值的方法是稳定的,但是当面对特殊情况下会不稳定,例如:【2,2,1,1】,当选择出最小值时与最左边的值交换,这样就破坏了数据2内的稳定性


希尔排序


希尔排序的核心思想与插入排序一致,但是希尔排序会先对数组进行预排序,使之基本有序,当进行最后一趟排序(也就是插入排序时)会变得很快。 预排序时,先每个几个元素进行预插入排序,这样可以使大数很快的跳到后部,小数很快的跳的前部,提高了效率。 算法实现;


void ShellSort(vector&v){
	int gap=v.size();
	while(gap>1){
		gap=gap/3+1;//也可以选择/2,只要最后一趟是1即可
		for(int i=gap;i=0&&v[tmp-gap]>v[tmp]){
				swap(v[tmp-gap],v[tmp]);
				tmp-=gap;
			}
		}
	}
}

时间复杂度:希尔排序的时间复杂度很难进行分析,目前一般认为是O(N^(1.3))左右 空间复杂度:O(1) 稳定性:不稳定 虽然插入排序是稳定的,但是希尔排序因为进行了分组,使得同一个数在一组里面可能跳到前面也可能跳到后面,破坏了稳定性


堆排序


堆排序利用了二叉堆这种数据结构的性质,它可以轻松的选出一组数里权最大的元素,我们只需要建堆,然后不断将堆顶元素放到数据末端即可。 算法实现:


void HeapSort(vector& v){
	int sz=v.size();
//向下调整建堆
	for(int i=(sz-1-1)/2;i>=0;--i){
		int father=i,child=(i*2)+1;
		while(childv[child]){
				++child;
			}
			if(v[child]>v[father]){
				swap(v[child],v[father]);
			}else break;
			father=child;
			child=(child*2)+1;
		}
	}
//将堆顶元素与末尾元素交换,然后将堆顶元素向下调整即可
	while(sz--){
		swap(v[0],v[sz]);
		int father=0,child=1;
		while(childv[child]){
				++child;
			}
			if(v[child]>v[father]){
				swap(v[child],v[father]);
			}else break;
			father=child;
			child=(child*2)+1;
		}
	}
}

时间复杂度:O(NlogN) 空间复杂度:O(1) 稳定性:不稳定


快速排序


快速排序的思想就是每一趟选定一个数作为key,在一趟排序中将所有比key小的数放到k前面,比k大的数放到k后面,经过一趟这样的排序key就能在最终应该在的位置。 算法实现:


//非递归+双指针法+三数取中
void QuickSort(vector&v){
	queue>q;
	q.push ({0,v.size()-1});
	while(!q.empty()){
		int left=q.front().first,right=q.front().second;
		q.pop();
		int k= find_mid (left,right,(left+right)/2,v);
		swap(v[k],v[left]);
		k=left;
		int less,cur;
		less=cur=left;
		++cur;
		while(cur<=right){
			if(v[cur]less+1)q.push ({less+1,right});
	}
}

选key可以使用随机k法和三数取中法,可以使快排的最坏情况更难出现。 时间复杂度:O(NlogN) 空间复杂度:O(logN) 需要进行递归或者使用数据记录开始和结束位点 稳定性:不稳定


归并排序


归并排序与快速排序同样采用分治法对数据进行排序,将两组有序的数组进行归并,变成一个有序的更大的数组


//递归版本
void MergeSortRec(vector&v,int begin,int end,vector&tmp){
	if(begin>=end)return;
	int mid=(begin+end)/2;
	MergeSortRec (v,begin,mid,tmp);
	MergeSortRec (v,mid+1,end,tmp);
	int begin1=begin,end1=mid;
	int begin2=mid+1,end2=end;
	int m=begin;
	while(begin1<=end1&&begin2<=end2){
		if(v[begin1]
//非递归
void MergeSort(vector&v){
	auto tmp=v;
	int gap=1;
	while( gap < v.size()){
		int i=0;
		while( i < v.size() / gap / 2 + 1){
			int left_begin = i * 2 * gap, left_end = left_begin + gap - 1;
			int right_begin = left_end + 1, right_end = right_begin + gap - 1;
			int m = left_begin;
			//进行边界划分
			//如果rb越界,直接跳出循环,只有一组的不需要归并和拷贝
			if( right_begin >= v.size()){
				break;
			}
			else if( right_end >= v.size()){
				right_end= v.size() - 1;
			}
			printf("[%d,%d][%d,%d] ",left_begin,left_end,right_begin,right_end);
			while ( left_begin <= left_end && right_begin <= right_end ) {
				if ( v[ left_begin ] < v[ right_begin ] ) {
					tmp[ m++ ] = v[ left_begin++ ];
				} else {
					tmp[ m++ ] = v[ right_begin++ ];
				}
			}
			while ( left_begin <= left_end ) {
				tmp[ m++ ] = v[ left_begin++ ];
			}
			while ( right_begin <= right_end ) {
				tmp[ m++ ] = v[ right_begin++ ];
			}
			for(int t=i*2*gap;t<=right_end;++t){
				v[t]=tmp[t];
			}
			++i;
		}
		gap*=2;
		cout<

时间复杂度:O(NlogN) 空间复杂度:O(N) 稳定性:稳定


总结



本文仅代表作者观点,版权归原创者所有,如需转载请在文中注明来源及作者名字。

免责声明:本文系转载编辑文章,仅作分享之用。如分享内容、图片侵犯到您的版权或非授权发布,请及时与我们联系进行审核处理或删除,您可以发送材料至邮箱:service@tojoy.com