刷题(3)-排序(1)

总结

排序小结
排序
(注意:n指数据规模;k指“桶”的个数;In-place指占用常数内存,不占用额外内存;Out-place指占用额外内存)

  1. 冒泡,插入,归并排序都是保证稳定性的,其他都不是
  2. 现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较和交换。
  3. 快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 ~cNlogN,这里的 c 比其它线性对数级别的排序算法都要小。

排序(2)总结
看到一个很好的总结


工程排序特点:

工程排序中,有2个点:

  1. 当类型是自己定义的,用归并排序,而不用快排,因为要保证稳定性,当类型是内置类型的时候,用快排,因为快排比归并快(虽然复杂度一样,但是系数小,而且额外空间复杂度小 归并是O(n),快排是O(logn) 不是O(1),因为要记住那个断点!!)
  2. 当要比较的数量小的时候(通常是小于60),用插入排序,因为这时候插入排序更快,虽然复杂度高,但因为系数小,所以数量小的时候,速度更快

leetcode 验证各种排序算法
[https://blog.csdn.net/speargod/article/details/121931432]

1. 快速排序

简介

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

思想

该方法的基本思想是:

  1. 先从数列中取出一个数作为基准数。
  2. 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
  3. 再对左右区间重复第二步,直到各区间只有一个数。

就是先搞定一个数 把它放到正确的位置,再继续搞。


时间复杂度:平均O(NlgN),最坏O(N^2)

  1. 最坏的情况是:
    第一次从最小的元素切分,第二次从第二小的元素切分。
    N+N-1+N-2+…+2+1 = N^2
  2. 最好的情况是:
    每次都正好将数组对半分,这样递归调用次数才是最少的。复杂度为 O(NlogN)。

所以要把数组随机洗牌 确保有序的可能性贼小.


a) 三向切分快排,代码(主要)


void quicksort(vector<int> &vec, int left,int right){
	if(left>=right)
		return;
	
	int less=left,more=right+1;
	int cur=left+1;
	int pivot=vec[left];
	while(cur<more){
		if(ves[cur]<pivot) swap(vec[cur++],vec[++less]);
		else if(vec[cur]>pivot) swap(vec[--more],vec[cur]);
		else ++cur;
	}
	swap(vec[less],vec[left]);
	quicksort(vec,left,less-1);
	quicksort(vec,more,right);
}

三向切分的partition。主要对有大量重复元素的数组好使,这里我们要把数组分成3个区,,小于,等于,大于主元的。
tips: 对于3向切分,我们时刻记住3点即可。

  1. 一直维护3个int,less是小于主元的最后一个,more是大于主元的第一个,cur是当前正在检查的。
  2. 假如不传主元,那么我们把a[left]当作主元,那么为了方便起见,把数组从 a[left+1]开始算,但最后别忘了,要swap主元。假如传了主元,那么我们数组从a[left]开始算,这次最后就不用swap了。
  3. 把 - - more交换过来的时候,cur不动,因为不知道换过来的是什么货色。
//快排 面试版
void quicksort(vector<int>& vec){
	quicksort(vec,0,vec.size()-1);
	return;
}

void quicksort(vector<int>& vec,int l,int r){
	if(l>=r)
		return;
	
	int pivot = a[left];//选第一个元素作为主元
 
	int small = left;
	//small指向比pivot小的元素段的尾部(那为啥不是left-1呢?
	// 因为我们主元选的是a[left],所以数组实际是从a[left+1]开始,不过同样这样处理,我们最后就要交换a[small]和主元a[left])
 
	//遍历数组,如果遇到比主元小的元素依次放在前半部分
	for(int i = left+1; i <= right; i++){
		if(a[i] < pivot)
			swap(a[i], a[++small]);
	}
	swap(a[left], a[small]);//注意需要交换!

	quicksort(vec,l,small-1);
	quicksort(vec,small+1,r);

}

不传主元的partition实现

先实现不传主元,直接把a[left]当主元的版本。
vector<int> partition2(int a[],int left,int right){
	
	int pivot = a[left]; //主元
	int less=left, more=right+1,cur=left+1; //less是小于主元的最后一个,more是大于主元的第一个,cur是当前正在检查的,同样我们把数组从 a[left+1]开算.
	
	//
	【left,less】 全是小于主元的   
	【less+1,cur-1】是等于主元的
	【cur,more-1】都是我们没有遍历过的。
	【more,right】全是大于主元的
	while(cur<more){
		//假如当前小于主元就把它给扔小于区,cur向前移动,往对应的操作就是++less,less往外扩。
		if(a[cur]<pivot){
			swap(a[++less],a[cur++]);    //一个是前++ 一个是后++
		}  
		//假如当前大于主元就把它给扔到大于区,但是cur不移动,因为swap过来的是more-1的元素,这个元素我们没检查过!!!
		else if(a[cur]>pivot){
			swap(a[cur],a[--more]);
		}
		//等于主元,自然就是往前移动
		else{
			++cur;
		}
	}
	
	swap(a[less],a[left]); //注意,因为我们之前不把a[left]当做数组的一部分,所以最后要交换主元。
	
	vector<int> res;
	res.push_back(less);
	res.push_back(more);
	return res;
}

传主元版,通常用于只partition,不完全sort的题目

传主元版,通常用于只partition,不完全sort的题目
注意和上面不传主元的区别
void partition4(int a[],int left,int right,int pivot){
	
	int less=left-1, cur=left, more=right+1;
	//int small=left, more=right+1,cur=left+1;  观察和不传主元有什么区别,应该很明白了
	while(cur<more){
		if(a[cur]<pivot){
			swap(a[++less],a[cur++]);
		}
		else if(a[cur]>pivot){
			swap(a[--more],a[cur]);
		}
		else{
			++cur;
		}
	}
	
	//注意最后不用swap
	return vector 这里就不写了
}

b)基本快排,随便看看吧

有单向切分,和双向切分之分

//基本快排 把第一个元素当主元  
写快排要注意两件事:1.指针不要越界 2.两端出发的 最后哪个跟主元交换

void quicksort1(int a[],int left,int right){
	
	//只有1个元素
	if(left>=right)
		return;
	
	int p1=partition(a,left,right);
	quicksort1(a,left,p1-1);
	quicksort1(a,p1+1,right);
	
}

双向扫描的partition
int partition1(int a[],int left,int right){
	
	int pivot =a[left];	//选最左边的为主元
	
	int p1 = left,p2 =right+1;  
	//因为后面用的是++ 所以选这两个初值p1和p2代表的都是已经符合条件的
	所以下面的代码中p1>=p2的时候 break
	//主元的左边都是小于等于主元的, 右边都是大于等于主元的
	while(true){
		//左边的指针要找到一个大于等于主元的(等于为了防止极端情况,元素全部相同的时候),所以小于主元不停,同时可能数组越界,
		//比如a[left]是最大的,所以需要检查
		while(a[++p1]<pivot && p1!= right) 
		       ;
    	//右边的指针要找到一个小于等于主元的,所以大于主元不停,而且不会发生数组越界,因为a[left]是我们的主元,相当于哨兵   
	    while(a[--p2]>pivot)   
		       ;
		if(p1>=p2)
			break;
		swap(a[p1],a[p2]);
	}
	//必须把主元跟p2交换,因为选的left做主元,而要达到的目的是主元的左边都是小于等于主元的, 右边都是大于等于主元的,而p1停在一个大于等于主元的位置,p2停在一个小于等于主元的位置,要是最后p1跟主元换,那最左边就会是一个大于等于主元的数,也就是可能大于,那就gg。
	swap(a[left],a[p2]);
	return p2;
}

单向扫描的partition
int partition2(int a[], int left, int right)
 {
     int pivot = a[left];//选第一个元素作为主元
	 
     int small = left;
	 //small指向比pivot小的元素段的尾部(那为啥不是left-1呢?因为我们主元选的是a[left],所以数组实际是从a[left+1]开始,不过同样这样处理,我们最后就要交换a[small]和主元a[left])
	 
	 //遍历数组,如果遇到比主元小的元素依次放在前半部分
     for(int i = left+1; i <= right; i++){
		if(a[i] < pivot)
			swap(a[i], a[++small]);
	 }
     swap(a[left], a[small]);//注意需要交换!
     return small;
 
 }

c) 三数中值快排,且结合插入排序

//三数中值 当主元,且小数组进行插入排序的优化快排
int median3(int a[],int left,int right){
	int center = left+(right-left)/2;
	if(a[left]>a[center])
		swap(a[left],a[center]);
	if(a[left]>a[right])
		swap(a[left],a[center]);
	if(a[right]<a[center])
		swap(a[right],a[center]);
	
	swap(a[center],a[right-1]);
	return a[right-1];            //实现三数中值分割,并且a[right-1]当做主元,并且a[left]<a[right-1]<a[right] ,把两个当做了哨兵,避免了越界检查
	
}
const int cutoff=3;

void quicksort2(int a[],int left,int right){
	if(left +cutoff<=right){    //大数组用快排
		int pivot = median3(a,left,right);
		int p1 = left,p2=right-1;
		while(true){
			while(a[++p1]<pivot){}         //因为median3的实现,对数组动了手脚,所以两个都不需要越界检查
			while(a[--p2]>pivot){}
			if(p1<p2)
				swap(a[p1],a[p2]);
			else
				break;
		}
		swap(a[p1],a[right-1]);        //这里换p1,不换p2,就是因为主元的位置在右边!,所以换哪个跟主元的位置有关
		
		quicksort2(a,left,p1-1);
		quicksort2(a,p1+1,right);
		
	}
	else            
		insertsort(a,left,right); //小数组用插入排序
}

d) 快排非递归版

其实就是用栈来保存下一次要排的两个部分的两个左右指针。

void QuickSort(int *a, int left,int right)
{
    if (a == NULL || left < 0 || right <= 0 || left>right)
        return;
    stack<int>temp;
    int i, j;
    //(注意保存顺序)先将初始状态的左右指针压栈
    temp.push(right);//先存右指针
    temp.push(left);//再存左指针
    while (!temp.empty())
    {
        i = temp.top();//先弹出左指针
        temp.pop();
        j = temp.top();//再弹出右指针
        temp.pop();
        if (i < j)
        {
            int k = Pritation(a, i, j);
            if (k > i)
            {
                temp.push(k - 1);//保存中间变量
                temp.push(i);  //保存中间变量 
            }
            if (j > k)
            {
                temp.push(j);
                temp.push(k + 1);
            }
        }

    }
    
}
//快排 面试版
void quicksort(vector<int>& vec){
	quicksort(vec,0,vec.size()-1);
	return;
}

void quicksort(vector<int>& vec,int l,int r){
	if(l>=r)
		return;
	
	int pivot=vec[l];
	int less=l;
	for(int i=l+1;i<=r;++i){
		if(vec[i]<pivot)
			swap(vec[i],vec[++less]);
	}
	
	swap(vec[l],vec[less]);
	quicksort(vec,l,less-1);
	quicksort(vec,less+1,r);

}

排序(2)总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值