算法学习笔记——归并排序

归并排序

归并是一种基于分治的算法,就是将一个数组依次折半拆分成一个个小数组依次排序,最后将这些排序好的数组再合并起来。

(分治:大问题拆分为小问题逐个解决)

逐步分析

假设我们有{5,8,1,0,2,9,7,4}这样一个数组。需要我们进行从小到大进行排序。

第一次

我们将其拆分成{5,8,1,0} , {2,9,7,4}这样两个数组。

第二次

再进行拆分:{5,8},{1,0},{2,9},{7,4}四个数组。

第三次就会变成8个单独的数字。

{5}, {8} , {1} , {0} , {2} , {9} , {7} , {4};

最后我们将

5和8排序,1和0排序,2和9排序,7和4排序(按之前分出来的数组进行排序)

就得到了这样四个数组

{5 , 8} , {0, 1} { 2 , 9 } , { 4 , 7 }.

​ ↑ ↑ ↑ ↑

​ ① ② ③ ④

此处需要注意:

我们之前是如何将大数组分割成小数组的,在归并的时候也要按照原本的顺序归并回去。

之后将①②进行排序并合并得到{0, 1, 5, 8},同理通过③④得到{2, 4, 7, 9}。

最后一步就可以将两个刚刚得到的数组合并得到最终排序好的数组啦。

算法分析与实现

在刚刚的分析过程中可以看到,在分割后的每一步都是将两个数组进行合并同时排序得到一个新的数组,处理步骤都是一样的,因此在实际的代码中我们就可以利用递归的方法实现上述算法。

归并排序因为对数组进行持续的折半排序,因此时间复杂度是O(nlogn),最好最坏情况都是一样的,因为都得遍历整个数组,无法进行进化(进化:成为更高时间复杂度的排序算法),同时,归并排序也是一种稳定的排序算法(假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。)如稳定性不理解可查阅相关资料。

现在要注意的问题就是如何将两个数组合并而且排序得到一个新的数组:

这里我们采用一个很常用的算法思想——双指针。

引用刚刚的例子:{0, 1, 5, 8} , {2, 4, 7, 9}两个数组,这里用a,b代替。

先创建一个临时数组temp。用两个指针(迭代器等)pa,pb分别指向a和b的开头,然后比较pa,pb的大小。

将较小的数字放入temp然后将该指针进行自增操作。

就比如:pa和pb一开始指向a[0],b[0]。

​ 比较a[0],b[0],也就是比较0和2,将a[0]的值放入temp,然后pa++,pa就指向了a[1]。

​ 之后我们就可以用a[1]和b[0]比较a[1]<b[0],a[1]放入temp,pa++。

​ a[2]和b[0]比较,b[0]<a[2],b[0]放入temp,pb++,pb就指向了b[1].

​ 然后我们就可以用a[2]和b[1]按照上述方法进行比较。

​ 以此类推,我们就可以得到一个排序好的数组。

但其实目前为止所说的双指针算法还没有完全完成,可以自行考虑还有哪里有问题,后续代码注释给出答案。

这里给出代码和注释:

(归并排序的算法实现上各有千秋,本文仅简述思想,代码仅供参考,可自行改动以尝试改进效率)

//归并排序
//传入数组,需要进行排序的左右下标,整个数组排序就传入0和vec.size()
//默认从小到大
void mergeSort(vector<int>& vec, int l, int r) {
	if (l >= r) return;   
    //判断是否分割完毕
	
    int mid = (l + r) / 2;
    
    //递归排序数组
	mergeSort(vec, l, mid);
    //vec[l...mid]代表前半段
	mergeSort(vec, mid+1, r);
    //vec[mid+1...r]代表后半段
    
    
    /*
    注意这里每次递归后的l和r就是代表vec分组后各个小数组的开头和结尾
    l和r代表的意思一定要理解清楚
    */
    
    
	//下面开始将递归排序好的两个数组依次合并
	vector<int> temp(r - l + 1);
	for (int i = l;i <= r; i++) {
		temp[i-l] = vec[i];
	}
	//第一步,临时空间赋值
    //这里和刚刚解释的有所不同。我们是用临时数组temp存储原数组vec中的值,排序好的数组直接放回vec

    //第二步,将分好的两个数组vec[l...mid],vec[mid+1...r]进行双指针的合并操作。
	int i = l, j = mid + 1;
	for (int k = l;k <= r;k++) {
        /*回到代码开始前提到的问题:
        	完善我们的双指针算法。
        	利用双指针遍历两个数组的时候,如果其中一个数组遍历完成的时候就需要将剩下一个数组所有数据一起放入temp,
        	否则会出现数据不完成。
        	比如{1,3,5,9} {2,4,6,10,12,31};
        	当我们把1,2,3,4,5,6,9全部放入数组后,10、12、31还没有放进去
        	就需要我们将这三个数一同放入排序好的数组。
        	
        	下述①②就是判断是否还有空余数据。
        */
		if (i > mid) {//①
			vec[k] = temp[j - l];
			j++;
		}
		else if(j>r){//②
			vec[k] = temp[i - l];
			i++;
		}
        //下述是双指针的比较并合并
		else if (temp[i - l] < temp[j - l]) {
			vec[k] = temp[i - l];
			i++;
		}else{
			vec[k] = temp[j - l];
			j++;
		}
	}
}

代码测试截图:在这里插入图片描述
)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于 jQuery 的事件,我可以和你分享一些学习笔记。 1. 绑定事件 在 jQuery 中,我们可以通过以下方式来绑定事件: ``` $(selector).event(function(){ // 事件处理程序 }) ``` 其中,`selector` 表示要绑定事件的元素,`event` 表示要绑定的事件类型,比如 `click`、`mouseover` 等等。事件处理程序则是在事件触发时要执行的代码块。 2. 多个事件绑定 我们可以通过 `on()` 方法来同时绑定多个事件: ``` $(selector).on({ event1: function(){ // 事件处理程序1 }, event2: function(){ // 事件处理程序2 } }) ``` 这样,当 `event1` 或 `event2` 中任意一个事件触发时,对应的处理程序都会被执行。 3. 解除事件 如果需要解除某个元素的事件处理程序,可以使用 `off()` 方法: ``` $(selector).off(event); ``` 其中,`event` 表示要解除的事件类型。如果不指定事件类型,则会解除该元素上所有的事件处理程序。 4. 事件委托 在 jQuery 中,我们可以使用事件委托来提高性能。事件委托是指将事件绑定到父元素上,而不是绑定到子元素上,然后通过事件冒泡来判断是哪个子元素触发了该事件。这样,当子元素数量较多时,只需要绑定一次事件,就可以监听到所有子元素的事件。 ``` $(selector).on(event, childSelector, function(){ // 事件处理程序 }) ``` 其中,`selector` 表示父元素,`event` 表示要绑定的事件类型,`childSelector` 表示要委托的子元素的选择器,事件处理程序则是在子元素触发事件时要执行的代码块。 以上是 jQuery 中事件的一些基本操作,希望对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值