冒泡排序、选择排序、插入排序、快速排序

整理自视频,侵删:https://www.bilibili.com/video/BV15b4y117RJ?spm_id_from=333.999.0.0

一、冒泡排序

冒泡排序文字描述(以升序为例):
1.依次比较数组中相邻两个元素的大小,若a[j]>a[j+1],则交换连个元素,两两都比较一遍称为一轮冒泡,结果是让最大的元素排至最后
2.重复以上步骤,直至整个数组有序

冒泡排序是一种稳定的排序算法

代码:

	public static int[] swap(int[] a,int i,int j){
		int temp=a[i];
		a[i]=a[j];
		a[j]=temp;
		return a;
	}
	//平均时间复杂度O^2
	public static int[] bubble(int[] a){
		for(int j=0;j<a.length-1;j++){
			boolean swapped=false; //是否交换
			for(int i=0;i<a.length-1-j;i++){
				if(a[i]>a[i+1]){
					swap(a,i,i+1);
					swapped=true;
				}
			}
			if(!swapped){
				break;
			}
		}
		return a;
	}
	//平均时间复杂度O^2,在有序的情况下是O(只需要进行一轮冒泡)
	public static int[] bubble_v2(int[] a){
		while(true){
			int last=a.length-1;  //最后一次交换的索引
			for(int i=0;i<last;i++){
				if(a[i]>a[i+1]){
					swap(a,i,i+1);
					last=i;
				}
			}
			if(last==0){
				break;
			}
		}
		return a;
	}

二、选择排序

选择排序文字描述(以升序为例):
1.将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集
2.重复以上步骤,直至整个数组有序

选择排序是一种不稳定的排序算法

	//在有序度高的情况下,冒泡优秀于选择
	//平均时间复杂度O^2 ,一般情况选择优于冒泡,因为冒泡排序交换次数多,
	public static int[] select(int[] a){
		for(int j=0;j<a.length-1;j++){
			int min=j;
			for(int i=min+1;i<a.length-1;i++){
				if(a[i]<a[min]){
					min=i;
				}
			}
			if(min!=j){
				swap(a,min,j);  //优化:为减少交换次数,每一轮可以先找最小的索引,在每轮最后再交换元
				//素
			}

		}

		return a;
	}

三、插入排序

插入排序文字描述(以升序为例):
1.将数组分位两个区域,排序区域和未排序区域,每一轮从未排序区域中取出第一个元素,插入到排序区域(需要保证顺序)
2.重复以上步骤,直至整个数组有序

插入排序是一种稳定的排序算法

优化方式:
1.待插入元素进行比较时,遇到比自己小的元素,就代表找到了插入位置,无需进行后续比较
2.插入时可以直接移动元素位置,而不是交换元素

与选择排序比较:
1.二者时间复杂度都是O(n^2)
2.大部分情况下,插入略优于选择
3.有序集合插入的时间复杂度为O(n)
4.插入排序属于有序的排序算法,而选择属于不稳定排序

代码:

	public static int[] insert(int[] a){
		for(int i=1;i<a.length;i++){
			int j=i-1;
			int t =a[i];
			while(j>=0){
				if(t<a[j]){
					a[j+1]=a[j];
				}else{
					break; //退出循环,减少比较次数
				}
				j--;
			}
			a[j+1]=t;
		}
		return a;
	}

四、快速排序

快速排序文字描述:
1.每一轮排序选择一个基准点,进行分区
(1)让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区
(2)当分区完成时,基准点元素的位置就是其最终位置
2.在子分区内重复以上过程,直至子分区元素个数小于等于1

单边循环-快速排序

单边循环快速排序(lomuto洛穆托分区方案)
1.选择最右边的元素作为基准点元素
2.j 指针负责找到比基准点元素小的元素,一旦找到则与i进行交换
3.i 指针维护小于基准点元素的边界,也是每次交换的目标索引
4.最后基准点与i交换,i即为分区位置

代码:

	public static void quick(int[] a,int l,int h){
		if(l>=h){
			return;
		}
		int p = partition1(a,l,h);  // p:基准点的索引值
		quick(a,l,p-1);  //左边分区的范围确定
		quick(a,p+1,h);  //右边分区的范围确定
	}
     //单边循环——分区
	public static int partition1(int[] a,int l,int h){ //l,h分区的上下边界
		//返回值代表了基准点元素所在的正确索引,用它确定下一轮分区的上下边界
		int pv=a[h]; //基准点元素
		int i=l;
		for(int j=l;j<h;j++){
			if(a[j]<pv){
				if(i!=j){
					swap(a,i,j);   //优化:加个判断条件
				}
				i++;
			}
		}
		if (i!=h){
			swap(a,h,i);  //优化:加个判断条件
		}

		return i;
	}

双边循环-快速排序

双边循环快速排序(并不完全等价于hoare霍尔分区方案)
1.选择最左边的元素作为基准点元素
2.j 指针负责从右向左找比基准点小的元素,i指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至i, j相交
3.最后记住那点与i(此时i与j相等)交换,i即为分区位置

	public static void quick(int[] a,int l,int h){
		if(l>=h){
			return;
		}
		int p = partition1(a,l,h);  // p:基准点的索引值
		quick(a,l,p-1);  //左边分区的范围确定
		quick(a,p+1,h);  //右边分区的范围确定
	}
		//双边循环——分区
	public static int partition2(int[] a,int l,int h){ //l,h分区的上下边界
		//返回值代表了基准点元素所在的正确索引,用它确定下一轮分区的上下边界
		int pv=a[l]; //基准点元素
		int i=l;
		int j=h;
		while(i<j){
			//基准点在左边,先循环j,后循环i,顺序不可颠倒
			// j从右找比基准点元素小的元素
			while(i<j && a[j]>pv){
				j--;
			}
			// i从左找比基准点元素大的元素
			while(i<j && a[i]<=pv){ //i<j why? --避免基准点元素被错误地交换
				i++;
			}

			swap(a,i,j);
		}
        swap(a,l,j); //此时i==j
		return j;
	}

快速排序特点

1.平均时间复杂度是O(nlog2n),最坏时间复杂度O(n^2)
2.数据量较大时,优势非常明显
3.属于不稳定排序

若数组中重复元素很多,则快排优势减小

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值